Compare commits

...

25 Commits
2.0.0 ... 2.0.3

Author SHA1 Message Date
crazywhalecc
7434bac94e update to 2.0.3 version 2020-12-31 16:26:51 +08:00
crazywhalecc
ac45ab0dec update to 2.0.2 version 2020-12-31 13:58:59 +08:00
crazywhalecc
8d248f301e update to 2.0.2 version 2020-12-31 13:56:59 +08:00
jerry
937d31ccd9 update docs 2020-12-31 01:44:09 +08:00
crazywhalecc
ef263a5d1e update docs 2020-12-31 00:35:40 +08:00
jerry
bf03dfda38 Merge remote-tracking branch 'origin/master' 2020-12-31 00:28:36 +08:00
jerry
6ba209c4c7 update docs 2020-12-31 00:28:16 +08:00
Whale
e7d86537be Update README.md 2020-12-26 21:04:48 +08:00
crazywhalecc
44979c670f update docs 2020-12-25 16:53:44 +08:00
Whale
747ecf28ea Merge pull request #23 from 854854321/master
[MOD] add global function -> getAllFdByConnectType  根据连接类型获取所有fd.
2020-12-25 16:41:48 +08:00
Whale
f5d5929cb9 Update global_functions.php 2020-12-25 16:41:14 +08:00
Whale
6e866001d6 Update global_functions.php 2020-12-25 16:40:02 +08:00
wenhao
775c275288 [MOD] 解决php Notice <PHP Notice: Undefined index: x-self-id> 2020-12-25 16:28:21 +08:00
wenhao
86a0e1a2ca [MOD] add global function -> getAllFdByConnectType 根据连接类型获取所有fd. 2020-12-25 15:48:41 +08:00
Whale
aae79aacb5 Update README.md 2020-12-25 01:42:23 +08:00
Whale
991af3d728 Update README.md 2020-12-25 01:41:23 +08:00
Whale
25ae4e02c2 Update README.md 2020-12-25 01:40:45 +08:00
Whale
5a6642e217 Update README.md 2020-12-25 01:40:24 +08:00
Whale
d00f6ee3a2 Update README.md 2020-12-25 01:27:19 +08:00
crazywhalecc
1e6cc9cd84 update docs 2020-12-23 16:20:35 +08:00
crazywhalecc
b7b94db5ea Merge remote-tracking branch 'origin/master' into master 2020-12-23 16:15:18 +08:00
crazywhalecc
b0054d7884 update docs 2020-12-23 16:14:59 +08:00
Whale
22942c33cf Update README.md 2020-12-23 11:27:23 +08:00
crazywhalecc
1f9c5eeeb4 add CNAME 2020-12-23 11:23:57 +08:00
crazywhalecc
44337d2ad4 update to 2.0.1 version
fix swoole log file "no such file or directory" error
2020-12-23 11:16:24 +08:00
25 changed files with 2499 additions and 99 deletions

View File

