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

View File

@ -29,31 +29,36 @@ jobs:
run: ./gradlew build # if test's failed, don't publish
- name: Check keys
run: ./gradlew :mirai-core-utils:ensureBintrayAvailable
:mirai-core-api:ensureBintrayAvailable
:mirai-core:ensureBintrayAvailable
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
run: >-
./gradlew :mirai-core-utils:ensureBintrayAvailable
:mirai-core-api:ensureBintrayAvailable
:mirai-core:ensureBintrayAvailable
-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
run: ./gradlew :mirai-core-utils:publish --info
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
run: >-
./gradlew :mirai-core-utils:publish --info
-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
run: ./gradlew :mirai-core-api:publish --info
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
run: >-
./gradlew :mirai-core-api:publish --info
-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
run: ./gradlew :mirai-core:publish --info
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
run: >-
./gradlew :mirai-core:publish --info
-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
run: ./gradlew :mirai-core-all:bintrayUpload
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
run: >-
./gradlew :mirai-core-all:bintrayUpload
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
# - name: Upload artifact
# 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
**Development document** [docs/mirai.md](docs/mirai.md)
[CHANGELOG](https://github.com/mamoe/mirai/blob/master/CHANGELOG.md)
### Use as a framework
Mirai is able to run as a plugin-supported framework.

View File

@ -109,84 +109,27 @@ mirai 是一个在全平台下运行,提供 QQ Android 协议支持的高效
## 开始
### 在开始之前,建议你了解一下 Mirai 生态
[Mirai 生态概览](docs/mirai-ecology.md)
### 文档
**对于一般使用者, 更建议使用 [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)
- 开发文档(新,编写中):[docs](docs/README.md)
- 开发文档(旧):[docs/mirai.md](docs/mirai.md)
- 更新日志: [release](https://github.com/mamoe/mirai/releases)
- 开发计划: [milestones](https://github.com/mamoe/mirai/milestones)
- 贡献: [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相关项目合集**
#### 从其他平台迁移
- 酷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
- 常见问题: [docs/FAQ.md](docs/FAQ.md)
## [贡献](CONTRIBUTING.md)
我们欢迎一切形式的贡献。
我们也期待有更多人能加入 mirai 的开发。
若在使用过程中有任何疑问,可提交 `issue` 或是[邮件联系](mailto:support@mamoe.net). 我们希望 mirai 变得更易用.
若在使用过程中有任何疑问,可提交 [`issue`](https://github.com/mamoe/mirai/issues) 或在 [`Discussions`](https://github.com/mamoe/mirai/discussions) 讨论。 我们希望 mirai 变得更易用.
您的 `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-console 和相关社区开发, 请 [邮件联系](mailto:support@mamoe.net) (`support@mamoe.net`), 并附加相关开发经验证明.

View File

@ -21,7 +21,7 @@ import org.gradle.api.attributes.Attribute
*/
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 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-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.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 修正。
实体关系图采用 [Mermaid](https://github.com/mermaid-js/mermaid) 绘制。
> [回到 Mirai 文档索引](README.md)

View File

@ -26,7 +26,7 @@ mirai 项目整体由 核心 (`mirai-core`) 与 控制台(`mirai-console`) 组
`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-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 {
api(project(":mirai-core"))
api(project(":mirai-core-api"))
api(project(":mirai-core-utils"))
}
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.MiraiLogger
import net.mamoe.mirai.utils.PlannedRemoval
import java.util.*
import kotlin.NoSuchElementException
import java.util.concurrent.ConcurrentHashMap
/**
* 登录, 返回 [this]
@ -153,14 +152,14 @@ public interface Bot : CoroutineScope, ContactOrBot, UserOrBot {
public companion object {
@Suppress("ObjectPropertyName")
internal val _instances: WeakHashMap<Long, Bot> = WeakHashMap()
internal val _instances: ConcurrentHashMap<Long, Bot> = ConcurrentHashMap()
/**
* 复制一份此时的 [Bot] 实例列表.
*/
@JvmStatic
public val instances: List<Bot>
get() = _instances.values.filterNotNull()
get() = _instances.values.toList()
/**
* 复制一份此时的 [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]. 否则返回相应的成员
*/
public val owner: Member
public val owner: NormalMember
/**
* [Bot] 在群内的 [Member] 实例
*/
@MiraiExperimentalApi
public val botAsMember: Member
public val botAsMember: NormalMember
/**
* 机器人被禁言还剩余多少秒
@ -89,14 +89,14 @@ public interface Group : Contact, CoroutineScope {
*
* [Group] 实例创建的时候查询一次. 并与事件同步事件更新.
*/
public val members: ContactList<Member>
public val members: ContactList<NormalMember>
/**
* 获取群成员实例. 不存在时返回 `null`.
*
* [id] [Bot.id] 时返回 [botAsMember].
*/
public operator fun get(id: Long): Member?
public operator fun get(id: Long): NormalMember?
@Deprecated("Use get", ReplaceWith("get(id)"))
@PlannedRemoval("2.0-M2")
@ -104,14 +104,14 @@ public interface Group : Contact, CoroutineScope {
* 获取群成员实例, 不存在则 null
* [id] [Bot.id] 时返回 [botAsMember]
*/
public fun getOrNull(id: Long): Member? = get(id)
public fun getOrNull(id: Long): NormalMember? = get(id)
/**
* 获取群成员实例. 不存在时抛出 [kotlin.NoSuchElementException].
*
* [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}")
@ -125,7 +125,7 @@ public interface Group : Contact, CoroutineScope {
/**
* [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
import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.Bot
import net.mamoe.mirai.JavaFriendlyAPI
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.event.events.BotMuteEvent
import net.mamoe.mirai.event.events.MemberMuteEvent
import net.mamoe.mirai.event.events.MemberPermissionChangeEvent
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 net.mamoe.mirai.utils.WeakRefProperty
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
/**
* 代表一位群成员.
*
* 群成员可能也是好友, 但他们在对象类型上不同.
* 群成员可以通过 [asFriend] 得到相关好友对象.
* 群成员分为 [普通成员][NormalMember] [匿名成员][AnonymousMember]
*
* ## 与好友相关的操作
* 一个群成员可能也是机器人的好友, 但他们在对象类型上不同 ([Member] != [Friend]). 可以通过 [Member.asFriend] 得到相关好友对象.
*
* ## 相关的操作
* [Member.isFriend] 判断此成员是否为好友
* [Member.isAnonymous] 判断此成员是否为匿名群成员
* [Member.isNormal] 判断此成员是否为正常群成员
*/
@Suppress("INAPPLICABLE_JVM_NAME", "EXPOSED_SUPER_CLASS")
@OptIn(JavaFriendlyAPI::class)
public interface Member : User {
/**
* 所在的群.
@ -56,125 +52,62 @@ public interface Member : User {
/**
* 群名片. 可能为空.
*
* 管理员和群主都可修改任何人包括群主的群名片.
*
* 在修改时将会异步上传至服务器.
*
* @see [nameCardOrNick] 获取非空群名片或昵称
*
* @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件.
* @throws PermissionDeniedException 无权限修改时
* @see [NormalMember.nameCard]
* @see [AnonymousMember.nameCard]
*/
public var nameCard: String
public val nameCard: String
/**
* 群头衔.
*
* 仅群主可以修改群头衔.
* [AnonymousMember] 时一定是 `"匿名"`
*
* 在修改时将会异步上传至服务器.
*
* @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件.
* @throws PermissionDeniedException 无权限修改时
* @see [NormalMember.specialTitle]
*/
public var specialTitle: String
public val specialTitle: String
/**
* 被禁言剩余时长. 单位为秒.
*
* @see isMuted 判断改成员是否处于禁言状态
* @see mute 设置禁言
* @see unmute 取消禁言
*/
@MemberDeprecatedApi("仅 NormalMember 支持 muteTimeRemaining. 请先检查类型为 NormalMember.")
@PlannedRemoval("2.0-M2")
public val muteTimeRemaining: Int
/**
* 禁言.
* 禁言这个群成员 [durationSeconds] , 在机器人无权限操作时抛出 [PermissionDeniedException].
*
* QQ 中最小操作和显示的时间都是一分钟.
* 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间.
* QQ 中最小操作和显示的时间都是一分钟. 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间.
*
* 管理员可禁言成员, 群主可禁言管理员和群员.
*
* @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常.
* @return 机器人无权限时返回 `false`
* @param durationSeconds 持续时间. 精确到秒. 最短 0 , 最长 30 . 超过范围则会抛出异常 [IllegalStateException].
*
* @see Member.isMuted 判断此成员是否正处于禁言状态中
* @see unmute 取消禁言此成员
*
* @see Int.minutesToSeconds
* @see Int.hoursToSeconds
* @see Int.daysToSeconds
* @see NormalMember.isMuted 判断此成员是否正处于禁言状态中
* @see NormalMember.unmute 取消禁言此成员
*
* @see MemberMuteEvent 成员被禁言事件
* @see BotMuteEvent Bot 被禁言事件
*
* @throws PermissionDeniedException 无权限修改时抛出
* @see Member.mute 支持 Kotlin [kotlin.time.Duration] 的扩展
*/
@JvmBlockingBridge
public suspend fun mute(durationSeconds: Int)
/**
* 解除禁言.
*
* 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言.
*
* @see Member.isMuted 判断此成员是否正处于禁言状态中
*
* @see MemberUnmuteEvent 成员被取消禁言事件
*
* @throws PermissionDeniedException 无权限修改时抛出
*/
@JvmBlockingBridge
@MemberDeprecatedApi("仅 NormalMember 支持 unmute. 请先检查类型为 NormalMember.")
@PlannedRemoval("2.0-M2")
public suspend fun unmute()
/**
* 踢出该成员.
*
* 管理员可踢出成员, 群主可踢出管理员和群员.
*
* @see MemberLeaveEvent.Kick 成员被踢出事件.
* @throws PermissionDeniedException 无权限修改时
*/
@JvmBlockingBridge
@MemberDeprecatedApi("仅 NormalMember 支持 kick. 请先检查类型为 NormalMember.")
@PlannedRemoval("2.0-M2")
public 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
@MemberDeprecatedApi("仅 NormalMember 支持 sendMessage. 请先检查类型为 NormalMember.")
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())
@MemberDeprecatedApi("仅 NormalMember 支持 sendMessage. 请先检查类型为 NormalMember.")
public override suspend fun sendMessage(message: String): MessageReceipt<Member>
/**
* 创建一个 "戳一戳" 消息
*
* @see Nudge.sendTo 发送这个戳一戳消息
*/
@MemberDeprecatedApi("仅 NormalMember 支持 nudge. 请先检查类型为 NormalMember.")
@PlannedRemoval("2.0-M2")
@MiraiExperimentalApi
public override fun nudge(): MemberNudge = MemberNudge(this)
public override fun nudge(): MemberNudge
}
/**
@ -200,7 +133,8 @@ public inline val Member.isFriend: Boolean
*/
@Deprecated(
"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")
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]
*/
public val Member.nameCardOrNick: String get() = this.nameCard.takeIf { it.isNotEmpty() } ?: this.nick
/**
* 获取非空群名片或昵称.
*
* @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())
public val Member.isNormal: Boolean get() = this is NormalMember
public val Member.isAnonymous: Boolean get() = this is AnonymousMember

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
import kotlinx.coroutines.*
import net.mamoe.mirai.utils.EventListenerLikeJava
import java.lang.reflect.Method
import kotlin.coroutines.CoroutineContext
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")
private fun Method.registerEvent(
@ -248,7 +266,7 @@ private fun Method.registerEvent(
): Listener<Event> {
this.isAccessible = true
val kotlinFunction = kotlin.runCatching { this.kotlinFunction }.getOrNull()
return if (kotlinFunction != null) {
return if (kotlinFunction != null && isKotlinFunction()) {
// kotlin functions
val param = kotlinFunction.parameters
@ -337,7 +355,7 @@ private fun Method.registerEvent(
val paramType = this.parameters[0].type
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) {
Void::class.java, Void.TYPE, Nothing::class.java -> {
@ -350,11 +368,11 @@ private fun Method.registerEvent(
if (annotation.ignoreCancelled) {
if ((this as? CancellableEvent)?.isCancelled != true) {
withContext(Dispatchers.IO) {
this@registerEvent.invoke(owner, this)
this@registerEvent.invokeWithErrorReport(owner, this@subscribeAlways)
}
}
} 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 ((this as? CancellableEvent)?.isCancelled != true) {
withContext(Dispatchers.IO) {
this@registerEvent.invoke(owner, this) as ListeningStatus
this@registerEvent.invokeWithErrorReport(owner, this@subscribe) as ListeningStatus
}
} else ListeningStatus.LISTENING
} 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
### [消息](message.kt)
- (`1.1.0-`) 主动发送消息: MessageSendEvent
- 群消息: GroupMessageSendEvent
- 好友消息: FriendMessageSendEvent
- 被动收到消息MessageEvent
- 群消息GroupMessageEvent
- 好友消息FriendMessageEvent
- 群临时会话消息TempMessageEvent
- (`1.1.0+`) 主动发送消息前: MessagePreSendEvent
- 群消息: GroupMessagePreSendEvent
- 好友消息: FriendMessagePreSendEvent
@ -44,6 +45,9 @@
- 图片上传完成: ImageUploadEvent
- 图片上传成功: Succeed
- 图片上传失败: Failed
- (`1.1.0-`) ~~主动发送消息: MessageSendEvent~~
- ~~群消息: GroupMessageSendEvent~~
- ~~好友消息: FriendMessageSendEvent~~
### [](group.kt)
- 机器人被踢出群或在其他客户端主动退出一个群: BotLeaveEvent
@ -60,7 +64,7 @@
- 入群公告改变: GroupEntranceAnnouncementChangeEvent
- 全员禁言状态改变: GroupMuteAllEvent
- 匿名聊天状态改变: GroupAllowAnonymousChatEvent
- 坦白说状态改变: GroupAllowConfessTalkEvent
- (`1.3.0-`) ~~坦白说状态改变: GroupAllowConfessTalkEvent~~
- 允许群员邀请好友加群状态改变: GroupAllowMemberInviteEvent
#### 群成员

View File

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

View File

@ -440,7 +440,7 @@ public class FriendMessageEvent constructor(
public override val sender: Friend,
public override val message: MessageChain,
public override val time: Int
) : AbstractEvent(), MessageEvent, MessageEventExtensions<User, Contact>, BroadcastControllable, FriendEvent {
) : AbstractMessageEvent(), MessageEvent, MessageEventExtensions<User, Contact>, BroadcastControllable, FriendEvent {
init {
val source =
message[MessageSource] ?: throw IllegalArgumentException("Cannot find MessageSource from message")
@ -480,7 +480,7 @@ public class GroupMessageEvent(
public override val sender: Member,
public override val message: MessageChain,
public override val time: Int
) : AbstractEvent(), GroupAwareMessageEvent, MessageEvent, Event, GroupEvent {
) : AbstractMessageEvent(), GroupAwareMessageEvent, MessageEvent, Event, GroupEvent {
init {
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" }
@ -512,7 +512,7 @@ public class TempMessageEvent(
public override val sender: Member,
public override val message: MessageChain,
public override val time: Int
) : AbstractEvent(), GroupAwareMessageEvent, MessageEvent, BroadcastControllable {
) : AbstractMessageEvent(), GroupAwareMessageEvent, MessageEvent, BroadcastControllable {
init {
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" }
@ -538,6 +538,75 @@ public interface UserMessageEvent : MessageEvent {
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]) 发送消息
*/
@JvmBlockingBridge
public suspend fun reply(message: Message): MessageReceipt<TSubject> =
subject.sendMessage(message.asMessageChain()) as MessageReceipt<TSubject>
public suspend fun reply(message: Message): MessageReceipt<TSubject>
@JvmBlockingBridge
public suspend fun reply(plain: String): MessageReceipt<TSubject> =
subject.sendMessage(PlainText(plain).asMessageChain()) as MessageReceipt<TSubject>
public suspend fun reply(plain: String): MessageReceipt<TSubject>
// endregion
@JvmSynthetic
public suspend fun ExternalImage.upload(): Image = this.upload(subject)
public suspend fun ExternalImage.upload(): Image
@JvmSynthetic
public suspend fun ExternalImage.send(): MessageReceipt<TSubject> = this.sendTo(subject)
public suspend fun ExternalImage.send(): MessageReceipt<TSubject>
@JvmSynthetic
public suspend fun Image.send(): MessageReceipt<TSubject> = this.sendTo(subject)
public suspend fun Image.send(): MessageReceipt<TSubject>
@JvmSynthetic
public suspend fun Message.send(): MessageReceipt<TSubject> = this.sendTo(subject)
public suspend fun Message.send(): MessageReceipt<TSubject>
@JvmSynthetic
public suspend fun String.send(): MessageReceipt<TSubject> = PlainText(this).sendTo(subject)
public suspend fun String.send(): MessageReceipt<TSubject>
// region 引用回复
/**
@ -642,18 +709,16 @@ public interface MessageEventExtensions<out TSender : User, out TSubject : Conta
* 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
*/
@JvmBlockingBridge
public suspend fun quoteReply(message: MessageChain): MessageReceipt<TSubject> =
reply(this.message.quote() + message)
public suspend fun quoteReply(message: MessageChain): MessageReceipt<TSubject>
@JvmBlockingBridge
public suspend fun quoteReply(message: Message): MessageReceipt<TSubject> =
reply(this.message.quote() + message)
public suspend fun quoteReply(message: Message): MessageReceipt<TSubject>
@JvmBlockingBridge
public suspend fun quoteReply(plain: String): MessageReceipt<TSubject> = reply(this.message.quote() + plain)
public suspend fun quoteReply(plain: String): MessageReceipt<TSubject>
@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/..."
*/
@JvmSynthetic
public suspend fun Image.url(): String = this@url.queryUrl()
public suspend fun Image.url(): String
}
/** 一个消息事件在各平台的相关扩展. 请使用 [MessageEventExtensions] */
@ -679,46 +744,46 @@ public interface MessageEventPlatformExtensions<out TSender : User, out TSubject
// region 上传图片
@JvmBlockingBridge
public suspend fun uploadImage(image: BufferedImage): Image = subject.uploadImage(image)
public suspend fun uploadImage(image: BufferedImage): Image
@JvmBlockingBridge
public suspend fun uploadImage(image: InputStream): Image = subject.uploadImage(image)
public suspend fun uploadImage(image: InputStream): Image
@JvmBlockingBridge
public suspend fun uploadImage(image: File): Image = subject.uploadImage(image)
public suspend fun uploadImage(image: File): Image
// endregion
// region 发送图片
@JvmBlockingBridge
public suspend fun sendImage(image: BufferedImage): MessageReceipt<TSubject> = subject.sendImage(image)
public suspend fun sendImage(image: BufferedImage): MessageReceipt<TSubject>
@JvmBlockingBridge
public suspend fun sendImage(image: InputStream): MessageReceipt<TSubject> = subject.sendImage(image)
public suspend fun sendImage(image: InputStream): MessageReceipt<TSubject>
@JvmBlockingBridge
public suspend fun sendImage(image: File): MessageReceipt<TSubject> = subject.sendImage(image)
public suspend fun sendImage(image: File): MessageReceipt<TSubject>
// endregion
// region 上传图片 (扩展)
@JvmSynthetic
public suspend fun BufferedImage.upload(): Image = upload(subject)
public suspend fun BufferedImage.upload(): Image
@JvmSynthetic
public suspend fun InputStream.uploadAsImage(): Image = uploadAsImage(subject)
public suspend fun InputStream.uploadAsImage(): Image
@JvmSynthetic
public suspend fun File.uploadAsImage(): Image = uploadAsImage(subject)
public suspend fun File.uploadAsImage(): Image
// endregion 上传图片 (扩展)
// region 发送图片 (扩展)
@JvmSynthetic
public suspend fun BufferedImage.send(): MessageReceipt<TSubject> = sendTo(subject)
public suspend fun BufferedImage.send(): MessageReceipt<TSubject>
@JvmSynthetic
public suspend fun InputStream.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject)
public suspend fun InputStream.sendAsImage(): MessageReceipt<TSubject>
@JvmSynthetic
public suspend fun File.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject)
public suspend fun File.sendAsImage(): MessageReceipt<TSubject>
// endregion 发送图片 (扩展)
}

