diff --git a/.github/workflows/build.yml b/.github/workflows/increment-build-number.yml
similarity index 93%
rename from .github/workflows/build.yml
rename to .github/workflows/increment-build-number.yml
index 407bbfcc..7379b468 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/increment-build-number.yml
@@ -1,4 +1,4 @@
-name: Build
+name: Increment Build Number
on:
pull_request:
@@ -27,4 +27,4 @@ jobs:
git config --global user.email 'noreply@github.com'
git add src/ZM/ConsoleApplication.php
git commit -m "increment build number"
- git push
\ No newline at end of file
+ git push
diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml
index 004c2471..d1be2dba 100644
--- a/.github/workflows/integration-test.yml
+++ b/.github/workflows/integration-test.yml
@@ -1,4 +1,4 @@
-name: Integration Test
+name: Integration and Style Test
on:
push:
@@ -37,6 +37,8 @@ jobs:
with:
php-version: ${{ matrix.php-versions }}
extensions: swoole, posix, json
+ env:
+ SWOOLE_CONFIGURE_OPTS: --enable-openssl
- name: Setup problem matchers for PHP
run: echo "::add-matcher::${{ runner.tool_cache }}/php.json"
@@ -61,5 +63,41 @@ jobs:
- name: Run Static Analysis
run: "composer analyse"
+ - name: Run PHPUnit
+ run: "composer test"
+
+ cs-check:
+ name: PHP CS Fixer Check
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ - name: Setup PHP 7.2
+ uses: "shivammathur/setup-php@v2"
+ with:
+ php-version: "7.2"
+ extensions: swoole, posix, json
+
+ - name: Setup problem matchers for PHP
+ run: echo "::add-matcher::${{ runner.tool_cache }}/php.json"
+
+ - name: Validate composer.json
+ run: "composer validate --strict"
+
+ - name: Get composer cache directory
+ id: composer-cache
+ run: echo "::set-output name=dir::$(composer config cache-files-dir)"
+
+ - name: Cache dependencies
+ uses: actions/cache@v2
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
+ restore-keys: ${{ runner.os }}-composer-
+
+ - name: Install Composer Dependencies
+ run: "composer install --prefer-dist --no-progress --optimize-autoloader"
+
- name: Run PHP CS Fixer Check
- run: "./vendor/bin/php-cs-fixer fix --dry-run --diff"
+ run: "composer cs-fix -- --dry-run --diff"
diff --git a/phpstan.neon b/phpstan.neon
index dcf8101f..d2324dce 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -5,3 +5,4 @@ parameters:
- ./src/
ignoreErrors:
- '#Used constant OS_TYPE_(LINUX|WINDOWS) not found#'
+ - '#Constant .* not found#'
diff --git a/src/ZM/API/CQ.php b/src/ZM/API/CQ.php
index 89b64166..43190bba 100644
--- a/src/ZM/API/CQ.php
+++ b/src/ZM/API/CQ.php
@@ -11,10 +11,10 @@ class CQ
{
/**
* at一下QQ用户(仅在QQ群支持at全体)
- * @param $qq
- * @return string
+ * @param int|string $qq 用户QQ号/ID号
+ * @return string CQ码
*/
- public static function at($qq)
+ public static function at($qq): string
{
if (is_numeric($qq) || $qq === 'all') {
return '[CQ:at,qq=' . $qq . ']';
@@ -25,10 +25,10 @@ class CQ
/**
* 发送QQ原生表情
- * @param $id
- * @return string
+ * @param int|string $id 表情ID
+ * @return string CQ码
*/
- public static function face($id)
+ public static function face($id): string
{
if (is_numeric($id)) {
return '[CQ:face,id=' . $id . ']';
@@ -39,10 +39,14 @@ class CQ
/**
* 发送图片
- * @param $file
- * @return string
+ * @param string $file 文件的路径、URL或者base64编码的图片数据
+ * @param bool $cache 是否缓存(默认为true)
+ * @param bool $flash 是否闪照(默认为false)
+ * @param bool $proxy 是否使用代理(默认为true)
+ * @param int $timeout 超时时间(默认不超时)
+ * @return string CQ码
*/
- public static function image($file, bool $cache = true, bool $flash = false, bool $proxy = true, int $timeout = -1)
+ public static function image(string $file, bool $cache = true, bool $flash = false, bool $proxy = true, int $timeout = -1): string
{
return
'[CQ:image,file=' . self::encode($file, true) .
@@ -55,15 +59,19 @@ class CQ
/**
* 发送语音
- * @param $file
- * @return string
+ * @param string $file 文件的路径、URL或者base64编码的语音数据
+ * @param bool $magic 是否加特技(默认为false)
+ * @param bool $cache 是否缓存(默认为true)
+ * @param bool $proxy 是否使用代理(默认为true)
+ * @param int $timeout 超时时间(默认不超时)
+ * @return string CQ码
*/
- public static function record($file, bool $magic = false, bool $cache = true, bool $proxy = true, int $timeout = -1)
+ public static function record(string $file, bool $magic = false, bool $cache = true, bool $proxy = true, int $timeout = -1): string
{
return
'[CQ:record,file=' . self::encode($file, true) .
- (!$cache ? ',cache=0' : '') .
($magic ? ',magic=1' : '') .
+ (!$cache ? ',cache=0' : '') .
(!$proxy ? ',proxy=false' : '') .
($timeout != -1 ? (',timeout=' . $timeout) : '') .
']';
@@ -71,10 +79,13 @@ class CQ
/**
* 发送短视频
- * @param $file
- * @return string
+ * @param string $file 文件的路径、URL或者base64编码的短视频数据
+ * @param bool $cache 是否缓存(默认为true)
+ * @param bool $proxy 是否使用代理(默认为true)
+ * @param int $timeout 超时时间(默认不超时)
+ * @return string CQ码
*/
- public static function video($file, bool $cache = true, bool $proxy = true, int $timeout = -1)
+ public static function video(string $file, bool $cache = true, bool $proxy = true, int $timeout = -1): string
{
return
'[CQ:video,file=' . self::encode($file, true) .
@@ -86,60 +97,62 @@ class CQ
/**
* 发送投掷骰子(只能在单条回复中单独使用)
- * @return string
+ * @return string CQ码
*/
- public static function rps()
+ public static function rps(): string
{
return '[CQ:rps]';
}
/**
* 发送掷骰子表情(只能在单条回复中单独使用)
- * @return string
+ * @return string CQ码
*/
- public static function dice()
+ public static function dice(): string
{
return '[CQ:dice]';
}
/**
* 戳一戳(原窗口抖动,仅支持好友消息使用)
- * @return string
+ * @return string CQ码
*/
- public static function shake()
+ public static function shake(): string
{
return '[CQ:shake]';
}
/**
* 发送新的戳一戳
- * @param $type
- * @param $id
- * @return string
+ * @param int|string $type 焯一戳类型
+ * @param int|string $id 戳一戳ID号
+ * @param string $name 戳一戳名称(可选)
+ * @return string CQ码
*/
- public static function poke($type, $id, string $name = '')
+ public static function poke($type, $id, string $name = ''): string
{
return "[CQ:poke,type={$type},id={$id}" . ($name != '' ? (',name=' . self::encode($name, true)) : '') . ']';
}
/**
* 发送匿名消息
- * @return string
+ * @param int $ignore 是否忽略错误(默认为1,0表示不忽略错误)
+ * @return string CQ码
*/
- public static function anonymous(int $ignore = 1)
+ public static function anonymous(int $ignore = 1): string
{
return '[CQ:anonymous' . ($ignore != 1 ? ',ignore=0' : '') . ']';
}
/**
* 发送链接分享(只能在单条回复中单独使用)
- * @param $url
- * @param $title
- * @param null $content
- * @param null $image
- * @return string
+ * @param string $url 分享地址
+ * @param string $title 标题
+ * @param null|string $content 卡片内容(可选)
+ * @param null|string $image 卡片图片(可选)
+ * @return string CQ码
*/
- public static function share($url, $title, $content = null, $image = null)
+ public static function share(string $url, string $title, ?string $content = null, ?string $image = null): string
{
if ($content === null) {
$c = '';
@@ -156,26 +169,28 @@ class CQ
/**
* 发送好友或群推荐名片
- * @param $type
- * @param $id
- * @return string
+ * @param string $type 名片类型
+ * @param int|string $id 好友或群ID
+ * @return string CQ码
*/
- public static function contact($type, $id)
+ public static function contact(string $type, $id): string
{
return "[CQ:contact,type={$type},id={$id}]";
}
/**
* 发送位置
- * @param $lat
- * @param $lon
- * @return string
+ * @param float|string $lat 纬度
+ * @param float|string $lon 经度
+ * @param string $title 标题(可选)
+ * @param string $content 卡片内容(可选)
+ * @return string CQ码
*/
- public static function location($lat, $lon, string $title = '', string $content = '')
+ public static function location($lat, $lon, string $title = '', string $content = ''): string
{
return '[CQ:location' .
- ',lat=' . self::encode($lat, true) .
- ',lon=' . self::encode($lon, true) .
+ ',lat=' . self::encode((string) $lat, true) .
+ ',lon=' . self::encode((string) $lon, true) .
($title != '' ? (',title=' . self::encode($title, true)) : '') .
($content != '' ? (',content=' . self::encode($content, true)) : '') .
']';
@@ -183,23 +198,18 @@ class CQ
/**
* 发送音乐分享(只能在单条回复中单独使用)
+ *
* qq、163、xiami为内置分享,需要先通过搜索功能获取id后使用
- * custom为自定义分享
- * 当为自定义分享时:
- * $id_or_url 为音乐卡片点进去打开的链接(一般是音乐介绍界面啦什么的)
- * $audio 为音乐(如mp3文件)的HTTP链接地址(不可为空)
- * $title 为音乐卡片的标题,建议12字以内(不可为空)
- * $content 为音乐卡片的简介(可忽略)
- * $image 为音乐卡片的图片链接地址(可忽略)
- * @param $type
- * @param $id_or_url
- * @param null $audio
- * @param null $title
- * @param null $content
- * @param null $image
- * @return string
+ *
+ * @param string $type 分享类型(仅限 `qq`、`163`、`xiami` 或 `custom`)
+ * @param int|string $id_or_url 当分享类型不是 `custom` 时,表示的是分享音乐的ID(需要先通过搜索功能获取id后使用),反之表示的是音乐卡片点入的链接
+ * @param null|string $audio 当分享类型是 `custom` 时,表示为音乐(如mp3文件)的HTTP链接地址(不可为空)
+ * @param null|string $title 当分享类型是 `custom` 时,表示为音乐卡片的标题,建议12字以内(不可为空)
+ * @param null|string $content 当分享类型是 `custom` 时,表示为音乐卡片的简介(可忽略)
+ * @param null|string $image 当分享类型是 `custom` 时,表示为音乐卡片的图片链接地址(可忽略)
+ * @return string CQ码
*/
- public static function music($type, $id_or_url, $audio = null, $title = null, $content = null, $image = null)
+ public static function music(string $type, $id_or_url, ?string $audio = null, ?string $title = null, ?string $content = null, ?string $image = null): string
{
switch ($type) {
case 'qq':
@@ -231,27 +241,60 @@ class CQ
}
}
- public static function forward($id)
+ /**
+ * 合并转发消息
+ * @param int|string $id 合并转发ID, 需要通过 `/get_forward_msg` API获取转发的具体内容
+ * @return string CQ码
+ */
+ public static function forward($id): string
{
- return '[CQ:forward,id=' . self::encode($id) . ']';
+ return '[CQ:forward,id=' . self::encode((string) $id) . ']';
}
- public static function node($user_id, $nickname, $content)
+ /**
+ * 合并转发消息节点
+ * 特殊说明: 需要使用单独的API /send_group_forward_msg 发送, 并且由于消息段较为复杂, 仅支持Array形式入参。
+ * 如果引用消息和自定义消息同时出现, 实际查看顺序将取消息段顺序。
+ * 另外按 CQHTTP 文档说明, data 应全为字符串, 但由于需要接收message 类型的消息, 所以 仅限此Type的content字段 支持Array套娃
+ * @deprecated 这个不推荐使用,因为 go-cqhttp 官方没有对其提供CQ码模式相关支持,仅支持Array模式发送
+ * @param int|string $user_id 转发消息id
+ * @param string $nickname 发送者显示名字
+ * @param string $content 具体消息
+ * @return string CQ码
+ */
+ public static function node($user_id, string $nickname, string $content): string
{
return "[CQ:node,user_id={$user_id},nickname=" . self::encode($nickname, true) . ',content=' . self::encode($content, true) . ']';
}
- public static function xml($data)
+ /**
+ * XML消息
+ * @param string $data xml内容, xml中的value部分
+ * @return string CQ码
+ */
+ public static function xml(string $data): string
{
return '[CQ:xml,data=' . self::encode($data, true) . ']';
}
- public static function json($data, $resid = 0)
+ /**
+ * JSON消息
+ * @param string $data json内容
+ * @param int $resid 0为走小程序通道,其他值为富文本通道(默认为0)
+ * @return string CQ码
+ */
+ public static function json(string $data, int $resid = 0): string
{
- return '[CQ:json,data=' . self::encode($data, true) . ',resid=' . intval($resid) . ']';
+ return '[CQ:json,data=' . self::encode($data, true) . ',resid=' . $resid . ']';
}
- public static function _custom(string $type_name, $params)
+ /**
+ * 返回一个自定义扩展的CQ码(支持自定义类型和参数)
+ * @param string $type_name CQ码类型名称
+ * @param array $params 参数
+ * @return string CQ码
+ */
+ public static function _custom(string $type_name, array $params): string
{
$code = '[CQ:' . $type_name;
foreach ($params as $k => $v) {
@@ -263,10 +306,11 @@ class CQ
/**
* 反转义字符串中的CQ码敏感符号
- * @param mixed $msg
- * @param mixed $is_content
+ * @param string $msg 字符串
+ * @param bool $is_content 如果是解码CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true
+ * @return string 转义后的CQ码
*/
- public static function decode($msg, $is_content = false)
+ public static function decode(string $msg, bool $is_content = false): string
{
$msg = str_replace(['&', '[', ']'], ['&', '[', ']'], $msg);
if ($is_content) {
@@ -275,7 +319,12 @@ class CQ
return $msg;
}
- public static function replace($str)
+ /**
+ * 简单反转义替换CQ码的方括号
+ * @param string $str 字符串
+ * @return string 字符串
+ */
+ public static function replace(string $str): string
{
$str = str_replace('{{', '[', $str);
return str_replace('}}', ']', $str);
@@ -283,10 +332,11 @@ class CQ
/**
* 转义CQ码的特殊字符,同encode
- * @param mixed $msg
- * @param mixed $is_content
+ * @param string $msg 字符串
+ * @param bool $is_content 如果是转义CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true
+ * @return string 转义后的CQ码
*/
- public static function escape($msg, $is_content = false)
+ public static function escape(string $msg, bool $is_content = false): string
{
$msg = str_replace(['&', '[', ']'], ['&', '[', ']'], $msg);
if ($is_content) {
@@ -297,10 +347,11 @@ class CQ
/**
* 转义CQ码的特殊字符
- * @param mixed $msg
- * @param mixed $is_content
+ * @param string $msg 字符串
+ * @param bool $is_content 如果是转义CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true
+ * @return string 转义后的CQ码
*/
- public static function encode($msg, $is_content = false)
+ public static function encode(string $msg, bool $is_content = false): string
{
$msg = str_replace(['&', '[', ']'], ['&', '[', ']'], $msg);
if ($is_content) {
@@ -311,10 +362,10 @@ class CQ
/**
* 移除消息中所有的CQ码并返回移除CQ码后的消息
- * @param $msg
- * @return string
+ * @param string $msg 消息
+ * @return string 消息内容
*/
- public static function removeCQ($msg)
+ public static function removeCQ(string $msg): string
{
$final = '';
$last_end = 0;
@@ -328,10 +379,11 @@ class CQ
/**
* 获取消息中第一个CQ码
- * @param mixed $msg
- * @param mixed $is_object
+ * @param string $msg 消息内容
+ * @param bool $is_object 是否以对象形式返回,如果为False的话,返回数组形式(默认为false)
+ * @return array|CQObject 返回的CQ码(数组或对象)
*/
- public static function getCQ($msg, $is_object = false)
+ public static function getCQ(string $msg, bool $is_object = false)
{
if (($head = mb_strpos($msg, '[CQ:')) !== false) {
$key_offset = mb_substr($msg, $head);
@@ -356,10 +408,11 @@ class CQ
/**
* 获取消息中所有的CQ码
- * @param mixed $msg
- * @param mixed $is_object
+ * @param string $msg 消息内容
+ * @param bool $is_object 是否以对象形式返回,如果为False的话,返回数组形式(默认为false)
+ * @return array|CQObject[] 返回的CQ码们(数组或对象)
*/
- public static function getAllCQ($msg, $is_object = false)
+ public static function getAllCQ(string $msg, bool $is_object = false): array
{
$cqs = [];
$offset = 0;
diff --git a/src/ZM/API/CQAPI.php b/src/ZM/API/CQAPI.php
index 045ceb0e..3e1d20be 100644
--- a/src/ZM/API/CQAPI.php
+++ b/src/ZM/API/CQAPI.php
@@ -13,7 +13,7 @@ use ZM\Utils\CoMessage;
trait CQAPI
{
- /** @var null|Closure */
+ /** @var null|Closure 过滤用的 */
private static $filter;
public function __call($name, $arguments)
@@ -33,12 +33,6 @@ trait CQAPI
return $func_name . $postfix;
}
- /**
- * @param $connection
- * @param $reply
- * @param |null $function
- * @return array|bool
- */
private function processAPI($connection, $reply, $function = null)
{
if (is_callable(self::$filter)) {
@@ -57,7 +51,7 @@ trait CQAPI
private function processWebsocketAPI($connection, $reply, $function = false)
{
- $api_id = ZMAtomic::get('wait_msg_id')->add(1);
+ $api_id = ZMAtomic::get('wait_msg_id')->add();
$reply['echo'] = $api_id;
if (server()->push($connection->getFd(), json_encode($reply))) {
if ($function === true) {
@@ -89,14 +83,10 @@ trait CQAPI
return false;
}
- /**
- * @param $connection
- * @param $reply
- * @param null $function
- * @noinspection PhpUnusedParameterInspection
- */
private function processHttpAPI($connection, $reply, $function = null): bool
{
+ unset($connection, $reply, $function);
+ // TODO: HTTP方式处理API的代码还没写
return false;
}
}
diff --git a/src/ZM/API/GoCqhttpAPIV11.php b/src/ZM/API/GoCqhttpAPIV11.php
index e6e0c87f..fc1743af 100644
--- a/src/ZM/API/GoCqhttpAPIV11.php
+++ b/src/ZM/API/GoCqhttpAPIV11.php
@@ -27,7 +27,7 @@ class GoCqhttpAPIV11
* 获取频道系统内BOT的资料
* 响应字段:nickname, tiny_id, avatar_url
* @see https://github.com/Mrs4s/go-cqhttp/blob/master/docs/guild.md#%E8%8E%B7%E5%8F%96%E9%A2%91%E9%81%93%E7%B3%BB%E7%BB%9F%E5%86%85bot%E7%9A%84%E8%B5%84%E6%96%99
- * @return array|bool
+ * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool)
*/
public function getGuildServiceProfile()
{
@@ -39,7 +39,7 @@ class GoCqhttpAPIV11
/**
* 获取频道列表
* @see https://github.com/Mrs4s/go-cqhttp/blob/master/docs/guild.md#%E8%8E%B7%E5%8F%96%E9%A2%91%E9%81%93%E5%88%97%E8%A1%A8
- * @return array|bool
+ * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool)
*/
public function getGuildList()
{
@@ -51,8 +51,8 @@ class GoCqhttpAPIV11
/**
* 通过访客获取频道元数据
* @see https://github.com/Mrs4s/go-cqhttp/blob/master/docs/guild.md#%E9%80%9A%E8%BF%87%E8%AE%BF%E5%AE%A2%E8%8E%B7%E5%8F%96%E9%A2%91%E9%81%93%E5%85%83%E6%95%B0%E6%8D%AE
- * @param $guild_id
- * @return array|bool
+ * @param int|string $guild_id 频道ID
+ * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool)
*/
public function getGuildMetaByGuest($guild_id)
{
@@ -67,9 +67,9 @@ class GoCqhttpAPIV11
/**
* 获取子频道列表
* @see https://github.com/Mrs4s/go-cqhttp/blob/master/docs/guild.md#%E8%8E%B7%E5%8F%96%E5%AD%90%E9%A2%91%E9%81%93%E5%88%97%E8%A1%A8
- * @param $guild_id
- * @param false $no_cache
- * @return array|bool
+ * @param int|string $guild_id 频道ID
+ * @param false $no_cache 禁用缓存(默认为false)
+ * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool)
*/
public function getGuildChannelList($guild_id, bool $no_cache = false)
{
@@ -85,8 +85,8 @@ class GoCqhttpAPIV11
/**
* 获取频道成员列表
* @see https://github.com/Mrs4s/go-cqhttp/blob/master/docs/guild.md#%E8%8E%B7%E5%8F%96%E9%A2%91%E9%81%93%E6%88%90%E5%91%98%E5%88%97%E8%A1%A8
- * @param $guild_id
- * @return array|bool
+ * @param int|string $guild_id 频道ID
+ * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool)
*/
public function getGuildMembers($guild_id)
{
@@ -101,12 +101,12 @@ class GoCqhttpAPIV11
/**
* 发送信息到子频道
* @see https://github.com/Mrs4s/go-cqhttp/blob/master/docs/guild.md#%E5%8F%91%E9%80%81%E4%BF%A1%E6%81%AF%E5%88%B0%E5%AD%90%E9%A2%91%E9%81%93
- * @param $guild_id
- * @param $channel_id
- * @param $message
- * @return array|bool
+ * @param int|string $guild_id 频道ID
+ * @param int|string $channel_id 子频道ID
+ * @param string $message 信息内容
+ * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool)
*/
- public function sendGuildChannelMsg($guild_id, $channel_id, $message)
+ public function sendGuildChannelMsg($guild_id, $channel_id, string $message)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName($this->prefix, __FUNCTION__),
diff --git a/tests/ZM/API/CQTest.php b/tests/ZM/API/CQTest.php
new file mode 100644
index 00000000..89afcdde
--- /dev/null
+++ b/tests/ZM/API/CQTest.php
@@ -0,0 +1,279 @@
+assertEquals($expected, CQ::share($url, $title, $content, $image));
+ }
+
+ public function providerShare(): array
+ {
+ return [
+ 'all' => [
+ 'https://www.baidu.com',
+ 'hello',
+ 'world',
+ 'https://www.baidu.com/img/bd_logo1.png',
+ '[CQ:share,url=https://www.baidu.com,title=hello,content=world,image=https://www.baidu.com/img/bd_logo1.png]',
+ ],
+ 'url title' => [
+ 'https://www.baidu.com',
+ '123',
+ null,
+ null,
+ '[CQ:share,url=https://www.baidu.com,title=123]',
+ ],
+ 'url title content' => [
+ 'https://www.baidu.com',
+ '123',
+ '456',
+ null,
+ '[CQ:share,url=https://www.baidu.com,title=123,content=456]',
+ ],
+ ];
+ }
+
+ public function testShake()
+ {
+ $this->assertEquals('[CQ:shake]', CQ::shake());
+ }
+
+ public function testLocation()
+ {
+ $this->assertEquals('[CQ:location,lat=23.137466,lon=113.352425]', CQ::location(23.137466, 113.352425));
+ }
+
+ public function testVideo()
+ {
+ $this->assertEquals('[CQ:video,file=https://www.baidu.com,cache=0,proxy=false,timeout=20]', CQ::video('https://www.baidu.com', false, false, 20));
+ }
+
+ public function testContact()
+ {
+ $this->assertEquals('[CQ:contact,type=qq,id=123456789]', CQ::contact('qq', '123456789'));
+ }
+
+ public function testForward()
+ {
+ $this->assertEquals('[CQ:forward,id=123456789]', CQ::forward(123456789));
+ }
+
+ public function testAnonymous()
+ {
+ $this->assertEquals('[CQ:anonymous,ignore=0]', CQ::anonymous(0));
+ }
+
+ public function testCustom()
+ {
+ $this->assertEquals('[CQ:test,type=test,data=hello]', CQ::_custom('test', ['type' => 'test', 'data' => 'hello']));
+ }
+
+ public function testEscape()
+ {
+ $this->assertEquals('hello[],', CQ::escape('hello[],'));
+ }
+
+ /**
+ * @dataProvider providerMusic
+ * @param mixed $data
+ * @param mixed $expected
+ */
+ public function testMusic($data, $expected)
+ {
+ $this->assertEquals($expected, CQ::music(...$data));
+ }
+
+ public function providerMusic(): array
+ {
+ return [
+ 'music qq' => [
+ ['qq', '123456789'],
+ '[CQ:music,type=qq,id=123456789]',
+ ],
+ 'music 163' => [
+ ['163', '123456789'],
+ '[CQ:music,type=163,id=123456789]',
+ ],
+ 'music xiami' => [
+ ['xiami', '123456789'],
+ '[CQ:music,type=xiami,id=123456789]',
+ ],
+ 'music custom' => [
+ ['custom', '123456789'],
+ ' ',
+ ],
+ 'music custom url audio title' => [
+ ['custom', '123456789', 'test', 'test1'],
+ '[CQ:music,type=custom,url=123456789,audio=test,title=test1]',
+ ],
+ 'music custom url audio title content' => [
+ ['custom', '123456789', 'test', 'test1', 'test2'],
+ '[CQ:music,type=custom,url=123456789,audio=test,title=test1,content=test2]',
+ ],
+ 'music custom url audio title content image' => [
+ ['custom', '123456789', 'test', 'test1', 'test2', 'test3'],
+ '[CQ:music,type=custom,url=123456789,audio=test,title=test1,content=test2,image=test3]',
+ ],
+ 'music custom test' => [
+ ['custom test', '123456789', 'test', 'test1', 'test2', 'test3'],
+ ' ',
+ ],
+ ];
+ }
+
+ public function testPoke()
+ {
+ $this->assertEquals('[CQ:poke,type=id,id=123456789]', CQ::poke('id', '123456789'));
+ }
+
+ public function testJson()
+ {
+ $this->assertEquals('[CQ:json,data={"a":"b["},resid=1]', CQ::json(json_encode(['a' => 'b[']), 1));
+ }
+
+ public function testEncode()
+ {
+ $this->assertEquals('hello[],', CQ::encode('hello[],'));
+ }
+
+ public function testDice()
+ {
+ $this->assertEquals('[CQ:dice]', CQ::dice());
+ }
+
+ public function testRecord()
+ {
+ $this->assertEquals('[CQ:record,file=https://www.baidu.com,cache=0]', CQ::record('https://www.baidu.com', false, false));
+ }
+
+ public function testDecode()
+ {
+ $this->assertEquals('hello[],', CQ::decode('hello[],'));
+ }
+
+ /**
+ * @dataProvider providerRemoveCQ
+ * @param mixed $msg
+ * @param mixed $expected
+ */
+ public function testRemoveCQ($msg, $expected)
+ {
+ $this->assertEquals($expected, CQ::removeCQ($msg));
+ }
+
+ public function providerRemoveCQ(): array
+ {
+ return [
+ 'remove cq 1' => ['hello[CQ:at,qq=123456789],', 'hello,'],
+ 'remove cq 2' => ['hello[CQ:at,qq=123456789,', 'hello[CQ:at,qq=123456789,'],
+ 'remove cq 3' => ['[CQ:dice]hello[CQ:at,qq=123456789]', 'hello'],
+ ];
+ }
+
+ public function testGetAllCQ()
+ {
+ $array = CQ::getAllCQ('[CQ:dice][CQ:at,qq=123456789]');
+ $this->assertEquals([
+ [
+ 'type' => 'dice',
+ 'start' => 0,
+ 'end' => 8,
+ ],
+ [
+ 'type' => 'at',
+ 'params' => [
+ 'qq' => '123456789',
+ ],
+ 'start' => 9,
+ 'end' => 28,
+ ],
+ ], $array);
+ }
+
+ /**
+ * @dataProvider providerGetCQ
+ * @param mixed $cq
+ * @param mixed $expected
+ */
+ public function testGetCQ($cq, $expected)
+ {
+ $this->assertEquals($expected, CQ::getCQ($cq));
+ }
+
+ public function providerGetCQ(): array
+ {
+ return [
+ 'one dice' => ['[CQ:dice]', [
+ 'type' => 'dice',
+ 'start' => 0,
+ 'end' => 8,
+ ]],
+ 'one at' => ['[CQ:at,qq=123456789]', [
+ 'type' => 'at',
+ 'params' => [
+ 'qq' => '123456789',
+ ],
+ 'start' => 0,
+ 'end' => 19,
+ ]],
+ 'invalid cq 1' => ['[CQ:at,qq=123456789', null],
+ 'invalid cq 2' => ['[CQ;at,qq=123456789]', null],
+ ];
+ }
+
+ public function testImage()
+ {
+ $this->assertEquals('[CQ:image,file=https://www.baidu.com]', CQ::image('https://www.baidu.com'));
+ }
+
+ public function testRps()
+ {
+ $this->assertEquals('[CQ:rps]', CQ::rps());
+ }
+
+ public function testReplace()
+ {
+ $this->assertEquals('[]', CQ::replace('{{}}'));
+ }
+
+ public function testAt()
+ {
+ $this->assertEquals('[CQ:at,qq=123456789]', CQ::at('123456789'));
+ $this->assertEquals(' ', CQ::at(null));
+ }
+
+ public function testXml()
+ {
+ $this->assertEquals('[CQ:xml,data=]', CQ::xml(''));
+ }
+
+ public function testFace()
+ {
+ $this->assertEquals('[CQ:face,id=1]', CQ::face(1));
+ $this->assertEquals(' ', CQ::face(null));
+ }
+
+ public function testNode()
+ {
+ $this->assertEquals('[CQ:node,user_id=test,nickname=content,content=blah]', CQ::node('test', 'content', 'blah'));
+ }
+}
diff --git a/tests/ZM/Utils/MessageUtilTest.php b/tests/ZM/Utils/MessageUtilTest.php
index d51373c7..0b9f17a6 100644
--- a/tests/ZM/Utils/MessageUtilTest.php
+++ b/tests/ZM/Utils/MessageUtilTest.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Tests\ZM\Utils;
use PHPUnit\Framework\TestCase;
+use Throwable;
use ZM\Annotation\CQ\CQCommand;
use ZM\API\CQ;
use ZM\Event\EventManager;
@@ -157,16 +158,27 @@ class MessageUtilTest extends TestCase
];
}
+ /**
+ * @throws Throwable
+ */
public function testDownloadCQImage(): void
{
if (file_exists(DataProvider::getDataFolder('images') . '/test.jpg')) {
unlink(DataProvider::getDataFolder('images') . '/test.jpg');
}
$msg = '[CQ:image,file=test.jpg,url=https://zhamao.xin/file/hello.jpg]';
- $result = MessageUtil::downloadCQImage($msg);
- $this->assertIsArray($result);
- $this->assertCount(1, $result);
- $this->assertFileExists(DataProvider::getDataFolder('images') . '/test.jpg');
- unlink(DataProvider::getDataFolder('images') . '/test.jpg');
+
+ try {
+ $result = MessageUtil::downloadCQImage($msg);
+ $this->assertIsArray($result);
+ $this->assertCount(1, $result);
+ $this->assertFileExists(DataProvider::getDataFolder('images') . '/test.jpg');
+ unlink(DataProvider::getDataFolder('images') . '/test.jpg');
+ } catch (Throwable $e) {
+ if (strpos($e->getMessage(), 'enable-openssl') !== false) {
+ $this->markTestSkipped('OpenSSL is not enabled');
+ }
+ throw $e;
+ }
}
}