@@ -1,7 +1,7 @@
<div align="center">
<img src="/resources/images/logo_trans.png" height = "150" alt="炸毛框架"><br>
<h2>炸毛框架</h2>
炸毛框架 (zhamao-frameowork) 是一个协程高性能的聊天机器人 + Web 服务器开发框架<br><br>
炸毛框架 (zhamao-framework) 是一个协程高性能的聊天机器人 + Web 服务器开发框架<br><br>
[![作者QQ](https://img.shields.io/badge/作者QQ-627577391-orange.svg)]()
[![zhamao License](https://img.shields.io/hexpm/l/plug.svg?maxAge=2592000)](https://github.com/zhamao-robot/zhamao-framework/blob/master/LICENSE)
@@ -20,6 +20,8 @@
**2.0 版本如果有问题请第一时间加群反馈!**
有关 3.0 版本的最新情况,请看这里:[Issue #22](https://github.com/zhamao-robot/zhamao-framework/issues/22)
## 简介
炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务聊天机器人OneBot 兼容的 QQ 机器人对接),包含 Websocket、HTTP 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。
@@ -46,7 +48,7 @@ public function index() {
## 文档v2 版本)
查看文档:[https://docs-v2.zhamao.xin/](https://docs-v2.zhamao.xin/)
备用链接:[http://docs-v2.zhamao.me/](http://docs-v2.zhamao.me/)
备用链接:[https://docs-v2.zhamao.me/](https://docs-v2.zhamao.me/)
自行构建文档:`mkdocs build -d distribute`
@@ -90,4 +92,4 @@ public function index() {
**注意**:在你使用 mirai 等 `AGPL-3.0` 协议的机器人软件与框架连接时,使用本框架需要将你编写或修改的部分使用 `AGPL-3.0` 协议重新分发。
![star](https://starchart.cc/zhamao-robot/zhamao-framework.svg)
![star](https://starchart.cc/zhamao-robot/zhamao-framework.svg)

View File

@@ -3,8 +3,8 @@
"description": "High performance QQ robot and web server development framework",
"minimum-stability": "stable",
"license": "Apache-2.0",
"version": "2.0.0",
"extra": {},
"version": "2.0.3",
"extra": [],
"authors": [
{
"name": "whale",
@@ -47,16 +47,7 @@
"src/ZM/global_functions.php"
]
},
"autoload-dev": {
"psr-4": {
"ZMTest\\": "test/ZMTest"
},
"files": [
"test/ZMTest/Mock/mock.php"
]
},
"require-dev": {
"phpunit/phpunit": "^9.3",
"swoole/ide-helper": "@dev"
}
}
}

1
docs/CNAME Normal file
View File

@@ -0,0 +1 @@
docs-v2.zhamao.me

View File

@@ -0,0 +1,29 @@
# 内部类文件手册
这个章节写明了在框架使用过程中可能涉及到的框架内部或 Swoole、其他 composer 依赖组件的内部类,这里会根据类的命名空间一一说明。
## Swoole\Http\Request
此类是 Swoole 内部的一个类,一般在收到 HTTP 请求时,在 `@RequestMapping``@OnSwooleEvent("request")` 两个注解下可用,用作获取 GET、POST参数上传到后端的文件、Cookies 等。详见 [Swoole 文档 - Request](http://wiki.swoole.com/#/http_server?id=httprequest) 。
### 属性
- `$fd`:获取当前连接的文件描述符 ID。
- `$header``HTTP` 请求的头部信息。类型为数组,所有 `key` 均为小写。
- `$server``HTTP` 请求相关的服务器信息。
- `$cookie`:获取 Cookies。
- `$get`:获取 GET 参数。
- `$post`:获取 POST 参数。
- `$files`:获取上传的文件信息
### 方法
- `rawContent()`:获取 POST 包原始二进制内容,相当于原生 PHP 的 ` file_get_contents("php://input");`
- `getData()`:获取完整的原始 `Http` 请求报文。包括 `Http Header``Http Body`
### 示例
```php
TODO先放一放。
```

View File

@@ -29,6 +29,17 @@
justify-content: flex-end;
}
.doc-chat-banner {
justify-content: center;
background: rgba(0,0,0,0.1);
width: max-content;
margin: 8px auto;
padding: 4px 14px;
border-radius: 8px;
color: gray;
font-size: 14px;
}
.doc-chat-row-robot {
justify-content: flex-start !important;
}
@@ -85,4 +96,8 @@
width: 36px !important;
height: 36px !important;
border-radius: 18px;
}
.md-typeset .admonition, .md-typeset details {
font-size: .72rem;
}

424
docs/component/CQ码.md Normal file
View File

@@ -0,0 +1,424 @@
# CQ 码(多媒体消息)
消息中的多媒体内容使用 CQ 码来表示,形如 `[CQ:face,id=178]`。其中,`[CQ:]` 是固定格式;`face` 是「功能名」,除了 `face` 还有许多不同的功能名;`id=178` 是「参数」,某些功能不需要参数,而另一些需要多个参数,当有多个参数时,参数间使用逗号分隔。
## 格式
一些 CQ 码的例子如下:
```
[CQ:shake]
[CQ:face,id=178]
[CQ:share,title=标题,url=http://baidu.com]
```
更多 CQ 码功能请参考 [消息段类型](https://github.com/howmanybots/onebot/blob/master/v11/specs/message/segment.md)。
!!! warning "注意"
CQ 码中不应有多余的空格,例如不应该使用 `[CQ:face, id=178]`
CQ 码的参数值可以包含空格、换行、除 `[],&` 之外的特殊符号等。在解析时,应直接取 `[CQ:` 后、第一个 `,``]` 前的部分为功能名,第一个 `,` 之后到 `]` 之间的部分为参数,按 `,` 分割后,每个部分第一个 `=` 前的内容为参数名,之后的部分为参数值。例如 `[CQ:share,title=标题中有=等号,url=http://baidu.com]` 中,功能名为 `share``title` 参数值为 `标题中有=等号``url` 参数值为 `http://baidu.com`
## 转义
CQ 码中包含一些特殊字符:`[``]``,` 等,而 CQ 码又是可能混杂在纯文本内容之中的,因此消息中的纯文本内容需要对特殊字符进行转义,以避免歧义。具体的转义规则如下:
| 转义前 | 转义后 |
| ------ | ------- |
| `&` | `&amp;` |
| `[` | `&#91;` |
| `]` | `&#93;` |
另一方面CQ 码内部的参数值也可能出现特殊字符,也是需要转义的。由于 `,`(半角逗号)在 CQ 码中用于分隔参数,因此除了上面的转义规则,还需要对 `,` 进行转义,如下:
| 转义前 | 转义后 |
| ------ | ------- |
| `&` | `&amp;` |
| `[` | `&#91;` |
| `]` | `&#93;` |
| `,` | `&#44;` |
例如,一个链接分享消息的 CQ 码可能如下:
```
[CQ:share,title=震惊&#44;小伙睡觉前居然...,url=http://baidu.com/?a=1&amp;b=2]
```
## 封装调用
框架提供了 CQ 码的封装,你可以在任何位置使用封装好的 CQ 码生成器。
生成器是一个静态类,里面的方法全部是静态调用,命名空间是:`ZM\API\CQ`
例如,给用户发送图片这样写就好啦!只需要将添加图片的地方拼到回复用户的字符串里。如果只发图片,整个字符串里只能有 CQ 码。
```php
<?php
namespace Module\Example;
use ZM\API\CQ;
use ZM\Annotation\CQ\CQCommand;
class Hello {
/**
* @CQCommand("发送图片")
*/
public function msgRecv() {
return CQ::image("https://zhamao.xin/file/hello.jpg");
// 相当于返回:"[CQ:image,file=https://zhamao.xin/file/hello.jpg]"
}
}
```
效果
<chat-box>
) 发送图片
[ https://zhamao.xin/file/hello.jpg
</chat-box>
## CQ 码列表
### CQ::face() - 发送 QQ 表情
发送 QQ 原生表情。
定义:`CQ::face($id)`
参数:`$id` 为 QQ 表情对应的 ID 号,一些常见的表情 ID 对应的表情样式见 [炸毛框架 1.x 版本文档](https://docs-v1.zhamao.xin/face_list.html)。
```php
/**
* @CQCommand("打盹")
*/
public function faceTest() {
ctx()->reply("正在打盹...");
ctx()->reply(CQ::face(8));
}
```
<chat-box>
) 打盹
( 正在打盹...
[ https://docs-v1.zhamao.xin/face/8.gif
</chat-box>
!!! note "提示"
对于不断更新的 QQ 版本下,可能会持续扩充新的 QQ 表情,如果上表没有新的表情的话,也可以使用消息接收的方式,让机器人收到表情后解析出来对应的 id 然后再发送。
### CQ::image() - 发送图片
发送图片。
定义:`image($file, $cache = true, $flash = false, $proxy = true, $timeout = -1)`
参数
| 参数名 | 收 | 发 | 默认值 | 说明 |
| --------- | ---- | ---- | ------- | ------------------------------------------------------------ |
| `file` | ✓ | ✓ | 必填 | 图片文件名 |
| `flash` | ✓ | ✓ | `false` | 图片类型,当参数为 true 时代表发送闪照 |
| `cache` | | ✓ | `true` | 只在通过网络 URL 发送时有效,表示是否使用已缓存的文件,默认 `true` |
| `proxy` | | ✓ | `true` | 只在通过网络 URL 发送时有效,表示是否通过代理下载文件(需通过环境变量或配置文件配置代理),默认 `true` |
| `timeout` | | ✓ | `-1` | 只在通过网络 URL 发送时有效,单位秒,表示下载网络文件的超时时间,默认 -1 不超时 |
发送时,`file` 参数除了支持使用收到的图片文件名直接发送外,还支持:
- 绝对路径,例如 `file:///root/imagetest/1.png`,格式使用 [`file` URI](https://tools.ietf.org/html/rfc8089)
- 网络 URL例如 `http://i1.piimg.com/567571/fdd6e7b6d93f1ef0.jpg`
- Base64 编码,例如 `base64://iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAIAAADJt1n/AAAAKElEQVQ4EWPk5+RmIBcwkasRpG9UM4mhNxpgowFGMARGEwnBIEJVAAAdBgBNAZf+QAAAAABJRU5ErkJggg==`
### CQ::record() - 发送语音
发送语音消息。
定义:`CQ::record($file, $magic = false, $cache = true, $proxy = true, $timeout = -1)`
参数
| 参数名 | 收 | 发 | 默认值 | 说明 |
| --------- | ---- | ---- | ------- | ------------------------------------------------------------ |
| `file` | ✓ | ✓ | 必填 | 音频文件名 |
| `flash` | ✓ | ✓ | `false` | 图片类型,当参数为 true 时代表发送闪照 |
| `cache` | | ✓ | `true` | 只在通过网络 URL 发送时有效,表示是否使用已缓存的文件,默认 `true` |
| `proxy` | | ✓ | `true` | 只在通过网络 URL 发送时有效,表示是否通过代理下载文件(需通过环境变量或配置文件配置代理),默认 `true` |
| `timeout` | | ✓ | `-1` | 只在通过网络 URL 发送时有效,单位秒,表示下载网络文件的超时时间,默认 -1 不超时 |
发送时,`file` 参数除了支持使用收到的语音文件名直接发送外,还支持其它形式,参考上方发送图片。
```php
/**
* @CQCommand("说你好")
*/
public function say() {
ctx()->reply(CQ::record("https://zhamao.xin/file/hello.mp3"));
}
```
<chat-box>
) 说你好
( [语音消息,点击收听] 2'' )))
</chat-box>
> 此 CQ 码只能用于单独一条文本消息中,如果混有其他字符串,则会吞掉其他字符串内容。
### CQ::at() - 群里@某人或全体
在群里 at 某个人或全体成员(全体成员需要有管理员权限)。
定义:`CQ::at($qq)`
参数:`$qq` 参数必填,如果填的是 QQ 号,则是单独 at 某人,如果是 `all`,则是 at 全体成员。
```php
/**
* @CQCommand("at测试")
*/
public function atTest() {
ctx()->reply(CQ::at(627577391)." 你好啊!");
}
```
<chat-box>
) at测试
( @鲸鱼 你好啊!
</chat-box>
### CQ::video() - 发送短视频
发送短视频。
定义:`CQ::video($file, $cache = true, $proxy = true, $timeout = -1)`
参数
| 参数名 | 收 | 发 | 默认值 | 说明 |
| --------- | ---- | ---- | ------ | ------------------------------------------------------------ |
| `file` | ✓ | ✓ | 必填 | 短视频文件名 |
| `cache` | | ✓ | `true` | 只在通过网络 URL 发送时有效,表示是否使用已缓存的文件,默认 `true` |
| `proxy` | | ✓ | `true` | 只在通过网络 URL 发送时有效,表示是否通过代理下载文件(需通过环境变量或配置文件配置代理),默认 `true` |
| `timeout` | | ✓ | `-1` | 只在通过网络 URL 发送时有效,单位秒,表示下载网络文件的超时时间,默认 -1 不超时 |
发送时,`file` 参数除了支持使用收到的视频文件名直接发送外,还支持其它形式,参考上方发送图片。
> 此 CQ 码只能用于单独一条文本消息中,如果混有其他字符串,则会吞掉其他字符串内容。
### CQ::rps() - 猜拳
定义:`CQ::rps()`
用法:`CQ::rps()`
> 此 CQ 码只能用于单独一条文本消息中,如果混有其他字符串,则会吞掉其他字符串内容。
### CQ::dice() - 掷骰子
定义:`CQ::dice()`
用法:`CQ::dice()`
> 此 CQ 码只能用于单独一条文本消息中,如果混有其他字符串,则会吞掉其他字符串内容。
### CQ::shake() - 窗口抖动
定义:`CQ::shake()`
用法:`CQ::shake()`
> 此 CQ 码只能用于单独一条文本消息中,如果混有其他字符串,则会吞掉其他字符串内容。
### CQ::poke() - 戳一戳
发送戳一戳。
定义:`CQ::poke($type, $id, $name = "")`
参数
| 参数名 | 收 | 发 | 可能的值 | 说明 |
| ------ | ---- | ---- | ------------------------------------------------------------ | ------ |
| `type` | ✓ | ✓ | 见 [Mirai 的 PokeMessage 类](https://github.com/mamoe/mirai/blob/f5eefae7ecee84d18a66afce3f89b89fe1584b78/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/HummerMessage.kt#L49) | 类型 |
| `id` | ✓ | ✓ | 同上 | ID |
| `name` | ✓ | | 同上 | 表情名 |
例子:`CQ::poke(6,-1)`
效果:放大招
> 此 CQ 码只能用于单独一条文本消息中,如果混有其他字符串,则会吞掉其他字符串内容。
### CQ::anonymous() - 匿名发消息
匿名发消息。需要在允许匿名发消息的群里发。
!!! tip "提示"
当收到匿名消息时,需要通过 [消息事件的群消息](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/message.md#群消息) 的 `anonymous` 字段判断。
定义:`CQ::anonymous($ignore = 1)`
```php
/**
* @CQCommand("匿名测试")
*/
public function anonymousTest() {
ctx()->reply(CQ::anonymous()."匿名测试");
}
```
### CQ::share() - 链接分享
发送链接分享卡片,可自定义内容。
定义:`CQ::share($url, $title, $content = null, $image = null)`
参数
| 参数名 | 收 | 发 | 可能的值 | 说明 |
| --------- | ---- | ---- | -------- | -------------- |
| `url` | ✓ | ✓ | - | URL |
| `title` | ✓ | ✓ | - | 标题 |
| `content` | ✓ | ✓ | - | 可选,内容描述 |
| `image` | ✓ | ✓ | - | 可选,图片 URL |
```php
/**
* @CQCommand("链接分享测试")
*/
public function shareTest() {
ctx()->reply(CQ::share("https://baidu.com", "UC忽悠部", "震惊!我市一男子在光天化日之下..."));
}
```
### CQ::contact() - 推荐好友
发送推荐好友的卡片。
定义:`CQ::contact($type, $id)`
参数
| 参数名 | 收 | 发 | 可能的值 | 说明 |
| ------ | ---- | ---- | ------------- | ---------------------- |
| `type` | ✓ | ✓ | `qq``group` | 推荐好友或群 |
| `id` | ✓ | ✓ | - | 被推荐人的 QQ 号或群号 |
```php
/**
* @CQCommand("我的名片")
*/
public function myCard() {
ctx()->reply(CQ::contact("qq", ctx()->getUserId()));
}
```
### CQ::location() - 发送位置
发送位置,基于经纬度坐标发的。
定义:`CQ::location($lat, $lon, $title = "", $content = "")`
参数
| 参数名 | 收 | 发 | 可能的值 | 说明 |
| --------- | ---- | ---- | -------- | -------------- |
| `lat` | ✓ | ✓ | - | 纬度 |
| `lon` | ✓ | ✓ | - | 经度 |
| `title` | ✓ | ✓ | - | 可选,标题 |
| `content` | ✓ | ✓ | - | 可选,内容描述 |
### CQ::music() - 音乐分享
分享音乐,通过卡片。
发送音乐分享卡片。此 CQ 码如果伴随着其他文字,则文字内容会被丢弃。
定义:`CQ::music($type, $id_or_url, $audio = null, $title = null, $content = null, $image = null)`
- `$type`: 发送类型
- `$id_or_url`: 音乐的 id 或 音乐卡片点进去打开的链接
- `$audio`: 音频文件的 HTTP 地址
- `$title`: 音乐卡片的标题,建议 12 字以内
- `$content`: 音乐卡片的简介内容(可选)
- `$image`: 音乐卡片的图片的链接地址(可选)
如果 `$type` 参数为 `qq``163``xiami`,则必须且只和第二个参数 `$id_or_url` 配合使用。这三个为内置分享,需要先通过搜索功能获取对应平台歌曲的 id 后使用。
如果 `$type` 参数为 `custom`,则表明此音乐卡片为用户自定义,你可以根据自己的需要自定义卡片内容和音频。此时必须填写 `$id_or_url`, `$audio`, `$title` 三个参数。
```php
ctx()->reply(CQ::music("163", "730806")); //一首我喜欢的歌
// 以内置的发送类型发送音乐卡片,我这里挑了网易云音乐的一首歌。
ctx()->reply("custom", "https://baidu.com/", "https://zhamao.xin/file/hello.mp3", "我是Siri说出来的Hello", "不服来打我呀!", "https://zhamao.xin/file/hello.jpg");
// 自定义整个卡片的每个内容
```
### CQ::forward() - 合并转发
合并转发消息。
定义:`CQ::forward($id)`
参数
```
[CQ:forward,id=123456]
```
| 参数名 | 收 | 发 | 可能的值 | 说明 |
| ------ | ---- | ---- | -------- | ------------------------------------------------------------ |
| `id` | ✓ | | 必填 | 合并转发 ID需通过 [`get_forward_msg` API](https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_forward_msg-获取合并转发消息) 获取具体内容 |
### CQ::node() - 合并转发自定义节点
接收时,此消息段不会直接出现在消息事件的 `message` 中,需通过 [`get_forward_msg` API](https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_forward_msg-获取合并转发消息) 获取。发送时,通过获取回来的 API 节点信息进行发送。
定义:`CQ::node($user_id, $nickname, $content)`
参数
| 参数名 | 收 | 发 | 可能的值 | 说明 |
| ---------- | ---- | ---- | -------- | ------------------------------------------------------------ |
| `user_id` | ✓ | ✓ | - | 发送者 QQ 号 |
| `nickname` | ✓ | ✓ | - | 发送者昵称 |
| `content` | ✓ | ✓ | - | 消息内容,支持发送消息时的 `message` 数据类型,见 [API 的参数](https://github.com/howmanybots/onebot/blob/master/v11/specs/api/#参数) |
```php
/**
* @CQCommand("node测试")
*/
public function nodeTest() {
ctx()->reply(CQ::node(123456, "Jack", "[CQ:face,id=123]哈喽~"));
}
```
### CQ::xml() - XML 消息
发送 QQ 兼容的 XML 多媒体消息。
定义:`CQ::xml($data)`
参数:`$data` 为 xml 字符串
```php
/**
* @CQCommand("xml测试")
*/
public function xmlTest() {
ctx()->reply(CQ::xml("<?xml ..."));
}
```
### CQ::json() - JSON 消息
发送 QQ 兼容的 JSON 多媒体消息。
定义:`CQ::json($data)`
参数同上,内含 JSON 字符串即可。
!!! tip "提示"
因为某些众所周知的原因XML 和 JSON 的返回不提供实例,有兴趣的可以自行研究如何编写,文档不含任何相关教程。

View File

@@ -1,3 +1,3 @@
# 框架组件
还没写到这里,不着急
这里列举了框架内的你可能会用到的常用组件。

299
docs/component/上下文.md Normal file
View File

@@ -0,0 +1,299 @@
# 上下文
上下文作为整个框架中最重要的内容之一,请务必理解和完整地阅读此部分!
一个上下文描述了一个事件和所关联的对象的环境。例如:你在处理 HTTP 请求的 `@RequestMapping` 绑定的事件中,你需要获取请求的 HTTP 头和 Cookie再比如你在处理 QQ 机器人发来的命令 `@CQCommand("随机数")` 的时候,在这个方法内,你需要获取发来的人的 QQ 号码。以上我们将处理以上运行环境的对象叫做上下文。
由于 Swoole 的协程加持,我们利用了协程 ID 绑定对象来进行构造上下文。
以默认的机器人收发消息为例,通过对默认模块的了解,我们可以知道,在绑定 `@CQCommand` 等类似事件后,你可以用上下文获取发来这条消息的人的 QQ 号码:
```php
/**
* @CQCommand("你好")
*/
public function hello() {
$user_id = ctx()->getUserId();
ctx()->reply("你好啊,".$user_id.",很高兴认识你!");
}
```
`context()` 就是获取上下文对象的全局函数,它还有简写:`ctx()`
当然,上下文中的方法不是每个都能在任何时候使用的。例如 `getUserId()` 你不能在 `@RequestMapping` 注解的函数中使用,因为它不是机器人消息的上下文。下面说明上下文对象的方法中,每个都会说明每个方法可以在哪些事件中使用:
## getServer() - 获取 Server 对象
获取 Swoole WebSocker Server 对象。此对象是 Swoole 的对象,详情见 [Swoole 文档](https://wiki.swoole.com/#/websocket_server)。
可以使用的事件:`@OnSwooleEvent("message")``@OnSwooleEvent("open")``@OnSwooleEvent("close")``@OnStart()` 以及所有 HTTP API 发来的事件:`@CQCommand()``@CQMessage()` 等。
## getFrame() - 获取 WS 数据帧
获取 `\Swoole\Websocket\Frame` 对象,此对象是 Swoole 的对象,详情见 [Swoole 文档](https://wiki.swoole.com/#/websocket_server?id=swoolewebsocketframe)。
可以使用的事件:`@OnSwooleEvent("message")` 以及所有 HTTP API 发来的事件:`@CQCommand()``@CQMessage()` 等,
## getFd() - 返回 fd 值
获取当前连入 Swoole 服务器的连接文件描述符 ID。返回 int。一般代表连接号可用来绑定对应链接。
可以使用的事件:所有 **getFrame()** 可以使用的,`@OnSwooleEvent("open")``@OnSwooleEvent("close")`
!!! tip "提示"
值得注意的是,由于机器人客户端和炸毛框架的连接是通过 WebSocket 进行的,而 WebSocket 是长连接,所以同一个机器人一次连接下收发消息所用的连接是同一个,所以 Fd 也是相同的。同理,炸毛框架的内部来区分多个机器人也是通过这一 Fd 进行判定的。
=== "代码"
```php
/**
* @CQCommand("测试fd")
*/
public function testfd() {
ctx()->reply("当前机器人连接的fd是".ctx()->getFd()"机器人QQ是".ctx()->getRobotId());
}
```
=== "效果"
<chat-box>
^ 假设我们和连接55555的机器人的私聊
) 测试fd
( 当前机器人连接的fd是1机器人QQ是55555
^ 假设切到了另一个机器人66666的私聊
) 测试fd
( 当前机器人连接的fd是2机器人QQ是66666
</chat-box>
## getData() - 获取事件完整数据
返回 CQHTTP 事件上报的原始数据包,已经被解析成数组,可以直接操作。
可以使用的事件:所有 HTTP API 发来的事件:`@CQCommand()``@CQMessage()` 等。
```php
/**
* @CQMessage(user_id=123456)
*/
public function onMessage() {
$data = ctx()->getData();
ctx()->reply("消息类型是:" . $data["message_type"]);
}
```
<chat-box>
^ 假设我是QQ为123456的用户私聊发消息
) 哈咯!!
( 消息类型是private
</chat-box>
## getRequest() - HTTP 请求对象
返回 `\Swoole\Http\Request` 对象,可在 `@RequestMapping` 中使用,获取 Cookie请求头GET 参数什么的。[Swoole 文档](https://wiki.swoole.com/#/http_server?id=httprequest)。
可以使用的事件:`@RequestMapping()``@OnSwooleEvent("request")``@OnSwooleEvent("open")`。
## getResponse() - HTTP 响应对象
返回 `\Swoole\Http\Response` 对象的增强版,可在 HTTP 请求相关的事件中使用,返回内容和设置 Cookie 什么的。[Swoole 文档](https://wiki.swoole.com/#/http_server?id=httpresponse)。
可以使用的事件:`@RequestMapping()``@OnSwooleEvent("request")`。
下面是使用以上两个功能的组合示例:
```php
/**
* @RequestMapping("/ping")
*/
public function ping() {
$name = ctx()->getRequest()->get["name"] ?? "unknown";
ctx()->getResponse()->end("Hello ".$name."!");
}
```
## getConnection() - WS 连接对象
返回此上下文相关联的 WebSocket 连接对象。
可以使用的事件:所有 **getFrame()** 可以使用的都可以使用。
## getCid() - 上下文 ID
返回当前上下文所绑定的协程 ID此 ID 和 `\Co::getCid()` 返回值一样。
## getRobot() - 获取机器人 API 对象
返回当前上下文关联的机器人 API 调用对象 [ZMRobot](机器人API.md)。
可以使用的事件:所有 HTTP API 发来的事件:`@CQCommand()``@CQMessage()` 等。
```php
ctx()->getRobot()->sendPrivateMsg(123456, "发送私聊消息");
```
<chat-box>
^ 正在和机器人聊天
( 发送私聊消息
</chat-box>
## getMessage() - 获取消息
获取 data 数据中的 `message` 消息,用于快速获取用户消息事件的消息内容。
可以使用的事件:`@CQCommand()``@CQMessage``@CQBefore("message")``@CQAfter("message")`
=== "代码"
```php
/**
* @CQMessage(group_id=33333)
*/
public function groupRepeat() {
ctx()->reply(ctx()->getMessage());
}
```
=== "效果"
<chat-box>
^ 现在在群33333内机器人已经成了复读机
) 来世还做复读机!!!
( 来世还做复读机!!!
) 你不许复读!
( 你不许复读!
</chat-box>
## getUserId() - 获取用户 QQ 号
获取发消息的用户的 QQ 号码。
可以使用的事件:所有 **含有** `user_id` 上报参数的 OneBot 事件。
```php
/**
* @CQCommand("whoami")
*/
public function whoami() {
ctx()->reply("你是".ctx()->getUserId()); //返回你是123456
}
```
## getGroupId() - 获取 QQ 群号
获取发消息来自的 QQ 群号。
可以使用的事件:所有含有 `group_id` 上报参数的 OneBot 事件。
## getMessageType() - 消息类型
获取消息类型,同参数 `message_type`。
可以使用的事件:所有 `post_type` 为 `message` 的响应事件,如 `@CQMessage``@CQCommand`。
## getRobotId() - 机器人 QQ 号
获取事件上报的机器人自己的 QQ 号码。
可以使用的事件:所有 OneBot 发来的事件:`@CQCommand()``@CQNotice()` 等。
## setMessage() - 设置消息
与 `getMessage()` 对应,用于更改上下文中保存的事件信息,可以用于消息变更和过滤。
## setUserId() - 设置用户 ID
与上同理,更改 `user_id`。
## setGroupId() - 设置群号
与上同理。
## setMessageType() - 设置类型
与上同理,修改消息类型。
## setData() - 设置数据包
与上同理,与 `getData()` 对应,用于更改上下文中的 `data`。
## getCache() - 上下文缓存
获取保存在上下文中的临时缓存变量。当相关联的事件结束后,数据会从内存中被释放。用于同一事件的多个函数中的信息传递。
- 参数:`$key`,缓存变量的键名
- 返回:`mixed`,存入缓存的变量值。
```php
$a = ctx()->getCache("block_continue");
// 如果变量不存在,则返回 null
```
## setCache() - 上下文缓存
与 `getCache()` 对应,是设置内容的。
```php
ctx()->setCache("abc", "asdasd");
$result = ctx()->getCache("abc"); // asdasd
```
## reply() - 快速回复
快速回复当前用户消息内容。
- 参数1`$msg`,字符串,你要回复的消息内容
- 参数2`$yield = false`,可选,当为 `true` 时,会协程等待后返回 **消息回复** 的结果,包括 API 状态码、消息 `message_id` 等。
```php
$r = ctx()->reply("我又好了。");
if($r["retcode"] == 0) Console::success("消息发送成功!");
```
## finalReply() - 快速回复
快速回复用户消息,并阻止其他模块接下来继续处理此事件。
参数同 `reply()`。
## waitMessage()
- 参数:`waitMessage($prompt = "", $timeout = 600, $timeout_prompt = "")`
- 用途:等待用户输入消息
`$prompt` 参数为回复用户的文本内容,`$timeout` 是等待用户回复的超时时间(秒)`$timeout_prompt` 是超时后回复用户的文本。
这个功能可以让开发机器人的代码逻辑和实际贴合,避免回调地狱、拼接参数、上下文脱节等问题,比如下方的示例,可以仅仅用两行代码实现一个问答式的对话过程。
用法示例:
```php
/**
* @CQCommand("自我介绍")
*/
function yourName(){
$r = ctx()->waitMessage("你叫啥名字呀?", 600, "你都10分钟不理我了嘤嘤嘤");
ctx()->finalReply("好的,可爱的机器人记住你叫 ".$r." 啦!以后多聊天哦!");
}
```
<chat-box>
) 自我介绍
( 你叫啥名字呀?
) jerry
( 好的,可爱的机器人记住你叫 jerry 啦!以后多聊天哦!
) 自我介绍
( 你叫啥名字呀?
^ 10分钟没理机器人
( 你都10分钟不理我了嘤嘤嘤
</chat-box>
## getArgs()
TODO还没写到这里下次更新今晚太困了。
## copy()
t
## getOption() - 获取匹配参数内容

View File

@@ -0,0 +1,790 @@
# 机器人 APIZMRobot
ZMRobot 类是封装好的 OneBot 标准的 API 接口调用类,可以在机器人连接后通过连接或者机器人 QQ 号获取对象并调用接口(如发送群消息、获取群列表等操作)。
| 属性项 | 属性值 | 备注 |
| -------- | ---------------- | ------------------------------ |
| 名称 | ZMRobot | |
| 类型 | 实例化类 | `$r = new ZMRobot($conn)` |
| 命名空间 | `ZM\API\ZMRobot` | 使用前先 `use ZM\API\ZMRobot;` |
## 属性
对象属性方法是对 API 的调整,例如是否以 `_async``_rate_limited` 后缀发送 API、设置协程返回还是异步返回结果等。
### ZMRobot::API_NORMAL
以默认(无后缀)方式请求 API。
### ZMRobot::API_ASYNC
以后缀 `_async` 方式异步请求 API。
### ZMRobot::API_RATE_LIMITED
以后缀 `_rate_limited` 方式请求 API。
## 方法
### setPrefix()
设置后缀。目前支持 `_async``_rate_limited`
- **prefix**: `int` `默认:API_NORMAL`,可选 `ZMRobot::API_NORMAL``ZMRobot::API_ASYNC``ZMRobot::API_RATE_LIMITED`
设置后缀后,请求的 API 会发生变化。例如发送私聊消息:`sendPrivateMsg()`,请求的 API 为 `send_private_msg_async`,详见 [OneBot 文档](https://github.com/howmanybots/onebot/blob/master/v11/specs/api/README.md)。
### setCallback()
设置 API 结果返回方式。默认为 true就是直接通过框架处理后接收回包直接返回给结果。如果为 false则 API 请求后只返回是否成功推送出 WS 数据包。
### getSelfId()
获取当前对象的机器人 QQ 或 OneBot 实例的 ID。
```php
$bot = ZMRobot::get(123456);
echo $bot->getSelfId(); //123456
```
### ZMRobot::get()
静态方法,用来通过机器人 QQ 或 OneBot 实例的 ID 获取 ZMRobot 对象。
参数:`$robot_id`,必填。
```php
$r = ZMRobot::get(123456);
$r->sendPrivateMsg(55555, "hello");
```
### ZMRobot::getRandom()
静态方法,随机获取一个连接到框架的机器人(多个机器人实例连接到框架时适用)。
如果框架没有连接到任何机器人实例,则会抛出一个异常:`ZM\Exception\RobotNotFoundException`
```php
try {
$bot = ZMRobot::getRandom();
$bot->sendPrivateMsg(55555, "foo");
} catch (\ZM\Exception\RobotNotFoundException $e) {
echo "还没有机器人连接到框架!\n";
}
```
### ZMRobot::getAllRobot()
获取所有连接到框架的机器人的 ZMRobot 对象。
返回值:`ZMRobot[]`
```php
$all = ZMRobot::getAllRobot();
foreach($all as $v) {
$v->sendPrivateMsg(55555, "机器人轮流给一个人发消息啦!");
}
```
### __construct()
构造方法。
参数:`$connection`:炸毛框架内部的连接对象,必填参数。
```php
//从上下文获取 Websocket 连接对象
$conn = ctx()->getConnection();
$bot = new ZMRobot($conn);
```
## 返回结果处理
因为框架的机器人是兼容 OneBot 标准的(原 CQHTTP所以每次接收发送 API 请求的结果都是大体一样的结构。我们以 `sendPrivateMsg()` 为例,因为发送出去的每一条消息都会在 OneBot 实例(如 CQHTTP 插件、go-cqhttp 等)中对应一个消息 ID以供我们核查消息和后续撤回等操作需要。
```php
$bot = ZMRobot::get("123456"); // 机器人QQ号
$obj = $bot->sendGroupMsg("234567", "你好");
echo json_encode($obj, 128|256);
```
```json
// 输出结果
{
"status": "ok",
"retcode": 0,
"data": {
"message_id": 1243
}
}
```
如上,`$obj` 就是我们的回包内容,我们通过调用 `sendPrivateMsg()` 这个 API 后,会拿到机器人发送此条消息的消息 ID然后可以通过它来进行其他操作例如撤回
```php
$result = $bot->deleteMsg($obj["data"]["message_id"]);
vardump($result["retcode"]); //如果成功撤回,输出 int(0)
```
### 状态码和 Data
状态码一般情况成功都是 0 或者 200在过去炸毛框架兼容 CQHTTP 插件时,错误码的标准按照 CYKU 给出的标准编写,不同的 OneBot 标准的实现,可能有不同的数值,需要根据你对接的机器人客户端进行适配。
结果中返回的 `data` 字段根据下方不同 API 的调用而不同,具体查看每个 API 写明的 `响应数据` 表格。
### response 表
| 字段名 | 数据类型 | 默认值 | 说明 |
| --------- | -------- | ---------- | ----------------------- |
| `status` | String | "ok" | 状态码说明 |
| `retcode` | number | 0 | 返回状态码 |
| `data` | array | 见 data 表 | 根据不同的 API 返回不同 |
## 机器人 API 方法
### sendPrivateMsg()
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ------------- | -------- | ------- | ------------------------------------------------------------ |
| `user_id` | number | - | 对方 QQ 号 |
| `message` | message | - | 要发送的内容 |
| `auto_escape` | boolean | `false` | 消息内容是否作为纯文本发送(即不解析 CQ 码),只在 `message` 字段是字符串时有效 |
响应数据
| 字段名 | 数据类型 | 说明 |
| ------------ | -------------- | ------- |
| `message_id` | number (int32) | 消息 ID |
例子
=== "代码"
```php
$bot = ZMRobot::get(123456); // 123456是你的机器人QQ
$bot->sendPrivateMsg("627577391", "你好啊!你好你好!");
```
=== "效果"
<chat-box>
( 你好啊!你好你好!
</chat-box>
### sendGroupMsg()
发送群组消息。
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ------------- | -------- | ------- | ------------------------------------------------------------ |
| `group_id` | number | - | 群号 |
| `message` | message | - | 要发送的内容 |
| `auto_escape` | boolean | `false` | 消息内容是否作为纯文本发送(即不解析 CQ 码),只在 `message` 字段是字符串时有效 |
响应数据
| 字段名 | 数据类型 | 说明 |
| ------------ | -------------- | ------- |
| `message_id` | number (int32) | 消息 ID |
### sendMsg()
发送消息。
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| -------------- | -------- | ------- | ------------------------------------------------------------ |
| `message_type` | string | - | 消息类型,支持 `private`、`group`、`discuss`,分别对应私聊、群组、讨论组,如不传入,则根据传入的 `*_id` 参数判断 |
| `target_id` | number | - | 目标号码,如 QQ 号,群号,讨论组号 |
| `message` | message | - | 要发送的内容 |
| `auto_escape` | boolean | `false` | 消息内容是否作为纯文本发送(即不解析 CQ 码),只在 `message` 字段是字符串时有效 |
响应数据
| 字段名 | 数据类型 | 说明 |
| ------------ | -------------- | ------- |
| `message_id` | number (int32) | 消息 ID |
### deleteMsg()
撤回消息。
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ------------ | -------------- | ------ | ------- |
| `message_id` | number (int32) | - | 消息 ID |
响应数据:无
### getMsg()
获取消息。
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| --------- | -------- | ------ | -------------------------------- |
| `message_id` | number | - | 消息 ID |
响应数据
| 字段名 | 数据类型 | 说明 |
| -------------- | -------------- | ------------------------------------------------------------ |
| `time` | number (int32) | 发送时间 |
| `message_type` | string | 消息类型,同 [消息事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/message.md) |
| `message_id` | number (int32) | 消息 ID |
| `real_id` | number (int32) | 消息真实 ID |
| `sender` | object | 发送人信息,同 [消息事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/message.md) |
| `message` | message | 消息内容 |
### getForwardMsg()
获取合并转发消息。
参数
| 字段名 | 数据类型 | 说明 |
| ------ | -------- | ----------- |
| `id` | string | 合并转发 ID |
响应数据
| 字段名 | 类型 | 说明 |
| --------- | ------- | ------------------------------------------------------------ |
| `message` | message | 消息内容,使用 [消息的数组格式](https://github.com/howmanybots/onebot/blob/master/v11/specs/message/array.md) 表示,数组中的消息段全部为 [`node` 消息段](https://github.com/howmanybots/onebot/blob/master/v11/specs/message/segment.md#合并转发自定义节点) |
### sendLike()
发送好友赞。
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| --------- | -------- | ------ | -------------------------------- |
| `user_id` | number | - | 对方 QQ 号 |
| `times` | number | 1 | 赞的次数,每个好友每天最多 10 次 |
响应数据
### setGroupKick()
群组踢人。
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| -------------------- | -------- | ------- | ------------------ |
| `group_id` | number | - | 群号 |
| `user_id` | number | - | 要踢的 QQ 号 |
| `reject_add_request` | boolean | `false` | 拒绝此人的加群请求 |
响应数据:无
### setGroupBan()
群组单人禁言。
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ---------- | -------- | --------- | -------------------------------- |
| `group_id` | number | - | 群号 |
| `user_id` | number | - | 要禁言的 QQ 号 |
| `duration` | number | `30 * 60` | 禁言时长单位秒0 表示取消禁言 |
响应数据:无
### setGroupAnonymousBan()
群组匿名用户禁言。
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ------------------- | ---------------- | --------- | ------------------------------------------------------------ |
| `group_id` | number | - | 群号 |
| `anonymous_or_flag` | object 或 string | - | 要禁言的匿名用户对象(群消息上报的 `anonymous` 字段)或用户的 flag |
| `duration` | number | `30 * 60` | 禁言时长,单位秒,无法取消匿名用户禁言 |
上面的 `anonymous_or_flag` 两者任选其一传入即可。
响应数据:无
### setGroupWholeBan()
群组全员禁言
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ---------- | -------- | ------ | -------- |
| `group_id` | number | - | 群号 |
| `enable` | boolean | `true` | 是否禁言 |
响应数据:无
### setGroupAdmin()
群组设置管理员
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ---------- | -------- | ------ | ------------------------- |
| `group_id` | number | - | 群号 |
| `user_id` | number | - | 要设置管理员的 QQ 号 |
| `enable` | boolean | `true` | true 为设置false 为取消 |
响应数据:无
### setGroupAnonymous()
群组匿名
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ---------- | -------- | ------ | ---------------- |
| `group_id` | number | - | 群号 |
| `enable` | boolean | `true` | 是否允许匿名聊天 |
响应数据:无
### setGroupCard()
设置群名片(群备注)
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ---------- | -------- | ------ | ---------------------------------------- |
| `group_id` | number | - | 群号 |
| `user_id` | number | - | 要设置的 QQ 号 |
| `card` | string | 空 | 群名片内容,不填或空字符串表示删除群名片 |
响应数据:无
### setGroupName()
设置群名。
参数
| 字段名 | 数据类型 | 说明 |
| ------------ | -------------- | ------ |
| `group_id` | number (int64) | 群号 |
| `group_name` | string | 新群名 |
响应数据:无
### setGroupLeave()
退出群组
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ------------ | -------- | ------- | -------------------------------------------------------- |
| `group_id` | number | - | 群号 |
| `is_dismiss` | boolean | `false` | 是否解散,如果登录号是群主,则仅在此项为 true 时能够解散 |
响应数据:无
### setGroupSpecialTitle()
设置群组专属头衔
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| --------------- | -------- | ------ | ------------------------------------------------------------ |
| `group_id` | number | - | 群号 |
| `user_id` | number | - | 要设置的 QQ 号 |
| `special_title` | string | 空 | 专属头衔,不填或空字符串表示删除专属头衔 |
| `duration` | number | `-1` | 专属头衔有效期,单位秒,-1 表示永久,不过此项似乎没有效果,可能是只有某些特殊的时间长度有效,有待测试 |
响应数据:无
### setFriendAddRequest()
处理加好友请求
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| --------- | -------- | ------ | ----------------------------------------- |
| `flag` | string | - | 加好友请求的 flag需从上报的数据中获得 |
| `approve` | boolean | `true` | 是否同意请求 |
| `remark` | string | 空 | 添加后的好友备注(仅在同意时有效) |
响应数据:无
### setGroupAddRequest()
处理加群请求 / 邀请
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ---------- | -------- | ------ | ------------------------------------------------------------ |
| `flag` | string | - | 加群请求的 flag需从上报的数据中获得 |
| `sub_type` | string | - | `add` 或 `invite`,请求类型(需要和上报消息中的 `sub_type` 字段相符) |
| `approve` | boolean | `true` | 是否同意请求/邀请 |
| `reason` | string | 空 | 拒绝理由(仅在拒绝时有效) |
响应数据无
### getLoginInfo()
获取登录号信息
参数:无
响应数据
| 字段名 | 数据类型 | 说明 |
| ---------- | -------------- | ------- |
| `user_id` | number (int64) | QQ 号 |
| `nickname` | string | QQ 昵称 |
### getStrangerInfo()
获取陌生人信息
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ---------- | -------- | ------- | ---------------------------------------------------- |
| `user_id` | number | - | QQ 号 |
| `no_cache` | boolean | `false` | 是否不使用缓存(使用缓存可能更新不及时,但响应更快) |
响应数据
| 字段名 | 数据类型 | 说明 |
| ---------- | -------------- | ------------------------------------- |
| `user_id` | number (int64) | QQ 号 |
| `nickname` | string | 昵称 |
| `sex` | string | 性别,`male` 或 `female` 或 `unknown` |
| `age` | number (int32) | 年龄 |
### getFriendList()
获取好友列表
参数:无
响应数据
响应内容为 JSON 数组,每个元素如下:
| 字段名 | 数据类型 | 说明 |
| ---------- | -------------- | ------ |
| `user_id` | number (int64) | QQ 号 |
| `nickname` | string | 昵称 |
| `remark` | string | 备注名 |
### getGroupInfo()
获取群信息
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ---------- | -------- | ------- | ---------------------------------------------------- |
| `group_id` | number | - | 群号 |
| `no_cache` | boolean | `false` | 是否不使用缓存(使用缓存可能更新不及时,但响应更快) |
响应数据
| 字段名 | 数据类型 | 说明 |
| ------------------ | -------------- | -------------------- |
| `group_id` | number (int64) | 群号 |
| `group_name` | string | 群名称 |
| `member_count` | number (int32) | 成员数 |
| `max_member_count` | number (int32) | 最大成员数(群容量) |
### getGroupList()
获取群列表
参数:无
响应数据
响应内容为 JSON 数组,每个元素如下:
| 字段名 | 数据类型 | 说明 |
| ------------ | -------------- | ------ |
| `group_id` | number (int64) | 群号 |
| `group_name` | string | 群名称 |
### getGroupMemberInfo()
获取群成员信息
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ---------- | -------- | ------- | ---------------------------------------------------- |
| `group_id` | number | - | 群号 |
| `user_id` | number | - | QQ 号 |
| `no_cache` | boolean | `false` | 是否不使用缓存(使用缓存可能更新不及时,但响应更快) |
响应数据
| 字段名 | 数据类型 | 说明 |
| ------------------- | -------------- | ------------------------------------- |
| `group_id` | number (int64) | 群号 |
| `user_id` | number (int64) | QQ 号 |
| `nickname` | string | 昵称 |
| `card` | string | 群名片/备注 |
| `sex` | string | 性别,`male` 或 `female` 或 `unknown` |
| `age` | number (int32) | 年龄 |
| `area` | string | 地区 |
| `join_time` | number (int32) | 加群时间戳 |
| `last_sent_time` | number (int32) | 最后发言时间戳 |
| `level` | string | 成员等级 |
| `role` | string | 角色,`owner` 或 `admin` 或 `member` |
| `unfriendly` | boolean | 是否不良记录成员 |
| `title` | string | 专属头衔 |
| `title_expire_time` | number (int32) | 专属头衔过期时间戳 |
| `card_changeable` | boolean | 是否允许修改群名片 |
### getGroupMemberList()
获取群成员列表
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ---------- | -------- | ------ | ---- |
| `group_id` | number | - | 群号 |
响应数据
响应内容为 JSON 数组,每个元素的内容和上面的 `/get_group_member_info` 接口相同,但对于同一个群组的同一个成员,获取列表时和获取单独的成员信息时,某些字段可能有所不同,例如 `area`、`title` 等字段在获取列表时无法获得,具体应以单独的成员信息为准。
### getGroupHonorInfo()
获取群荣誉信息。
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ---------- | -------------- | ------ | ------------------------------------------------------------ |
| `group_id` | number (int64) | - | 群号 |
| `type` | string | - | 要获取的群荣誉类型,可传入 `talkative` `performer` `legend` `strong_newbie` `emotion` 以分别获取单个类型的群荣誉数据,或传入 `all` 获取所有数据 |
响应数据
| 字段名 | 数据类型 | 说明 |
| -------------------- | -------------- | ---------------------------------------------------------- |
| `group_id` | number (int64) | 群号 |
| `current_talkative` | object | 当前龙王,仅 `type` 为 `talkative` 或 `all` 时有数据 |
| `talkative_list` | array | 历史龙王,仅 `type` 为 `talkative` 或 `all` 时有数据 |
| `performer_list` | array | 群聊之火,仅 `type` 为 `performer` 或 `all` 时有数据 |
| `legend_list` | array | 群聊炽焰,仅 `type` 为 `legend` 或 `all` 时有数据 |
| `strong_newbie_list` | array | 冒尖小春笋,仅 `type` 为 `strong_newbie` 或 `all` 时有数据 |
| `emotion_list` | array | 快乐之源,仅 `type` 为 `emotion` 或 `all` 时有数据 |
其中 `current_talkative` 字段的内容如下:
| 字段名 | 数据类型 | 说明 |
| ----------- | -------------- | -------- |
| `user_id` | number (int64) | QQ 号 |
| `nickname` | string | 昵称 |
| `avatar` | string | 头像 URL |
| `day_count` | number (int32) | 持续天数 |
其它各 `*_list` 的每个元素是一个 JSON 对象,内容如下:
| 字段名 | 数据类型 | 说明 |
| ------------- | -------------- | -------- |
| `user_id` | number (int64) | QQ 号 |
| `nickname` | string | 昵称 |
| `avatar` | string | 头像 URL |
| `description` | string | 荣誉描述 |
### getCookies()
获取 Cookies。
!!! warning "注意"
目前开源的 mirai 为底层的机器人客户端均不支持获取 Cookies 和 CSRF Token包括 go-cqhttp。
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| -------- | -------- | ------ | ----------------------- |
| `domain` | string | 空 | 需要获取 cookies 的域名 |
响应数据
| 字段名 | 数据类型 | 说明 |
| --------- | -------- | ------- |
| `cookies` | string | Cookies |
### getCsrfToken()
获取 CSRF Token
参数:无
响应数据
| 字段名 | 数据类型 | 说明 |
| ------- | -------------- | ---------- |
| `token` | number (int32) | CSRF Token |
### getCredentials()
获取 QQ 相关接口凭证,即上面两个合并。
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| -------- | -------- | ------ | ----------------------- |
| `domain` | string | 空 | 需要获取 cookies 的域名 |
响应数据
| 字段名 | 数据类型 | 说明 |
| ------------ | -------------- | ---------- |
| `cookies` | string | Cookies |
| `csrf_token` | number (int32) | CSRF Token |
### getRecord()
获取语音。其实并不是真的获取语音,而是转换语音到指定的格式。
> **提示**:要使用此接口,通常需要安装 ffmpeg请参考 OneBot 实现的相关说明。
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ------------ | -------- | ------ | ------------------------------------------------------------ |
| `file` | string | - | 收到的语音文件名CQ 码的 `file` 参数),如 `0B38145AA44505000B38145AA4450500.silk` |
| `out_format` | string | - | 要转换到的格式,目前支持 `mp3`、`amr`、`wma`、`m4a`、`spx`、`ogg`、`wav`、`flac` |
响应数据
| 字段名 | 数据类型 | 说明 |
| ------ | -------- | ------------------------------------------------------------ |
| `file` | string | 转换后的语音文件路径,如 `/home/somebody/cqhttp/data/record/0B38145AA44505000B38145AA4450500.mp3` |
### getImage()
获取图片。
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ------ | -------- | ------ | ------------------------------------------------------------ |
| `file` | string | - | 收到的图片文件名CQ 码的 `file` 参数),如 `6B4DE3DFD1BD271E3297859D41C530F5.jpg` |
响应数据
| 字段名 | 数据类型 | 说明 |
| ------ | -------- | ------------------------------------------------------------ |
| `file` | string | 下载后的图片文件路径,如 `/home/somebody/cqhttp/data/image/6B4DE3DFD1BD271E3297859D41C530F5.jpg` |
### canSendImage()
检查是否可以发送图片。
参数:无
响应数据
| 字段名 | 数据类型 | 说明 |
| ------ | -------- | ------ |
| `yes` | boolean | 是或否 |
### canSendRecord()
检查是否可以发送语音,返回同上。
### getStatus()
获取插件运行状态。
参数:无
响应数据
| 字段名 | 数据类型 | 说明 |
| -------- | -------- | -------------------------------------------------------- |
| `online` | boolean | 当前 QQ 在线,`null` 表示无法查询到在线状态 |
| `good` | boolean | 状态符合预期,意味着各模块正常运行、功能正常,且 QQ 在线 |
| ...... | - | OneBot 实例自行添加的其他内容 |
通常情况下建议只使用 `online` 和 `good` 这两个字段来判断运行状态,因为根据 OneBot 实现的不同,其它字段可能完全不同。
### getVersionInfo()
获取版本信息
响应数据
| 字段名 | 数据类型 | 说明 |
| ------------------ | -------- | ----------------------------- |
| `app_name` | string | 应用标识,如 `mirai-native` |
| `app_version` | string | 应用版本,如 `1.2.3` |
| `protocol_version` | string | OneBot 标准版本,如 `v11` |
| …… | - | OneBot 实现自行添加的其它内容 |
### setRestartPlugin()
重启 OneBot 客户端
由于重启 OneBot 实现同时需要重启 API 服务,这意味着当前的 API 请求会被中断,因此需要异步地重启,接口返回的 `status` 是 `async`。
参数
| 字段名 | 数据类型 | 默认值 | 说明 |
| ------- | -------- | ------ | ------------------------------------------------------------ |
| `delay` | number | `0` | 要延迟的毫秒数,如果默认情况下无法重启,可以尝试设置延迟为 2000 左右 |
响应数据:无
### cleanCache()
清理 OneBot 客户端的缓存。
参数:无
响应数据:无
### callExtendedAPI() (扩充 API
用来调用 OneBot 标准之外扩展出来的自定义 API。
使用不同 OneBot 客户端时,可能有一些 API 不在上方的 OneBot 标准里,这时可以使用此方法进行额外调用。
参数
| 字段名 | 数据类型 | 默认值 |
| -------- | -------- | ------ |
| `action` | string | 必填 |
| `params` | array | `[]` |
例子
```php
$result = $bot->callExtendedAPI("get_group_root_files", ["group_id" => 123456]);
//这里以 go-cqhttp 扩展的一个获取群文件的 API 为例
var_dump($result["data"]);
// 输出群文件列表
```

View File

@@ -53,4 +53,18 @@ class Hello {
机器人开发过程中常见的 `@CQCommand`,或者是 HTTP 服务器路由绑定 `@RequestMapping` 都是相当于由对应注解代表了事件,而 `@Middleware``@Closed` 等这类注解显然不代表任何事件,只能当作这个函数或类的修饰属性而已。代表了事件的注解,我们称之为**注解事件**,它会在某种事件达成条件后触发注解下方的函数本身。
值得注意的是,注解事件本身概念是我凭空捏造的,我不好解释所以只能创造这么一个词来代指这一抽象的概念,硬要解释的话,大致就好比一个社区里有一个卖牛奶的,有几家人订阅了每日上门送牛奶的服务,只要你打了“给我配送牛奶”的注解,他就会上门。而它送的不止一种奶,可以给你个性化定制,比如让卖牛奶的给你带包糖带瓶水,而描述这个的注解就只能做一个之前注解的修饰。假设你只写了带包糖的注解,没有写给我配送牛奶的注解,那他永远也不会给你送牛奶和糖过来。
值得注意的是,注解事件本身概念是我凭空捏造的,我不好解释所以只能创造这么一个词来代指这一抽象的概念,硬要解释的话,大致就好比一个社区里有一个卖牛奶的,有几家人订阅了每日上门送牛奶的服务,只要你打了“给我配送牛奶”的注解,他就会上门。而它送的不止一种奶,可以给你个性化定制,比如让卖牛奶的给你带包糖带瓶水,而描述这个的注解就只能做一个之前注解的修饰。假设你只写了带包糖的注解,没有写给我配送牛奶的注解,那他永远也不会给你送牛奶和糖过来。
## 阻断事件
由于炸毛框架内的注解事件统一由一个通用的事件分发器进行分发,所以你在任何注解事件内都可以用通用的方式阻断当前正在运行的事件。
首先就是要记得先 use 事件分发器的类:`use ZM\Event\EventDispatcher;`
```php
EventDispatcher::interrupt();
EventDispatcher::interrupt($data); // 也可以带返回值,自定义注解事件时有用。
```

3
docs/event/中间件.md Normal file
View File

@@ -0,0 +1,3 @@
# 中间件注解
TODO师傅莫催快肝完了

View File

@@ -0,0 +1,119 @@
# 事件分发器(进阶)
事件分发器是以上所有注解事件执行函数的一个分发器,如果你在上一章已经学会了如何创建自定义注解,那么本章就来说明如何用内置的事件分发器进行分发自定义事件。
如果你不需要了解或自定义有关事件分发的功能,此处可无需阅读。
## 属性
- 类名:`ZM\Event\EventDispatcher`
## 方法
### EventDispatcher::interrupt()
阻断当前正在运行的事件,只能在事件内部被调用的函数中实现。
### __construct()
构造方法。
```php
EventDispatcher::__construct(string $class = '')
```
初始化一个事件分发器,可进行一系列设置,对事件分发做限定。
#### 参数
`$class`:设置要分发的事件对应的注解类名,支持自定义注解(例如 `CQMessage::class`
### setRuleFunction()
设置函数触发规则判定的函数(就是在执行事件函数前执行的规则判定)
```php
setRuleFunction(callable $rule = null)
```
#### 参数
`$rule`:支持回调或闭包。闭包的参数为执行对应事件函数所绑定的注解事件对象。
```php
$dispatcher = new EventDispatcher(CustomEvent::class);
$dispatcher->setRuleFunction(function($obj) {
return $obj->name == "zhamao" ? true : false;
});
```
上方的 `$obj` 就是 CustomEvent 类的实例,参数绑定为注解中对应的参数。
### setResultFunction()
设置事件函数返回值处理的回调函数。
```php
setReturnFunction(callable $return_func)
```
#### 参数
`$return_func`:设置事件函数返回值处理的回调函数,回调参数绑定为对应单独事件函数的返回值。
```php
$dispatcher = new EventDispatcher(CustomEvent::class);
$dispatcher->setReturnFunction(function($return) {
if (is_string($return)) Console::info("函数返回了 ".$return);
});
```
### dispatchEvents()
开始分发事件。
```php
dispatchEvents(...$params)
```
#### 参数
自定义参数,这里填入的参数将被填入被分发的函数参数中。
```php
$dispatcher->dispatchEvents("foo", "bar");
```
```php
<?php
class Test {
/**
* @CustomEvent("zhamao")
*/
public function test($arg1, $arg2) {
echo "$arg1: $arg2"; //将输出 "foo: bar"
}
}
```
## 机制
事件分发器的机制说简单不简单,说复杂也不复杂,它和中间件有着非常大的关系,因为它会自动检测和识别所要执行的函数有没有中间件,并且根据顺序进行执行。
在炸毛框架内部,一个完整的事件流程和中间件的关系如下图:
![Untitled Diagram](../assets/img/diagram3.dbb4e32e.png)
对于同一事件的优先级和响应顺序,优先级的关系如下图:
![Untitled Diagram](../assets/img/Untitled Diagram.png)
对于事件内单个事件被调用的单个函数下如果存在多个中间件,中间件模型和事件的关系如下图:
![Untitled Diagram-2](../assets/img/diagram4.16ce39ca.png)
## 实战例子
我们假设 CustomEvent 是我们的自定义注解。还没写完,这部分太复杂了,而且举例子也不好举例,这块应该也不用着急更新。
TODO待完成

View File

@@ -0,0 +1,328 @@
# 机器人注解事件
QQ 机器人事件是指 CQHTTP 插件发来的 Event 事件,被框架处理后触发到单个类中方法的事件。
为了便于开发,这里的注解类对应 CQHTTP 插件返回的 `post_type` 类型,对号入座即可。
!!! tip "提示"
在使用注解绑定事件过程中,如果无 **必需** 参数,可一个参数也不写,效果就是此事件任何情况下都会调用此方法。例如:`@CQMessage()`
事件是用户需要从 OneBot 被动接收的数据,有以下几个大类:
- [消息事件](#cqmessage),包括私聊消息、群消息等,被 [`@CQCommand`](#cqcommand)`@CQMessage` 注解处理。
- [通知事件](#cqnotice),包括群成员变动、好友变动等,被 `@CQNotice` 注解事件处理。
- [请求事件](#cqrequest),包括加群请求、加好友请求等,被 `@CQRequest` 注解事件处理。
- [元事件](#cqmetaevent),包括 OneBot 生命周期、心跳等,被 `@CQMetaEvent` 注解事件处理。
## CQMessage()
QQ 收到消息后触发的事件对应注解。
### 属性
| 类型 | 值 |
| ---- | ----------- |
| 名称 | `@CQMessage` |
| 触发前提 | 当 `post_type``message` 时触发 |
| 命名空间 | `ZM\Annotation\CQ\CQMessage` |
| 适用位置 | 方法 |
| 返回值处理 | 当方法返回字符串时,效果等同于执行 `ctx()->reply("xxx")` |
### 参数
| 参数名称 | 参数范围 | 用途 | 默认 |
| ------------ | ------------------------------------- | -------------------------------------- | ---- |
| message_type | `string`,支持填入 `private``group` | 限定消息事件的来源类型,如私聊或群消息 | 空 |
| user_id | `int64``string` | 限定消息发送用户 IDQQ 号) | 空 |
| group_id | `int64``string` | 限定消息发送来源群 IDQQ 群号) | 空 |
| message | `string` | 限定消息内容文本 | 空 |
| level | `int` | 事件优先级(越大越靠前) | 20 |
### 用法
下面这个例子的注释用途就是:
- 在用户 QQ 为 `123456` 的用户私聊给机器人发消息后机器人回复内容。
- 用户发送文字为 `hello` 时返回 `你好啊xxx` 的消息。
=== "代码"
```php
<?php
namespace Module\Example;
use ZM\Annotation\CQ\CQMessage;
class Hello {
/**
* @CQMessage(message_type="private",user_id=123456)
*/
public function test() {
return "你和机器人私聊发送了这些文本:".ctx()->getMessage();
}
/**
* @CQMessage(message="hello")
*/
public function hello() {
return "你好啊,".ctx()->getUserId();
}
}
```
=== "效果"
<chat-box>
) 假设我是私聊机器人
( 你和机器人私聊发送了这些文本:假设我是私聊机器人
^ 假设我现在切到群里在群里发hello
) hello
( 你好啊123456
</chat-box>
## CQCommand()
此注解是对 `@CQMessage` 类别的再封装,是命令解析格式处理消息的利器。例如,你想写一个疫情上报,指令是 `疫情 城市名称`,那么此方式来解析用户消息会更加方便。
### 属性
| 类型 | 值 |
| ---------- | -------------------------------------------------------- |
| 名称 | `@CQCommand` |
| 触发前提 | 当根据参数规则匹配到用户命令式消息时触发 |
| 命名空间 | `ZM\Annotation\CQ\CQCommand` |
| 适用位置 | 方法 |
| 返回值处理 | 当方法返回字符串时,效果等同于执行 `ctx()->reply("xxx")` |
### 参数
| 参数名称 | 参数范围 | 用途 | 默认 |
| ------------ | ------------------------------------- | ------------------------------------------------------ | ---- |
| match | `string` | 匹配第一个词的命令式消息,如 `天气 北京` 中的 `天气` | 空 |
| pattern | `string` | 根据 * 号通配符进行模式匹配用户消息,如 `查询*天气` | 空 |
| regex | `string`,限定正则表达式 | 匹配正则表达式匹配到的用户消息 | 空 |
| start_with | `string` | 匹配消息开头相匹配的消息,如 `我叫炸毛`,这里写 `我叫` | 空 |
| end_with | `string` | 匹配消息结尾相匹配的消息,以 `start_with` 类推 | 空 |
| keyword | `string` | 匹配消息中有相关关键词的消息 | 空 |
| alias | `array[string]` | `match` 匹配到命令的别名,数组形式 | `{}` |
| message_type | `string`,支持填入 `private``group` | 限定消息事件的来源类型,同 `@CQMessage` | 空 |
| user_id | `int64` 或 `string` | 限定消息发送用户 ID同 `@CQMessage` | 空 |
| group_id | `int64` 或 `string` | 限定消息发送来源群 ID同 `@CQMessage` | 空 |
| level | `int` | 事件优先级(越大越靠前) | 20 |
!!! warning "注意"
在 `@CQCommand` 注解事件中,从 `match` 到 `keyword` 六个参数中,必须且只能定义一个,`alias` 目前只能和 `match` 参数同时使用;
框架内部对于同一条消息事件,优先处理 `@CQCommand` 注解事件,如果未匹配到任何注解事件,则才会继续执行 `@CQMessage` 注解事件。
- 参数 `match` 匹配模式是:遇到空格、换行就会切分,比如 `点歌 xxx yyy` 会被分割为 `[点歌,xxx,yyy]`,然后抽取第一个词做为命令去匹配,剩下的为参数。
- 参数 `pattern` 匹配模式是:\* 号位置变成参数,比如 `从*到*的随机数`,我们输入 `从1到9的随机数`,成功匹配,参数列表:`[1,9]`。
- 参数 `regex` 匹配模式为 PHP 标准的 pcre 正则表达式,比如 `([01][0-9][2][0-3]):[0-5][0-9]` 用来匹配 `22:45`。
- 参数 `start_with` `end_with` 和 `keyword` 都是根据消息内容开头、结尾或者内容包含是否匹配来匹配,这里就不多说了,你懂的。
- 参数 `alias` 用的时候一般是这样:`@CQCommand(match="你好",alias={"你好啊","你是谁"})`,用以扩充同义词下命令的适配广度。
### 用法
我们以参数 `match` 写一个简单的 demo
=== "代码"
```php
<?php
namespace Module\Example;
use ZM\Annotation\CQ\CQCommand;
class Hello {
/**
* @CQCommand(match="疫情",alias={"COVID"})
*/
public function virus(){
$city = ctx()->getNextArg("请输入城市名称");
return "城市 ".$city." 的疫情状况如下:"."{这里假装是疫情接口返回的数据}";
}
/**
* 如果选择使用 match 参数的话,可以省略 `match=`
* @CQCommand("掷硬币")
*/
public function randChoice() {
return "你看到的是:" . (mt_rand(0,1) ? "正面" : "反面");
}
/**
* @CQCommand(pattern="*把*翻译成*")
*/
public function translate() {
ctx()->getNextArg(); // 为什么需要单独调用一次呢?看下面例子就知道啦
$text = ctx()->getNextArg(); // 获取第二个星号匹配的内容
$target = ctx()->getNextArg(); // 获取第三个星号匹配的内容
// 这里 FakeTranslateAPI 是假设我们对接了一个翻译的 API开发时请替换为自己的接口。
return "翻译结果:" . FakeTranslateAPI::translate($text, $target);
}
}
```
=== "效果"
<chat-box>
) 疫情 北京
( 城市 北京 的疫情状况如下blablablabla
) COVID 香港
( 城市 香港 的疫情状况如下blablablabla
) 掷硬币
( 你看到的是:正面
) 我想把我爱你翻译成英语
( 翻译结果I love you!
</chat-box>
## CQNotice()
通知事件。
### 属性
| 类型 | 值 |
| ---------- | ----------------------------------------------------- |
| 名称 | `@CQNotice` |
| 触发前提 | 当 `post_type` 为 `notice` 时触发(通知类事件上报时) |
| 命名空间 | `ZM\Annotation\CQ\CQNotice` |
| 适用位置 | 方法 |
| 返回值处理 | 无作用 |
### 参数
| 参数名称 | 参数范围 | 用途 | 默认 |
| ----------- | ------------------------------------ | ------------------------------------------------------------ | ---- |
| notice_type | `string`,支持填入 onebot 标准的内容 | 限定通知事件的类型,见 [OneBot - 通知事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md) | 空 |
| user_id | `int64` 或 `string` | 限定通知事件用户 IDQQ 号),同上见 [OneBot - 通知事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md) | 空 |
| group_id | `int64` 或 `string` | 限定通知事件群 IDQQ 群号),同上见 [OneBot - 通知事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md) | 空 |
| operator_id | `int64` 或 `string` | 限定操作者 QQ 号,同上见 [OneBot - 通知事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md) | 空 |
| level | `int` | 事件优先级(越大越靠前) | 20 |
### 用法
TODO先放着有时间再更。
## CQRequest()
请求事件。
### 属性
| 类型 | 值 |
| ---------- | ------------------------------------------------------ |
| 名称 | `@CQRequest` |
| 触发前提 | 当 `post_type` 为 `request` 时触发(通知类事件上报时) |
| 命名空间 | `ZM\Annotation\CQ\CQRequest` |
| 适用位置 | 方法 |
| 返回值处理 | 无作用 |
### 参数
| 参数名称 | 参数范围 | 用途 | 默认 |
| ------------ | ------------------------------------ | ------------------------------------------------------------ | ---- |
| request_type | `string`,支持填入 onebot 标准的内容 | 限定请求事件的类型,见 [OneBot - 请求事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/request.md) | 空 |
| user_id | `int64` 或 `string` | 限定请求事件当事人用户 IDQQ 号),见 [OneBot - 请求事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/request.md) | 空 |
| sub_type | `string` | 限定请求事件来源群 IDQQ 群号),见 [OneBot - 请求事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/request.md) | 空 |
| comment | `string` | 限定验证消息内容,见 [OneBot - 请求事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/request.md) | 空 |
| level | `int` | 事件优先级(越大越靠前) | 20 |
### 用法
TODO先放着有时间再更。
## CQMetaEvent()
元事件元事件不属于用户交互的一部分消息、通知、请求三大类事件是与聊天软件直接相关的、机器人真实接收到的事件除了这些OneBot 自己还会产生一类事件,这里称之为「元事件」,例如生命周期事件、心跳事件等,这类事件与兼容 OneBot 的客户端和炸毛框架本身的运行状态有关,而与聊天软件无关。元事件的上报方式和普通事件完全一样。
### 属性
| 类型 | 值 |
| ---------- | ----------------------------------------------------- |
| 名称 | `@CQMetaEvent` |
| 触发前提 | 当 `post_type` 为 `meta_event` 时触发(元事件上报时) |
| 命名空间 | `ZM\Annotation\CQ\CQMetaEvent` |
| 适用位置 | 方法 |
| 返回值处理 | 无作用 |
### 参数
| 参数名称 | 参数范围 | 用途 | 默认 |
| --------------- | ------------------ | ------------------------------------------------------------ | ---- |
| meta_event_type | `string`**必需** | 限定元事件的类型,见 [OneBot - 元事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/meta.md) | |
| level | `int` | 事件优先级(越大越靠前) | 20 |
### 用法
TODO先放着有时间再更。
## CQBefore()
所有机器人事件的前置注解事件,一般用作消息过滤、全局日志、全局替换等。
### 属性
| 类型 | 值 |
| ---------- | ------------------------------------------------------------ |
| 名称 | `@CQBefore` |
| 触发前提 | 当 `post_type` 等于参数 `cq_event` 时触发 |
| 命名空间 | `ZM\Annotation\CQ\CQBefore` |
| 适用位置 | 方法 |
| 返回值处理 | 仅可返回 `bool`,如果为 `false`,则阻断 `cq_event` 类的所有事件防止被执行 |
### 参数
| 参数名称 | 参数范围 | 用途 | 默认 |
| -------- | ------------------------------------------------------------ | ------------------------ | ---- |
| cq_event | `string`**必需**,支持 `message``notice``request``meta_event` | 限定机器人时间的类型 | |
| level | `int` | 事件优先级(越大越靠前) | 20 |
### 用法
=== "代码"
```php
<?php
namespace Module\Example;
use ZM\Annotation\CQ\CQBefore;
use ZM\Annotation\CQ\CQMessage;
class Test {
/**
* @CQBefore("message")
*/
public function filter(){
// 可用于敏感词,如政治相关的词语不响应其他模块
if(mb_strpos(ctx()->getMessage(), "谷歌") !== false) return false;
else return true;
}
/**
* @CQCommand("百科")
*/
public function wiki() {
$content = ctx()->getNextArg("请说你要查百科的内容");
// 这里假设你对接了一个查百科的接口
return "已搜到匹配 $content 的如下结果:".FakeAPI::searchWiki($content);
}
}
```
=== "效果"
<chat-box>
) 百科 北京
( 已搜到匹配 北京 的如下结果blablabla
) 百科 谷歌被封
^ 机器人没有任何回复
!!! warning "注意"
在设置了 `level` 参数后,如果设置了多个 `@CQBefore` 监听事件函数,更高 `level` 的事件函数返回了 `false`,则低 `level` 的绑定函数不会执行,所有 `@CQMessage` 绑定的事件也不会执行。
你也可以使用 `@CQBefore` 做一些消息的转发和过滤。比如你想去除用户发来的文字中的 emoji、图片等 CQ 码,只保留文本。
## CQAfter()
同上。只是在以上所有事件都调用后才会调用的。
## CQAPIResponse()
TODO还没写完先放着有时间再更。

View File

@@ -0,0 +1,72 @@
# 框架核心注解事件
框架核心注解事件区别于机器人和路由注解事件,这里框架注解事件都是**直接**或封装调用 Swoole 的回调事件的,所以对一些比较底层或者基础的操作都在这里做,例如收到 HTTP 或 WebSocket 连接后执行的事件函数。
## OnSwooleEvent()
绑定 Swoole 所相关的事件,例如 WebSocket 接入、收到 WS 消息、关闭 WS 连接HTTP 请求到达等。
### 属性
| 类型 | 值 |
| ------------ | ------------------------------------------ |
| 名称 | `@OnSwooleEvent` |
| 触发前提 | 当参数指定的 `type` 对应的事件被触发后激活 |
| 命名空间 | `ZM\Annotation\Swoole\OnSwooleEvent` |
| 适用位置 | 方法 |
| 返回值处理 | 无 |
| 注解绑定参数 | |
### 注解参数
| 参数名称 | 参数范围 | 用途 | 默认 |
| -------- | -------------------------------------------------------- | ----------------------------------------------- | ---------------- |
| type | `string`,支持填入 `open``request``close``message` | 限定事件的类型,**必填** | |
| rule | `string`,必须是可执行且返回 bool 的 PHP 代码 | 例如判断连接是否为 QQ 机器人(`connectIsQQ()` | 空rule 为 true |
| level | `int` | 事件优先级(越大越靠前) | 20 |
### 事件绑定参数
`$conn`: [ConnectionObject](/advanced/inside-class/) 类型,返回一个当前 WS 连接的连接对象。
### 示例1机器人连接框架后输出信息
```php
<?php
namespace Module\Example;
use ZM\Annotation\Swoole\OnSwooleEvent;
use ZM\ConnectionManager\ConnectionObject;
use ZM\Console\Console;
class Hello {
/**
* 在机器人客户端连接框架后向终端输出信息
* @OnSwooleEvent("open",rule="connectIsQQ()")
* @param $conn
*/
public function onConnect(ConnectionObject $conn) {
Console::info("机器人 " . $conn->getOption("connect_id") . " 已连接!");
}
}
```
这里的 Console 是终端输出组件,详情见组件一栏对应的文档查询。
### 示例2阻断 Chrome 访问框架时多访问一次的问题)
```php
<?php
namespace Module\Example;
use ZM\Annotation\Swoole\OnSwooleEvent;
use ZM\Event\EventDispatcher;
class Hello {
/**
* 阻止 Chrome 自动请求 /favicon.ico 导致的多条请求并发和干扰
* @OnSwooleEvent("request",rule="ctx()->getRequest()->server['request_uri'] == '/favicon.ico'",level=200)
*/
public function onRequest() {
EventDispatcher::interrupt();
}
}
```
其中 EventDispatcher 为事件分发器interrupt 是通用阻断方法,如果你平常只使用阻断,则只需掌握这一个方法即可,`EventDispatcher::interrupt()` 在所有事件内可用。

View File

@@ -0,0 +1,3 @@
# 自定义注解
TODO师傅莫催快肝完了

View File

@@ -0,0 +1,231 @@
# 路由注解事件
炸毛框架提供了一个简易但是高效易用的 HTTP 路由注解,你可以使用路由功能来开发任何 Web 应用微服务、API 接口、中间件等。
!!! quote "开发提示"
本章节涉及的路由和控制器概念可能和其他传统框架有一些出入,而且炸毛框架非绝对根据 PSR 标准进行开发,目的是使用上一些常见的东西尽可能地灵活和不罗嗦。
## 控制器和路由
Controller 和 Route 为路由注解事件的核心注解事件,其中 Controller 的注解事件为 `@Controller`Route 的注解事件为 `@RequestMapping`
### Controller()
#### 属性
| 类型 | 值 |
| ---------- | ------------------------------- |
| 名称 | `@Controller` |
| 触发前提 | 当路由 url 匹配到时进入触发 |
| 命名空间 | `ZM\Annotation\Http\Controller` |
| 适用位置 | 类 |
| 返回值处理 | 对类注解修饰,无返回值 |
#### 参数
| 参数名称 | 参数范围 | 用途 | 默认 |
| -------- | -------------- | ------------ | ---- |
| prefix | `string`,必需 | 控制器的 url | 空 |
### RequestMapping()
#### 属性
| 类型 | 值 |
| ---------- | ----------------------------------------------------------- |
| 名称 | `@RequestMapping` |
| 触发前提 | 当路由 url 匹配到时进入触发 |
| 命名空间 | `ZM\Annotation\Http\RequestMapping` |
| 适用位置 | 方法 |
| 返回值处理 | 返回类型是 `string` 时,自动调用 HTTP 响应并返回 200 状态码 |
#### 参数
| 参数名称 | 参数范围 | 用途 | 默认 |
| -------------- | ----------------------------------------------------------- | -------------------------- | ------------------------------------------ |
| route | `string`,必需 | 控制器的 url | 空 |
| name | `string` | 路由的名称 | 空 |
| request_method | `array`,限定 `RequestMethod::GET` 等常量 | 限制激活路由的 HTTP 方法 | `[RequestMethod::GET,RequestMethod::POST]` |
| params | `array`,当路由中含有如 `{id}` 类似的动态路由时,会动态改变 | 动态参数的路由参数值的绑定 | `[]` |
#### 函数调用参数
- `$param`:如果路由中存在变量(动态路由),则会把动态路由所匹配的参数放入 `$param` 数组中。
```php
/**
* @RequestMapping(route="/test/{ass}")
*/
public function testName($param) {
return "Your name is ".($param["ass"] ?? "unknown");
}
```
### 路由示例
=== "代码"
```php
<?php
namespace Module\Example;
use ZM\Annotation\Http\Controller;
use ZM\Annotation\Http\RequestMapping;
/**
* @Controller("/api")
*/
class Hello {
/**
* @RequestMapping("/index")
*/
public function index(){
ctx()->getResponse()->end("This is API index page"); // 使用上下文获取响应对象
}
/**
* @RequestMapping("/ping")
*/
public function ping(){
return "pong"; // 直接返回字符串
}
}
```
=== "效果"
!!! example "效果描述"
当访问浏览器的 `http://localhost:20001/api/index` 时,浏览器会返回 `This is API index page`,当访问 `/api/ping` 的 url 时,浏览器会返回 `pong`。
```
/ -> 无任何路由
/api/index -> Hello->index
/api/ping -> Hello->ping
```
!!! tip "提示"
当 `@Controller` 为 `/` 的时候,效果和不写是一样的,`@RequestMapping` 为 `/` 或 `/index/inside` 等多级路由也是可以的。
### 绑定参数
在 `@RequestMapping` 中,不仅可以写静态的路由地址,也可以写绑定的参数。例如:`@RequestMapping(route="/index/{name}")`,则访问 `/index/xxx` 的时候,你在函数方法内可以这样获取此参数:
```php
/**
* @RequestMapping("/index/{name}")
*/
public function index($arg) {
return "Your param 'name' is ".$arg["name"];
}
```
## 获取请求参数 GET / POST
炸毛框架支持获取外部 HTTP 请求进来的 GET 和 POST 请求,通过获取 HTTP 请求对象 [Request](/advanced/inside-class/) 即可。对象具体属性和方法点这个链接进去就行。
### 示例
=== "获取 GET"
```php
/**
* @RequestMapping("/testUrl")
*/
public function testUrl() {
$get = ctx()->getRequest()->get;
if(isset($get["name"])) return "hello, ".$get["name"];
else return "Unknown name!!";
}
```
=== "获取 POSTx-www-form-urlencoded"
```php
/**
* @RequestMapping("/testUrl")
*/
public function testUrl() {
$post = ctx()->getRequest()->post;
if(isset($post["name"])) return "hello, ".$post["name"];
else return "Unknown name!!";
}
```
=== "获取 JSON POST"
```php
/**
* @RequestMapping("/testUrl")
*/
public function testUrl() {
$post = ctx()->getRequest()->rawContent();
$json = json_decode($post, true);
if ($json === null) return "Invalid json data!";
if(isset($json["name"])) return "hello, ".$json["name"];
else return "Unknown name!!";
}
```
## 设置路由请求方式
如果想要设置允许请求控制器的 HTTP 请求方式,可以使用方法在控制器中的 `@RequestMapping` 注解配置 `method` 参数,可以是 `GET``POST``PUT`, `PATCH``DELETE``OPTIONS``HEAD` 中的一个或多个。
- 限定 HTTP 方法:`@RequestMapping(method="GET")``@RequestMapping(method={"GET","POST"})`
## 静态文件服务器
框架支持了静态文件的访问。如需使用,则需要先到配置文件中配置相应的 `static_file_server` 参数中 `status` 为 `true`。
框架分为两种静态文件服务器,一种是全局的静态文件服务器,比如框架部署在 `http://127.0.0.1:20001/` 上通过 HTTP 访问,如果没有访问到 `@RequestMapping` 注解事件注册的路由地址,则会通过 url 自动查找静态文件服务器设置的根路径下面的文件,如果都不存在则会返回 404。
### 配置全局静态文件服务器
我们假设在你写的框架应用的根目录下,有如下文件和内容:
```
resources/html/hello.html (下面是内容)
<html>
<head>
<meta charset="utf-8">
</head>
<body>
框架文档内容太多了,写不完!!!
</body>
</html>
```
然后在 `global.php` 配置文件中静态文件服务器参数为:
```php
/** 静态文件访问 */
$config['static_file_server'] = [
'status' => true,
'document_root' => realpath(__DIR__ . "/../") . '/resources/html',
'document_index' => [
'index.html'
]
];
```
最终,我们通过 `vendor/bin/start server` 等方式,启动框架后,浏览器访问 `http://127.0.0.1:20001/hello.html` 即可获取内容。
### 配置局部静态文件服务器
所涉及的类的命名空间:`use ZM\Http\StaticFileHandler;`
局部静态文件服务器一般用于,比如机器人要发送图片,或者给其他 HTTP 服务提供文件下载的接口时可用。我们假设写了一个图片收集的一个静态文件夹区域,将其中一个子路由当作图片静态目录:
```php
/**
* @RequestMapping("/images/{filename}")
* @param $param
* @return StaticFileHandler
*/
public function staticImage($param) {
Console::info("[下载图片] " . $param["filename"]);
return new StaticFileHandler($param["filename"], "/path/to/your/image_dir/");
}
```
这样当用户访问 `http://框架地址/images/aaa.jpg` 就可以快速地调用此路由下的局部文件服务器功能了。

View File

@@ -78,6 +78,13 @@ setTimeout(() => {
' <img class="doc-chat-avatar" src="https://docs-v1.zhamao.xin/logo.png" alt=""/>\n' +
' <div class="doc-chat-box doc-chat-box-robot">' + j.substr(2) + '</div>\n' +
' </div>';
} else if (j.substr(0, 2) === '^ ') {
final += '<div class="doc-chat-row doc-chat-banner">' + j.substr(2) + '</div>';
} else if (j.substr(0, 2) === '[ ') {
final += '<div class="doc-chat-row doc-chat-row-robot">\n' +
' <img class="doc-chat-avatar" src="https://docs-v1.zhamao.xin/logo.png" alt=""/>\n' +
' <div class="doc-chat-box doc-chat-box-robot"><img src="' + j.substr(2) + '" alt=""/></div>\n' +
' </div>';
}
}
i.innerHTML = final;

View File

@@ -1,3 +1,26 @@
# 更新日志v2 版本)
> 暂未发布正式版。
## v2.0.3
> 更新事件2020.12.31
- 修复CQBefore 注解事件在 level 低于 200 时无法调用的 bug
- 修复CQMetaEvent 注解事件调用时报错的 bug
## v2.0.2
> 更新时间2020.12.31
- 更新:将 CQ 码调用类更新到与最新 OneBot 标准相兼容的状态
## v2.0.1
> 更新时间2020.12.23
- 修复:开屏报错文件夹不存在
## v2.0
> 更新时间2020.12.23
已发布正式版。

View File

@@ -2,7 +2,7 @@ site_name: 炸毛框架 v2
repo_name: '炸毛框架'
repo_url: 'https://github.com/zhamao-robot/zhamao-framework'
edit_uri: 'blob/2.0-dev/docs/'
edit_uri: 'blob/master/docs/'
theme:
name: material
@@ -61,13 +61,22 @@ nav:
- 注册事件响应: guide/注册事件响应.md
- 事件和注解:
- 事件和注解: event/index.md
- 机器人注解事件: event/机器人注解事件.md
- HTTP 路由注解事件: event/路由注解事件.md
- 框架核心注解事件: event/框架注解事件.md
- 中间件注解: event/中间件.md
- 自定义注解: event/自定义注解.md
- 事件分发器: event/事件分发器.md
- 框架组件:
- 框架组件: component/index.md
- 机器人 API: component/机器人API.md
- CQ 码(多媒体消息): component/CQ码.md
- 上下文: component/上下文.md
- 进阶开发:
- 进阶开发: advanced/index.md
- 从 v1 升级: advanced/to-v2.md
- FAQ:
- FAQ: FAQ.md
- 内部类文件手册: advanced/inside-class.md
- FAQ: FAQ.md
- 更新日志:
- 更新日志v2: update/v2.md
- 更新日志v1: update/v1.md

View File

@@ -34,70 +34,59 @@ class CQ
return " ";
}
/**
* 发送emoji表情
* @param $id
* @return string
*/
public static function emoji($id) {
if (is_numeric($id)) {
return "[CQ:emoji,id=" . $id . "]";
}
Console::warning("传入的emoji id($id)错误!");
return " ";
}
/**
* 发送原创表情存放在酷Q目录的data/bface/下
* @param $id
* @return string
*/
public static function bface($id) {
return "[CQ:bface,id=" . $id . "]";
}
/**
* 发送小表情
* @param $id
* @return string
*/
public static function sface($id) {
if (is_numeric($id)) {
return "[CQ:sface,id=" . $id . "]";
}
Console::warning("传入的sface id($id)错误!");
return " ";
}
/**
* 发送图片
* cache为<FALSE>时禁用CQ-HTTP-API插件的缓存
* @param $file
* @param bool $cache
* @param bool $flash
* @param bool $proxy
* @param int $timeout
* @return string
*/
public static function image($file, $cache = true) {
if ($cache === false)
return "[CQ:image,file=" . $file . ",cache=0]";
else
return "[CQ:image,file=" . $file . "]";
public static function image($file, $cache = true, $flash = false, $proxy = true, $timeout = -1) {
return
"[CQ:image,file=" . $file .
(!$cache ? ",cache=0" : "") .
($flash ? ",type=flash" : "") .
(!$proxy ? ",proxy=false" : "") .
($timeout != -1 ? (",timeout=" . $timeout) : "") .
"]";
}
/**
* 发送语音
* cache为<FALSE>时禁用CQ-HTTP-API插件的缓存
* magic为<TRUE>时标记为变声
* @param $file
* @param bool $magic
* @param bool $cache
* @param bool $proxy
* @param int $timeout
* @return string
*/
public static function record($file, $magic = false, $cache = true) {
if ($cache === false) $c = ",cache=0";
else $c = "";
if ($magic === true) $m = ",magic=true";
else $m = "";
return "[CQ:record,file=" . $file . $c . $m . "]";
public static function record($file, $magic = false, $cache = true, $proxy = true, $timeout = -1) {
return
"[CQ:record,file=" . $file .
(!$cache ? ",cache=0" : "") .
($magic ? ",magic=1" : "") .
(!$proxy ? ",proxy=false" : "") .
($timeout != -1 ? (",timeout=" . $timeout) : "") .
"]";
}
/**
* 发送短视频
* @param $file
* @param bool $cache
* @param bool $proxy
* @param int $timeout
* @return string
*/
public static function video($file, $cache = true, $proxy = true, $timeout = -1) {
return
"[CQ:video,file=" . $file .
(!$cache ? ",cache=0" : "") .
(!$proxy ? ",proxy=false" : "") .
($timeout != -1 ? (",timeout=" . $timeout) : "") .
"]";
}
/**
@@ -124,6 +113,56 @@ class CQ
return "[CQ:shake]";
}
/**
* 发送新的戳一戳
* @param $type
* @param $id
* @param string $name
* @return string
*/
public static function poke($type, $id, $name = "") {
return "[CQ:poke,type=$type,id=$id" . ($name != "" ? ",name=$name" : "") . "]";
}
/**
* 发送匿名消息
* @param int $ignore
* @return string
*/
public static function anonymous($ignore = 1) {
return "[CQ:anonymous".($ignore != 1 ? ",ignore=0" : "")."]";
}
/**
* 发送链接分享(只能在单条回复中单独使用)
* @param $url
* @param $title
* @param null $content
* @param null $image
* @return string
*/
public static function share($url, $title, $content = null, $image = null) {
if ($content === null) $c = "";
else $c = ",content=" . $content;
if ($image === null) $i = "";
else $i = ",image=" . $image;
return "[CQ:share,url=" . $url . ",title=" . $title . $c . $i . "]";
}
/**
* 发送好友或群推荐名片
* @param $type
* @param $id
* @return string
*/
public static function contact($type, $id) {
return "[CQ:contact,type=$type,id=$id]";
}
public static function location($lat, $lon, $title = "", $content = "") {
}
/**
* 发送音乐分享(只能在单条回复中单独使用)
* qq、163、xiami为内置分享需要先通过搜索功能获取id后使用
@@ -136,10 +175,10 @@ class CQ
* $image 为音乐卡片的图片链接地址(可忽略)
* @param $type
* @param $id_or_url
* @param string $audio
* @param string $title
* @param string $content
* @param string $image
* @param null $audio
* @param null $title
* @param null $content
* @param null $image
* @return string
*/
public static function music($type, $id_or_url, $audio = null, $title = null, $content = null, $image = null) {
@@ -164,20 +203,12 @@ class CQ
}
}
/**
* 发送链接分享(只能在单条回复中单独使用)
* @param $url
* @param $title
* @param null $content
* @param null $image
* @return string
*/
public static function share($url, $title, $content = null, $image = null) {
if ($content === null) $c = "";
else $c = ",content=" . $content;
if ($image === null) $i = "";
else $i = ",image=" . $image;
return "[CQ:share,url=" . $url . ",title=" . $title . $c . $i . "]";
public static function forward($id) {
return "[CQ:forward,id=$id]";
}
public static function node($user_id, $nickname, $content) {
return "[CQ:node,user_id=$user_id,nickname=$nickname,content=".self::escape($content)."]";
}
/**

View File

@@ -21,8 +21,6 @@ class CQMetaEvent extends AnnotationBase implements Level
* @Required()
*/
public $meta_event_type = '';
/** @var string */
public $sub_type = '';
/** @var int */
public $level;

View File

@@ -369,7 +369,7 @@ class ServerEventHandler
ManagerGM::pushConnect($request->fd, $type_conn);
$conn = ManagerGM::get($request->fd);
set_coroutine_params(["server" => $server, "request" => $request, "connection" => $conn, "fd" => $request->fd]);
$conn->setOption("connect_id", strval($request->header["x-self-id"]) ?? "");
$conn->setOption("connect_id", strval($request->header["x-self-id"] ?? ""));
$dispatcher = new EventDispatcher(OnSwooleEvent::class);
$dispatcher->setRuleFunction(function ($v) {

View File

@@ -54,6 +54,10 @@ class Framework
}
ZMAtomic::init();
try {
$sw = ZMConfig::get("global");
if(!is_dir($sw["zm_data"])) mkdir($sw["zm_data"]);
if(!is_dir($sw["config_dir"])) mkdir($sw["config_dir"]);
if(!is_dir($sw["crash_dir"])) mkdir($sw["crash_dir"]);
ManagerGM::init(ZMConfig::get("global", "swoole")["max_connection"] ?? 2048, 0.5, [
[
"key" => "connect_id",

View File

@@ -52,9 +52,7 @@ class QQBot
public function dispatchBeforeEvents($data) {
$before = new EventDispatcher(CQBefore::class);
$before->setRuleFunction(function ($v) use ($data) {
if ($v->level < 200) EventDispatcher::interrupt();
elseif ($v->cq_event != $data["post_type"]) return false;
return true;
return $v->cq_event == $data["post_type"];
});
$before->setReturnFunction(function ($result) {
if (!$result) EventDispatcher::interrupt();
@@ -137,8 +135,7 @@ class QQBot
//Console::success("当前数据包:".json_encode(ctx()->getData()));
$dispatcher = new EventDispatcher(CQMetaEvent::class);
$dispatcher->setRuleFunction(function (CQMetaEvent $v) {
return ($v->meta_event_type == '' || ($v->meta_event_type != '' && $v->meta_event_type == ctx()->getData()["meta_event_type"])) &&
($v->sub_type == '' || ($v->sub_type != '' && $v->sub_type == (ctx()->getData()["sub_type"] ?? '')));
return ($v->meta_event_type == '' || ($v->meta_event_type != '' && $v->meta_event_type == ctx()->getData()["meta_event_type"]));
});
//eval(BP);
$dispatcher->dispatchEvents(ctx()->getData());

View File

@@ -288,6 +288,16 @@ function bot() {
}
}
/**
* 获取同类型所有连接的文件描述符 ID
* @param string $type
* @return array
* @author 854854321
*/
function getAllFdByConnectType(string $type = 'default'): array {
$fds = [];
foreach (ManagerGM::getAllByName($type) as $obj) {
$fds[] = $obj->getFd();
}
return $fds;
}