View File

@ -18,7 +18,7 @@ import net.mamoe.mirai.message.data.*
* 使用 `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 可能在任何时刻被改变
*

View File

@ -16,8 +16,10 @@ package net.mamoe.mirai.message.data
import kotlinx.serialization.Serializable
import net.mamoe.mirai.LowLevelApi
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.UserOrBot
import net.mamoe.mirai.contact.nameCardOrNick
import net.mamoe.mirai.message.code.CodableMessage
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 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 {
/**
* 构造一个 [At], 仅供内部使用, 否则可能造成消息无法发出的问题.

View File

@ -17,6 +17,7 @@ import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import net.mamoe.mirai.message.code.CodableMessage
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.safeCast
@ -56,7 +57,14 @@ public data class PokeMessage internal constructor(
public val id: Int
) : HummerMessage(), CodableMessage {
@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 :
AbstractPolymorphicMessageKey<HummerMessage, PokeMessage>(HummerMessage, { it.castOrNull() }) {
@ -206,7 +214,8 @@ public data class VipFace internal constructor(
}
@ExperimentalMessageKey
override val key: MessageKey<VipFace> get() = Key
override val key: MessageKey<VipFace>
get() = Key
@Suppress("DEPRECATION_ERROR", "DEPRECATION", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
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.quote
import net.mamoe.mirai.utils.LazyProperty
import net.mamoe.mirai.utils.PlannedRemoval
import net.mamoe.mirai.utils.safeCast
/**
@ -145,7 +146,8 @@ public sealed class MessageSource : Message, MessageMetadata, ConstrainSingle {
/**
* 返回 `"[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() }) {
/**
@ -397,6 +399,13 @@ public abstract class OfflineMessageSource : MessageSource() {
* 消息种类
*/
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

