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; + } } }