View File

@ -15,6 +15,7 @@ package net.mamoe.mirai.message.data
import kotlinx.serialization.Serializable
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.PlannedRemoval
import net.mamoe.mirai.utils.safeCast
import kotlin.annotation.AnnotationTarget.*
@ -150,6 +151,15 @@ public interface ServiceMessage : RichMessage {
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
@Serializable
public abstract class AbstractServiceMessage : ServiceMessage {

View File

@ -17,7 +17,7 @@ import kotlin.annotation.AnnotationTarget.*
* 这些 API 可能会在任意时刻更改, 且不会发布任何预警.
* 非常不建议在发行版本中使用这些 API.
*/
@Retention(AnnotationRetention.SOURCE)
@Retention(AnnotationRetention.BINARY)
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
@Target(
CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR,
@ -34,7 +34,7 @@ public annotation class MiraiInternalApi(
* 这些 API 不具有稳定性, 且可能会在任意时刻更改.
* 不建议在发行版本中使用这些 API.
*/
@Retention(AnnotationRetention.SOURCE)
@Retention(AnnotationRetention.BINARY)
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
@MustBeDocumented
@ -48,4 +48,24 @@ public annotation class MiraiExperimentalApi(
@Target(CLASS, PROPERTY, FIELD, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, TYPEALIAS)
@Retention(AnnotationRetention.SOURCE)
@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")
public enum class MiraiProtocol constructor(
/** 协议模块使用的 ID */
@JvmField internal val id: Long
) {
public enum class MiraiProtocol {
/**
* Android 手机.
*/
ANDROID_PHONE(537066439),
ANDROID_PHONE,
/**
* Android 平板.
*/
ANDROID_PAD(537062409),
ANDROID_PAD,
/**
* Android 手表.
* */
ANDROID_WATCH(537061176)
ANDROID_WATCH,
}
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.utils.BotConfiguration
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.cast
import net.mamoe.mirai.utils.currentTimeSeconds
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.math.absoluteValue
@ -733,7 +734,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
override val nick: String get() = fromNick
override val remark: String
get() = ""
}))
}).cast())
}
}

View File

@ -77,23 +77,23 @@ internal class GroupImpl(
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
// e.g. 600
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) {
botAsMember = newMember(it)
botAsMember = newMember(it).cast()
if (it.permission == MemberPermission.OWNER) {
owner = botAsMember
}
null
} else newMember(it).also { member ->
} else newMember(it).cast<NormalMember>().also { member ->
if (member.permission == MemberPermission.OWNER) {
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) {
return botAsMember
}
@ -298,7 +298,7 @@ internal class GroupImpl(
throw EventCancelledException("exception thrown when broadcasting GroupMessagePreSendEvent", it)
}.message.asMessageChain()
val length = chain.estimateLength(703) // 阈值为700左右限制到3的倍数
val length = chain.estimateLength(this, 703) // 阈值为700左右限制到3的倍数
var imageCnt = 0 // 通过下方逻辑短路延迟计算
if (length > 5000 || chain.count { it is Image }.apply { imageCnt = this } > 50) {

View File

@ -44,7 +44,7 @@ internal class MemberImpl constructor(
group: GroupImpl,
coroutineContext: CoroutineContext,
memberInfo: MemberInfo
) : Member {
) : NormalMember {
override val group: GroupImpl by group.unsafeWeakRef()
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.WrongPasswordException
import net.mamoe.mirai.utils.*
import java.util.concurrent.ConcurrentLinkedQueue
import kotlin.coroutines.CoroutineContext
@Suppress("MemberVisibilityCanBePrivate")
@ -220,8 +221,8 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
@JvmField
@Volatile
internal var pendingIncomingPackets: LockFreeLinkedList<KnownPacketFactories.IncomingPacket<*>>? =
LockFreeLinkedList()
internal var pendingIncomingPackets: ConcurrentLinkedQueue<KnownPacketFactories.IncomingPacket<*>>? =
ConcurrentLinkedQueue()
private var initFriendOk = false
private var initGroupOk = false
@ -322,7 +323,7 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
}
if (!pendingEnabled) {
pendingIncomingPackets = LockFreeLinkedList()
pendingIncomingPackets = ConcurrentLinkedQueue()
_pendingEnabled.value = true
}

View File

@ -76,9 +76,10 @@ internal open class QQAndroidClient(
val device: DeviceInfo,
bot: QQAndroidBot
) {
@Suppress("INVISIBLE_MEMBER")
val protocol = MiraiProtocolInternal[bot.configuration.protocol]
val subAppId: Long
get() = bot.configuration.protocol.id
get() = protocol.id
internal val serverList: MutableList<Pair<String, Int>> = DefaultServerList.toMutableList()
@ -118,9 +119,10 @@ internal open class QQAndroidClient(
var tgtgtKey: ByteArray = generateTgtgtKey(device.guid)
val randomKey: ByteArray = getRandomByteArray(16)
var miscBitMap: Int = 184024956 // 也可能是 150470524 ?
private var mainSigMap: Int = 16724722
var subSigMap: Int = 0x10400 //=66,560
val miscBitMap: Int get() = protocol.miscBitMap // 184024956 // 也可能是 150470524 ?
private val mainSigMap: Int = protocol.mainSigMap
var subSigMap: Int = protocol.subSigMap // 0x10400 //=66,560
private val _ssoSequenceId: AtomicInt = atomic(85600)
@ -157,9 +159,12 @@ internal open class QQAndroidClient(
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 buildTime: Long get() = protocol.buildTime
val sdkVersion: String get() = protocol.sdkVer
private val messageSequenceId: AtomicInt = atomic(22911)
internal fun atomicNextMessageSequenceId(): Int = messageSequenceId.getAndAdd(2)
@ -194,7 +199,7 @@ internal open class QQAndroidClient(
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

View File

@ -184,60 +184,59 @@ internal object KnownPacketFactories {
bot: QQAndroidBot,
rawInput: ByteReadPacket,
consumer: PacketConsumer<T>
) =
with(rawInput) {
// login
val flag1 = readInt()
): Unit = with(rawInput) {
// login
val flag1 = readInt()
PacketLogger.verbose { "开始处理一个包" }
PacketLogger.verbose { "开始处理一个包" }
val flag2 = readByte().toInt()
val flag3 = readByte().toInt()
check(flag3 == 0) {
"Illegal flag3. Expected 0, whereas got $flag3. flag1=$flag1, flag2=$flag2. " +
"Remaining=${this.readBytes().toUHexString()}"
}
val flag2 = readByte().toInt()
val flag3 = readByte().toInt()
check(flag3 == 0) {
"Illegal flag3. Expected 0, whereas got $flag3. flag1=$flag1, flag2=$flag2. " +
"Remaining=${this.readBytes().toUHexString()}"
}
readString(readInt() - 4)// uinAccount
readString(readInt() - 4)// uinAccount
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
ByteArrayPool.useInstance(this.remaining.toInt()) { data ->
val size = this.readAvailable(data)
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
ByteArrayPool.useInstance(this.remaining.toInt()) { data ->
val size = this.readAvailable(data)
kotlin.runCatching {
when (flag2) {
2 -> TEA.decrypt(data, DECRYPTER_16_ZERO, size)
1 -> TEA.decrypt(data, bot.client.wLoginSigInfo.d2Key, size)
0 -> data
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
kotlin.runCatching {
when (flag2) {
2 -> TEA.decrypt(data, DECRYPTER_16_ZERO, size)
1 -> TEA.decrypt(data, bot.client.wLoginSigInfo.d2Key, size)
0 -> data
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?.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(
it: IncomingPacket<T>,

View File

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

View File

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

View File

@ -128,7 +128,7 @@ internal class WtLogin {
writeSsoPacket(client, client.subAppId, commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
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())
t18(appId, client.appClientVersion, client.uin)
@ -161,7 +161,8 @@ internal class WtLogin {
*/
t116(client.miscBitMap, client.subSigMap)
t100(appId, client.subAppId, client.appClientVersion, client.ssoVersion)
t107(6)
t107(0)
t108(client.device.imei.toByteArray())
// t108(byteArrayOf())
// ignored: t104()
@ -192,9 +193,11 @@ internal class WtLogin {
t145(client.device.guid)
t147(appId, client.apkVersionName, client.apkSignatureMd5)
/*
if (client.miscBitMap and 0x80 != 0) {
t166(1)
}
*/
// ignored t16a because array5 is null
@ -210,14 +213,14 @@ internal class WtLogin {
"connect.qq.com",
"qzone.qq.com",
"vip.qq.com",
"gamecenter.qq.com",
"qun.qq.com",
"game.qq.com",
"qqweb.qq.com",
"office.qq.com",
"ti.qq.com",
"mail.qq.com",
"qzone.com",
"mma.qq.com"
"mma.qq.com",
)
)
@ -243,7 +246,10 @@ internal class WtLogin {
t202(bssid, ssid)
}
t177()
t177(
buildTime = client.buildTime,
buildVersion = client.sdkVersion,
)
t516()
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
import net.mamoe.mirai.contact.ContactOrBot
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.data.AtAll.display
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
import net.mamoe.mirai.utils.safeCast
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 ->
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) {
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 PlainText -> content.chineseLength(upTo)
is At -> display.chineseLength(upTo)
is At -> this.getDisplay(target.safeCast()).chineseLength(upTo)
is AtAll -> display.chineseLength(upTo)
else -> this.toString().chineseLength(upTo)
}