├── .editorconfig ├── .gitattributes ├── .gitignore ├── .php_cs ├── LICENSE ├── QQfaceId.md ├── README.md ├── composer.json ├── phpunit.xml.dist ├── run.sh ├── src ├── CoolQ │ ├── CQ.php │ ├── QQ.php │ └── Url.php ├── Core │ ├── Blacklist.php │ ├── CQ.php │ ├── Exceptions │ │ └── Exception.php │ ├── Protocols │ │ ├── GuzzleProtocol.php │ │ ├── Protocol.php │ │ └── WebSocketProtocol.php │ ├── QQ.php │ ├── Response.php │ ├── Whitelist.php │ └── isCanSend.php ├── Light │ └── .gitkeep ├── functions.php └── functions_include.php └── tests ├── CoolQQTest.php └── GuzzleProtocolTest.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = false 10 | 11 | [*.{vue,js,scss}] 12 | charset = utf-8 13 | indent_style = space 14 | indent_size = 2 15 | end_of_line = lf 16 | insert_final_newline = true 17 | trim_trailing_whitespace = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | /vendor/ 3 | .idea 4 | composer.lock 5 | Resopnse.md 6 | /tests/put.json 7 | /tests/logs -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | 6 | 7 | This source file is subject to the MIT license that is bundled. 8 | EOF; 9 | 10 | return PhpCsFixer\Config::create() 11 | ->setRiskyAllowed(true) 12 | ->setRules(array( 13 | '@Y' => true, 14 | 'header_comment' => array('header' => $header), 15 | 'array_syntax' => array('syntax' => 'short'), 16 | 'ordered_imports' => true, 17 | 'no_useless_else' => true, 18 | 'no_useless_return' => true, 19 | 'php_unit_construct' => true, 20 | 'php_unit_strict' => true, 21 | )) 22 | ->setFinder( 23 | PhpCsFixer\Finder::create() 24 | ->exclude('vendor') 25 | ->in(__DIR__) 26 | ) 27 | ; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 kilingzhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /QQfaceId.md: -------------------------------------------------------------------------------- 1 | 0: 惊讶 2 | 1: 撇嘴 3 | 2: 色 4 | 3: 发呆 5 | 4: 得意 6 | 5: 流泪 7 | 6: 害羞 8 | 7: 闭嘴 9 | 8: 睡 10 | 9: 大哭 11 | 10: 尴尬 12 | 11: 发怒 13 | 12: 调皮 14 | 13: 呲牙 15 | 14: 微笑 16 | 15: 难过 17 | 16: 酷 18 | 18: 抓狂 19 | 19: 吐 20 | 20: 偷笑 21 | 21: 可爱 22 | 22: 白眼 23 | 23: 傲慢 24 | 24: 饥饿 25 | 25: 困 26 | 26: 惊恐 27 | 27: 流汗 28 | 28: 憨笑 29 | 29: 大兵 30 | 30: 奋斗 31 | 31: 咒骂 32 | 32: 疑问 33 | 33: 嘘 34 | 34: 晕 35 | 35: 折磨 36 | 36: 衰 37 | 37: 骷髅 38 | 38: 敲打 39 | 39: 再见 40 | 96: 冷汗 41 | 97: 擦汗 42 | 98: 抠鼻 43 | 99: 鼓掌 44 | 100: 糗大了 45 | 101: 坏笑 46 | 102: 左哼哼 47 | 103: 右哼哼 48 | 104: 哈欠 49 | 105: 鄙视 50 | 106: 委屈 51 | 107: 快哭了 52 | 108: 阴险 53 | 109: 亲亲 54 | 110: 吓 55 | 111: 可怜 56 | 172: 眨眼睛 57 | 182: 笑哭 58 | 179: doge 59 | 173: 泪奔 60 | 174: 无奈 61 | 212: 托腮 62 | 175: 卖萌 63 | 178: 斜眼笑 64 | 177: 喷血 65 | 180: 惊喜 66 | 181: 骚扰 67 | 176: 小纠结 68 | 183: 我最美 69 | 112: 菜刀 70 | 89: 西瓜 71 | 113: 啤酒 72 | 114: 篮球 73 | 115: 乒乓 74 | 171: 茶 75 | 60: 咖啡 76 | 61: 饭 77 | 46: 猪头 78 | 63: 玫瑰 79 | 64: 凋谢 80 | 116: 示爱 81 | 66: 爱心 82 | 67: 心碎 83 | 53: 蛋糕 84 | 54: 闪电 85 | 55: 炸弹 86 | 56: 刀 87 | 57: 足球 88 | 117: 瓢虫 89 | 59: 便便 90 | 75: 月亮 91 | 74: 太阳 92 | 69: 礼物 93 | 49: 拥抱 94 | 76: 强 95 | 77: 弱 96 | 78: 握手 97 | 79: 胜利 98 | 118: 抱拳 99 | 119: 勾引 100 | 120: 拳头 101 | 121: 差劲 102 | 122: 爱你 103 | 123: NO 104 | 124: OK 105 | 42: 爱情 106 | 85: 飞吻 107 | 43: 跳跳 108 | 41: 发抖 109 | 86: 怄火 110 | 125: 转圈 111 | 126: 磕头 112 | 127: 回头 113 | 128: 跳绳 114 | 129: 挥手 115 | 130: 激动 116 | 131: 街舞 117 | 132: 献吻 118 | 133: 左太极 119 | 134: 右太极 120 | 136: 双喜 121 | 137: 鞭炮 122 | 138: 灯笼 123 | 140: K歌 124 | 144: 喝彩 125 | 145: 祈祷 126 | 146: 爆筋 127 | 147: 棒棒糖 128 | 148: 喝奶 129 | 151: 飞机 130 | 158: 钞票 131 | 168: 药 132 | 169: 手枪 133 | 188: 蛋 134 | 192: 红包 135 | 184: 河蟹 136 | 185: 羊驼 137 | 190: 菊花 138 | 187: 幽灵 139 | 193: 大笑 140 | 194: 不开心 141 | 197: 冷漠 142 | 198: 呃 143 | 199: 好棒 144 | 200: 拜托 145 | 201: 点赞 146 | 202: 无聊 147 | 203: 托脸 148 | 204: 吃 149 | 205: 送花 150 | 206: 害怕 151 | 207: 花痴 152 | 208: 小样儿 153 | 210: 飙泪 154 | 211: 我不看 155 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

qq-php-sdk

2 | 3 |

A QQ PHP SDK.

4 | 5 | 6 | ## Installing 7 | 8 | ```shell 9 | $ composer require kilingzhang/qq-php-sdk -vvv 10 | ``` 11 | 12 | ## Usage 13 | 14 | TODO 15 | 16 | ## Contributing 17 | 18 | You can contribute in one of three ways: 19 | 20 | 1. File bug reports using the [issue tracker](https://github.com/kilingzhang/qq-php-sdk/issues). 21 | 2. Answer questions or fix bugs on the [issue tracker](https://github.com/kilingzhang/qq-php-sdk/issues). 22 | 3. Contribute new features or update the wiki. 23 | 24 | _The code contribution process is not very formal. You just need to make sure that you follow the PSR-0, PSR-1, and PSR-2 coding guidelines. Any new code contributions must be accompanied by unit tests where applicable._ 25 | 26 | ## License 27 | 28 | MIT 29 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kilingzhang/qq-php-sdk", 3 | "description": "qq-php-sdk", 4 | "keywords": [ 5 | "qq-php-sdk", 6 | "coolq-http-api", 7 | "coolq", 8 | "php", 9 | "library", 10 | "sdk" 11 | ], 12 | "homepage": "https://github.com/kilingzhang/qq-php-sdk", 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "kilingzhang", 17 | "email": "slight@kilingzhang.com", 18 | "homepage": "https://blog.kilingzhang.com/" 19 | } 20 | ], 21 | "require": { 22 | "php": ">=7.0", 23 | "guzzlehttp/guzzle": "^6.0" 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "^7.0", 27 | "mockery/mockery": "^1.0", 28 | "fzaninotto/faker": "^1.4" 29 | }, 30 | "autoload": { 31 | "files": [ 32 | "src/functions_include.php" 33 | ], 34 | "psr-4": { 35 | "Kilingzhang\\QQ\\": "src/" 36 | } 37 | }, 38 | "autoload-dev": { 39 | "psr-4": { 40 | "Kilingzhang\\Tests\\": "tests/" 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests/ 14 | 15 | 16 | 17 | 18 | src/ 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | php -S 0.0.0.0:8000 -t tests -------------------------------------------------------------------------------- /src/CoolQ/CQ.php: -------------------------------------------------------------------------------- 1 | $value) { 16 | $res[] = $key . '=' . $value; 17 | } 18 | $res = implode(',', $res); 19 | return "[{$res}]"; 20 | } 21 | 22 | public static function at(int $userId) 23 | { 24 | return self::CQ('at', [ 25 | 'qq' => $userId 26 | ]); 27 | } 28 | 29 | public static function atAll() 30 | { 31 | return self::CQ('at', [ 32 | 'qq' => 'all' 33 | ]); 34 | } 35 | 36 | public static function face(int $id) 37 | { 38 | return self::CQ('face', [ 39 | 'id' => $id 40 | ]); 41 | } 42 | 43 | public static function emoji(int $id) 44 | { 45 | return self::CQ('emoji', [ 46 | 'id' => $id 47 | ]); 48 | } 49 | 50 | public static function bFace(int $id) 51 | { 52 | return self::CQ('bface', [ 53 | 'id' => $id 54 | ]); 55 | } 56 | 57 | public static function sFace(int $id) 58 | { 59 | return self::CQ('sface', [ 60 | 'id' => $id 61 | ]); 62 | } 63 | 64 | public static function magic(string $magic) 65 | { 66 | return '[]'; 67 | } 68 | 69 | public static function image(string $url) 70 | { 71 | return self::CQ('image', [ 72 | 'file' => $url 73 | ]); 74 | } 75 | 76 | public static function flash(string $url) 77 | { 78 | return '[]'; 79 | } 80 | 81 | public static function record(string $url, bool $isMagic = false) 82 | { 83 | return self::CQ('record', [ 84 | 'file' => $url, 85 | 'magic' => $isMagic, 86 | ]); 87 | } 88 | 89 | public static function rps(string $type) 90 | { 91 | return self::CQ('rps', [ 92 | 'type' => $type 93 | ]); 94 | } 95 | 96 | public static function dice(string $type) 97 | { 98 | return self::CQ('dice', [ 99 | 'type' => $type 100 | ]); 101 | } 102 | 103 | public static function shake() 104 | { 105 | return self::CQ('shake', []); 106 | } 107 | 108 | public static function anonymouse(bool $ignore) 109 | { 110 | return self::CQ('anonymouse', $ignore ? [ 111 | 'ignore' => $ignore 112 | ] : []); 113 | } 114 | 115 | public static function music(string $type, int $id) 116 | { 117 | return self::CQ('music', [ 118 | 'type' => $type, 119 | 'id' => $id, 120 | ]); 121 | } 122 | 123 | public static function diyMusic(string $url, string $audio, string $title, string $content, string $image) 124 | { 125 | return self::CQ('music', [ 126 | 'type' => 'custom', 127 | 'audio' => $audio, 128 | 'title' => $title, 129 | 'content' => $content, 130 | 'image' => $image, 131 | ]); 132 | } 133 | 134 | public static function share(string $url, string $title, string $content, string $image) 135 | { 136 | return self::CQ('share', [ 137 | 'url' => $url, 138 | 'title' => $title, 139 | 'content' => $content, 140 | 'image' => $image, 141 | ]); 142 | } 143 | 144 | public static function rich(string $url, string $text) 145 | { 146 | return self::CQ('rich', [ 147 | 'url' => $url, 148 | 'text' => $text 149 | ]); 150 | } 151 | 152 | 153 | public static function filterCQAt(string $string) 154 | { 155 | return preg_replace('/\[CQ:at,qq=\d+\]/', '', $string); 156 | } 157 | 158 | public static function decodeHtml(string $message) 159 | { 160 | $message = preg_replace("/&/", "&", $message); 161 | $message = preg_replace("/[/", "[", $message); 162 | $message = preg_replace("/]/", "]", $message); 163 | return $message; 164 | } 165 | 166 | public static function isAtMe(string $message, $userId) 167 | { 168 | $cq = self::at($userId); 169 | $pos = strpos($message, $cq); 170 | return $pos !== false ? true : false; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/CoolQ/QQ.php: -------------------------------------------------------------------------------- 1 | request = $request; 31 | } 32 | 33 | public function getProtocol(): Protocol 34 | { 35 | return $this->request; 36 | } 37 | 38 | public function event($messageEvent, $noticeEvent, $requestEvent, $otherEvent): Response 39 | { 40 | 41 | if(!$this->getProtocol()->isValidated()){ 42 | return Response::signatureError(); 43 | } 44 | 45 | try{ 46 | $content = $this->getContent(); 47 | }catch (Exception $exception){ 48 | return Response::eventMissParamsError(); 49 | } 50 | try{ 51 | switch ($content['post_type']) { 52 | //收到消息 53 | case 'message': 54 | $messageEvent($content); 55 | break; 56 | //群、讨论组变动等非消息类事件 57 | case 'notice': //兼容4.0 58 | case 'event': 59 | $noticeEvent($content); 60 | break; 61 | //加好友请求、加群请求/邀请 62 | case 'request': 63 | $requestEvent($content); 64 | break; 65 | default: 66 | $otherEvent($content); 67 | break; 68 | } 69 | }catch (Exception $exception){ 70 | return Response::error($exception->getMessage()); 71 | } 72 | return Response::ok(); 73 | } 74 | 75 | 76 | public function getContent(): array 77 | { 78 | return $this->getProtocol()->getContent(); 79 | } 80 | 81 | /** 82 | * 发送私聊消息 同步 83 | * @param int $userId QQ 84 | * @param string $message 消息 85 | * @param bool $autoEscape 是否转译CQ码等特殊符号 86 | * @return Response 87 | */ 88 | public function sendPrivateMsg(int $userId, string $message, bool $autoEscape = false): Response 89 | { 90 | $response = $this->getProtocol()->send(Url::send_private_msg, [ 91 | 'user_id' => $userId, 92 | 'message' => $message, 93 | 'auto_escape' => $autoEscape, 94 | ], 'POST'); 95 | return $response; 96 | } 97 | 98 | /** 99 | * 发送私聊消息 异步 100 | * @param int $userId QQ 101 | * @param string $message 消息 102 | * @param bool $autoEscape 是否转译CQ码等特殊符号 103 | * @return Response 104 | */ 105 | public function sendPrivateMsgAsync(int $userId, string $message, bool $autoEscape = false): Response 106 | { 107 | $response = $this->getProtocol()->send(Url::send_private_msg_async, [ 108 | 'user_id' => $userId, 109 | 'message' => $message, 110 | 'auto_escape' => $autoEscape, 111 | ], 'POST'); 112 | return $response; 113 | } 114 | 115 | /** 116 | * 发送群聊消息 同步 117 | * @param int $groupId 群号 118 | * @param string $message 消息 119 | * @param bool $autoEscape 是否转译CQ码等特殊符号 120 | * @return Response 121 | */ 122 | public function sendGroupMsg(int $groupId, string $message, bool $autoEscape = false): Response 123 | { 124 | $response = $this->getProtocol()->send(Url::send_group_msg, [ 125 | 'group_id' => $groupId, 126 | 'message' => $message, 127 | 'auto_escape' => $autoEscape, 128 | ], 'POST'); 129 | return $response; 130 | } 131 | 132 | /** 133 | * 发送群聊消息 异步 134 | * @param int $groupId 群号 135 | * @param string $message 消息 136 | * @param bool $autoEscape 是否转译CQ码等特殊符号 137 | * @return Response 138 | */ 139 | public function sendGroupMsgAsync(int $groupId, string $message, bool $autoEscape = false): Response 140 | { 141 | $response = $this->getProtocol()->send(Url::send_group_msg_async, [ 142 | 'group_id' => $groupId, 143 | 'message' => $message, 144 | 'auto_escape' => $autoEscape, 145 | ], 'POST'); 146 | return $response; 147 | } 148 | 149 | /** 150 | * 发送讨论组消息 同步 151 | * @param int $discussId 讨论组id 152 | * @param string $message 消息 153 | * @param bool $autoEscape 是否转译CQ码等特殊符号 154 | * @return Response 155 | */ 156 | public function sendDiscussMsg(int $discussId, string $message, bool $autoEscape = false): Response 157 | { 158 | $response = $this->getProtocol()->send(Url::send_discuss_msg, [ 159 | 'discuss_id' => $discussId, 160 | 'message' => $message, 161 | 'auto_escape' => $autoEscape, 162 | ], 'POST'); 163 | return $response; 164 | } 165 | 166 | /** 167 | * 发送讨论组消息 异步 168 | * @param int $discussId 讨论组id 169 | * @param string $message 消息 170 | * @param bool $autoEscape 是否转译CQ码等特殊符号 171 | * @return Response 172 | */ 173 | public function sendDiscussMsgAsync(int $discussId, string $message, bool $autoEscape = false): Response 174 | { 175 | $response = $this->getProtocol()->send(Url::send_discuss_msg_async, [ 176 | 'discuss_id' => $discussId, 177 | 'message' => $message, 178 | 'auto_escape' => $autoEscape, 179 | ], 'POST'); 180 | return $response; 181 | } 182 | 183 | /** 184 | * 发送消息 同步 185 | * @param string $messageType 消息类型 186 | * @param int $id 消息接受者id 187 | * @param string $message 消息 188 | * @param bool $autoEscape 是否转译CQ码等特殊符号 189 | * @return Response 190 | */ 191 | public function sendMsg(string $messageType, int $id, string $message, bool $autoEscape = false): Response 192 | { 193 | $response = $this->getProtocol()->send(Url::send_msg, [ 194 | 'message_type' => $messageType, 195 | 'id' => $id, 196 | 'message' => $message, 197 | 'auto_escape' => $autoEscape, 198 | ], 'POST'); 199 | return $response; 200 | } 201 | 202 | /** 203 | * 发送消息 异步 204 | * @param string $messageType 消息类型 205 | * @param int $id 消息接受者id 206 | * @param string $message 消息 207 | * @param bool $autoEscape 是否转译CQ码等特殊符号 208 | * @return Response 209 | */ 210 | public function sendMsgAsync(string $messageType, int $id, string $message, bool $autoEscape = false): Response 211 | { 212 | $response = $this->getProtocol()->send(Url::send_msg_async, [ 213 | 'message_type' => $messageType, 214 | 'id' => $id, 215 | 'message' => $message, 216 | 'auto_escape' => $autoEscape, 217 | ], 'POST'); 218 | return $response; 219 | } 220 | 221 | /** 222 | * 撤回消息 223 | * @param int $messageId 消息id 224 | * @return Response 225 | */ 226 | public function deleteMsg(int $messageId): Response 227 | { 228 | $response = $this->getProtocol()->send(Url::delete_msg, [ 229 | 'message_id' => $messageId, 230 | ], 'POST'); 231 | return $response; 232 | } 233 | 234 | /** 235 | * 撤回群内其他成员消息 机器人必须为管理员 236 | * @param int $groupId 群号 237 | * @param int $messageId 消息id 238 | * @return Response 239 | */ 240 | public function deleteGroupMsg(int $groupId, int $messageId): Response 241 | { 242 | return $this->deleteMsg($messageId); 243 | } 244 | 245 | /** 246 | * 赞 247 | * @param int $userId 248 | * @param int $times 249 | * @return Response 250 | */ 251 | public function sendLike(int $userId, int $times = 1): Response 252 | { 253 | $response = $this->getProtocol()->send(Url::send_like, [ 254 | 'user_id' => $userId, 255 | 'times' => $times, 256 | ], 'POST'); 257 | return $response; 258 | } 259 | 260 | /** 261 | * 窗口抖动 262 | * @param int $userId 263 | * @return Response 264 | */ 265 | public function sendShake(int $userId): Response 266 | { 267 | return $this->sendPrivateMsg($userId, CQ::shake()); 268 | } 269 | 270 | /** 271 | * 修改个性签名 272 | * @param string $ignature 273 | * @return Response 274 | */ 275 | public function setQQSignature(string $ignature): Response 276 | { 277 | return Response::notFoundResourceError(); 278 | } 279 | 280 | /** 281 | * 修改好友备注 282 | * @param int $userId QQ 283 | * @param string $friendName 备注名 284 | * @return Response 285 | */ 286 | public function setFriendName(int $userId, string $friendName): Response 287 | { 288 | return Response::notFoundResourceError(); 289 | } 290 | 291 | /** 292 | * 删除好友 293 | * @param int $userId QQ 294 | * @return Response 295 | */ 296 | public function deleteFriend(int $userId): Response 297 | { 298 | return Response::notFoundResourceError(); 299 | } 300 | 301 | /** 302 | * 加好友 303 | * @param int $userId QQ 304 | * @param string $msg 附带信息 305 | * @return Response 306 | */ 307 | public function addFriend(int $userId, string $msg): Response 308 | { 309 | return Response::notFoundResourceError(); 310 | } 311 | 312 | /** 313 | * 加群 314 | * @param int $groupId 群号 315 | * @param string $msg 附带信息 316 | * @return Response 317 | */ 318 | public function addGroup(int $groupId, string $msg): Response 319 | { 320 | return Response::notFoundResourceError(); 321 | } 322 | 323 | /** 324 | * 移除群成员 325 | * @param int $groupId 326 | * @param int $userId 327 | * @param bool $rejectAddRequest 是否不再接收加群申请 328 | * @return Response 329 | */ 330 | public function setGroupKick(int $groupId, int $userId, bool $rejectAddRequest = false): Response 331 | { 332 | $response = $this->getProtocol()->send(Url::set_group_kick, [ 333 | 'group_id' => $groupId, 334 | 'user_id' => $userId, 335 | 'reject_add_request' => $rejectAddRequest, 336 | ], 'POST'); 337 | return $response; 338 | } 339 | 340 | /** 341 | * 禁言 342 | * @param int $groupId 群号 343 | * @param int $userId QQ 344 | * @param int $duration 禁言时长 单位:秒 0为解除禁言 默认30分钟 345 | * @return Response 346 | */ 347 | public function setGroupBan(int $groupId, int $userId, int $duration = 30 * 60): Response 348 | { 349 | $response = $this->getProtocol()->send(Url::set_group_ban, [ 350 | 'group_id' => $groupId, 351 | 'user_id' => $userId, 352 | 'duration' => $duration, 353 | ], 'POST'); 354 | return $response; 355 | } 356 | 357 | /** 358 | * 匿名禁言消息 359 | * @param int $groupId 群号 360 | * @param string $flag 用户id 361 | * @param int $duration 禁言时长 单位:秒 0为解除禁言 默认30分钟 362 | * @return Response 363 | */ 364 | public function setGroupAnonymousBan(int $groupId, string $flag, int $duration = 30 * 60): Response 365 | { 366 | $response = $this->getProtocol()->send(Url::set_group_anonymous_ban, [ 367 | 'group_id' => $groupId, 368 | 'flag' => $flag, 369 | 'duration' => $duration, 370 | ], 'POST'); 371 | return $response; 372 | } 373 | 374 | /** 375 | * 设置全员禁言 376 | * @param int $groupId 群号 377 | * @param bool $enable 禁言、解除 378 | * @return Response 379 | */ 380 | public function setGroupWholeBan(int $groupId, bool $enable = true): Response 381 | { 382 | $response = $this->getProtocol()->send(Url::set_group_whole_ban, [ 383 | 'group_id' => $groupId, 384 | 'enable' => $enable, 385 | ], 'POST'); 386 | return $response; 387 | } 388 | 389 | /** 390 | * 设置群管理 391 | * @param int $groupId 群号 392 | * @param int $userId QQ 393 | * @param bool $enable 设置、取消 394 | * @return Response 395 | */ 396 | public function setGroupAdmin(int $groupId, int $userId, bool $enable = true): Response 397 | { 398 | $response = $this->getProtocol()->send(Url::set_group_admin, [ 399 | 'group_id' => $groupId, 400 | 'user_id' => $userId, 401 | 'enable' => $enable, 402 | ], 'POST'); 403 | return $response; 404 | } 405 | 406 | /** 407 | * 匿名设置 408 | * @param int $groupId 群号 409 | * @param bool $enable 开启、关闭 410 | * @return Response 411 | */ 412 | public function setGroupAnonymous(int $groupId, bool $enable = true): Response 413 | { 414 | $response = $this->getProtocol()->send(Url::set_group_anonymous, [ 415 | 'group_id' => $groupId, 416 | 'enable' => $enable, 417 | ], 'POST'); 418 | return $response; 419 | } 420 | 421 | /** 422 | * 修改群内成员的名片 423 | * @param int $groupId 群号 424 | * @param int $userId QQ 425 | * @param string|null $card 新名片 426 | * @return Response 427 | */ 428 | public function setGroupCard(int $groupId, int $userId, string $card = null): Response 429 | { 430 | $response = $this->getProtocol()->send(Url::set_group_card, [ 431 | 'group_id' => $groupId, 432 | 'user_id' => $userId, 433 | 'card' => $card, 434 | ], 'POST'); 435 | return $response; 436 | } 437 | 438 | /** 439 | * 退群 440 | * @param int $groupId 群号 441 | * @param bool $isDismiss 是否解散,如果登录号是群主,则仅在此项为 true 时能够解散 442 | * @return Response 443 | */ 444 | public function setGroupLeave(int $groupId, bool $isDismiss = false): Response 445 | { 446 | $response = $this->getProtocol()->send(Url::set_group_leave, [ 447 | 'group_id' => $groupId, 448 | 'is_dismiss' => $isDismiss, 449 | ], 'POST'); 450 | return $response; 451 | } 452 | 453 | /** 454 | * 解散群 455 | * @param int $groupId 群号 456 | * @return Response 457 | */ 458 | public function setRemoveGroup(int $groupId): Response 459 | { 460 | return $this->setGroupLeave($groupId, true); 461 | } 462 | 463 | /** 464 | * 设置群组专属头衔 465 | * @param int $groupId 群号 466 | * @param int $userId QQ 467 | * @param string|null $specialTitle 专属头衔,不填或空字符串表示删除专属头衔 468 | * @param int $duration 专属头衔有效期,单位秒,-1 表示永久,不过此项似乎没有效果,可能是只有某些特殊的时间长度有效,有待测试 469 | * @return Response 470 | */ 471 | public function setGroupSpecialTitle(int $groupId, int $userId, string $specialTitle = null, int $duration = -1): Response 472 | { 473 | $response = $this->getProtocol()->send(Url::set_group_special_title, [ 474 | 'group_id' => $groupId, 475 | 'user_id' => $userId, 476 | 'special_title' => $specialTitle, 477 | 'duration' => $duration, 478 | ], 'POST'); 479 | return $response; 480 | } 481 | 482 | /** 483 | * 修改讨论组名称 484 | * @param int $discussId 485 | * @param string $discussName 486 | * @return Response 487 | */ 488 | public function setDiscussName(int $discussId, string $discussName): Response 489 | { 490 | return Response::notFoundResourceError(); 491 | } 492 | 493 | /** 494 | * 退讨论组 495 | * @param int $discussId 496 | * @return Response 497 | */ 498 | public function setDiscussLeave(int $discussId): Response 499 | { 500 | $response = $this->getProtocol()->send(Url::set_discuss_leave, [ 501 | 'discuss_id' => $discussId, 502 | ], 'POST'); 503 | return $response; 504 | } 505 | 506 | /** 507 | * 处理加好友请求 (不同驱动实现不同、参数不同。待兼容) CoolQ 508 | * @param string $flag 加好友请求的 flag(需从上报的数据中获得) 509 | * @param bool $approve 是否同意请求 510 | * @param string $remark 添加后的好友备注(仅在同意时有效) 511 | * @return Response 512 | */ 513 | public function setFriendAddRequest(string $flag, bool $approve = true, string $remark = ''): Response 514 | { 515 | $response = $this->getProtocol()->send(Url::set_friend_add_request, [ 516 | 'flag' => $flag, 517 | 'approve' => $approve, 518 | 'remark' => $remark, 519 | ], 'POST'); 520 | return $response; 521 | } 522 | 523 | /** 524 | * 处理加群请求/邀请 (不同驱动实现不同、参数不同。待兼容) CoolQ 525 | * @param string $flag 加好友请求的 flag(需从上报的数据中获得) 526 | * @param string $type add 或 invite,请求类型(需要和上报消息中的 sub_type 字段相符) 527 | * @param bool $approve 是否同意请求/邀请 528 | * @param string $reason 拒绝理由(仅在拒绝时有效) 529 | * @return Response 530 | */ 531 | public function setGroupAddRequest(string $flag, string $type, bool $approve = true, string $reason = ''): Response 532 | { 533 | $response = $this->getProtocol()->send(Url::set_group_add_request, [ 534 | 'flag' => $flag, 535 | 'type' => $type, 536 | 'approve' => $approve, 537 | 'reason' => $reason, 538 | ], 'POST'); 539 | return $response; 540 | } 541 | 542 | /** 543 | * 获取登录号信息 544 | * @return Response 545 | */ 546 | public function getQQLoginInfo(): Response 547 | { 548 | $response = $this->getProtocol()->send(Url::get_login_info, [], 'POST'); 549 | return $response; 550 | } 551 | 552 | /** 553 | * 获取陌生人信息 554 | * @param int $userId 555 | * @param bool $noCache 556 | * @return Response 557 | */ 558 | public function getStrangerInfo(int $userId, bool $noCache = false): Response 559 | { 560 | $response = $this->getProtocol()->send(Url::get_stranger_info, [ 561 | 'user_id' => $userId, 562 | 'no_cache' => $noCache, 563 | ], 'POST'); 564 | return $response; 565 | } 566 | 567 | /** 568 | * 某QQ个人信息 569 | * @param int $userId 570 | * @param bool $noCache 571 | * @return Response 572 | */ 573 | public function getQQInfo(int $userId, bool $noCache = false): Response 574 | { 575 | return Response::notFoundResourceError(); 576 | } 577 | 578 | /** 579 | * 获取群列表 580 | * @return Response 581 | */ 582 | public function getGroupList(): Response 583 | { 584 | $response = $this->getProtocol()->send(Url::get_group_list, [], 'POST'); 585 | return $response; 586 | } 587 | 588 | /** 589 | * 群成员信息 590 | * @param int $groupId 591 | * @param int $userId 592 | * @param bool $noCache 593 | * @return Response 594 | */ 595 | public function getGroupMemberInfo(int $groupId, int $userId, bool $noCache = false): Response 596 | { 597 | $response = $this->getProtocol()->send(Url::get_group_member_info, [ 598 | 'group_id' => $groupId, 599 | 'user_id' => $userId, 600 | 'no_cache' => $noCache, 601 | ], 'POST'); 602 | return $response; 603 | } 604 | 605 | /** 606 | * 群成员列表 607 | * @param int $groupId 608 | * @return Response 609 | */ 610 | public function getGroupMemberList(int $groupId): Response 611 | { 612 | $response = $this->getProtocol()->send(Url::get_group_member_list, [ 613 | 'group_id' => $groupId, 614 | ], 'POST'); 615 | return $response; 616 | } 617 | 618 | /** 619 | * 邀请好友入群 620 | * @param int $groupId 621 | * @param int $userId 622 | * @return Response 623 | */ 624 | public function inviteFriendIntoGroup(int $groupId, int $userId): Response 625 | { 626 | return Response::notFoundResourceError(); 627 | } 628 | 629 | /** 630 | * 取Cookies 631 | * @return Response 632 | */ 633 | public function getCookies(): Response 634 | { 635 | $response = $this->getProtocol()->send(Url::get_cookies, [], 'POST'); 636 | return $response; 637 | } 638 | 639 | /** 640 | * 取bkn 641 | * @return Response 642 | */ 643 | public function getBkn(): Response 644 | { 645 | return Response::notFoundResourceError(); 646 | } 647 | 648 | /** 649 | * 取ClientKey 650 | * @return Response 651 | */ 652 | public function getClientKey(): Response 653 | { 654 | return Response::notFoundResourceError(); 655 | } 656 | 657 | /** 658 | * 获取 QQ 相关接口凭证 (为了兼容不同平台返回的不同值) 659 | * @return Response 660 | */ 661 | public function getCredentials(): Response 662 | { 663 | $response = $this->getProtocol()->send(Url::get_credentials, [], 'POST'); 664 | return $response; 665 | } 666 | 667 | public function returnApi(Response $response) 668 | { 669 | $this->getProtocol()->returnApi($response); 670 | } 671 | } 672 | 673 | -------------------------------------------------------------------------------- /src/CoolQ/Url.php: -------------------------------------------------------------------------------- 1 | isBlackList; 43 | } 44 | 45 | /** 46 | * @param bool $isBlackList 47 | */ 48 | public function setIsBlackList(bool $isBlackList) 49 | { 50 | $this->isBlackList = $isBlackList; 51 | } 52 | 53 | 54 | /** 55 | * @return array 56 | */ 57 | public function getPrivateBlackList(): array 58 | { 59 | return $this->privateBlackList; 60 | } 61 | 62 | /** 63 | * @param array $privateBlackList 64 | */ 65 | public function setPrivateBlackList(array $privateBlackList) 66 | { 67 | $this->privateBlackList = $privateBlackList; 68 | } 69 | 70 | /** 71 | * @return array 72 | */ 73 | public function getGroupBlackList(): array 74 | { 75 | return $this->groupBlackList; 76 | } 77 | 78 | /** 79 | * @param array $groupBlackList 80 | */ 81 | public function setGroupBlackList(array $groupBlackList) 82 | { 83 | $this->groupBlackList = $groupBlackList; 84 | } 85 | 86 | /** 87 | * @return array 88 | */ 89 | public function getDiscussBlackList(): array 90 | { 91 | return $this->discussBlackList; 92 | } 93 | 94 | /** 95 | * @param array $discussBlackList 96 | */ 97 | public function setDiscussBlackList(array $discussBlackList) 98 | { 99 | $this->discussBlackList = $discussBlackList; 100 | } 101 | 102 | } -------------------------------------------------------------------------------- /src/Core/CQ.php: -------------------------------------------------------------------------------- 1 | url = $url; 44 | $hosts = explode(':', $this->url); 45 | if (count($hosts) != 2) { 46 | throw new Exception('missing url or port'); 47 | } 48 | $this->host = $hosts[0]; 49 | $this->port = $hosts[1]; 50 | $this->accessToken = $access_token; 51 | $this->secret = $secret; 52 | 53 | $this->options['headers'] = [ 54 | 'Authorization' => 'Token ' . $this->accessToken 55 | ]; 56 | 57 | $this->client = new Client([ 58 | // Base URI is used with relative requests 59 | 'base_uri' => $this->url, 60 | // You can set any number of default request options. 61 | 'timeout' => 10.0, 62 | ]); 63 | 64 | } 65 | 66 | public function send($uri, $param = [], $method = 'POST'): Response 67 | { 68 | 69 | try { 70 | $this->response = $this->client->request($method, $uri, array_merge($this->options, [ 71 | 'query' => $param 72 | ])); 73 | $response = $this->response; 74 | if ($response->getStatusCode() == 200) { 75 | $response = $response->getBody(); 76 | $response = json_decode($response, true); 77 | $code = $response['retcode']; 78 | $data = $response['data']; 79 | if ($code != 0) { 80 | return Response::response(200, $code, []); 81 | } 82 | $data = empty($data) ? [] : $data; 83 | return Response::ok($data); 84 | } 85 | } catch (ClientException $e) { 86 | //如果 http_errors 请求参数设置成true,在400级别的错误的时候将会抛出 87 | switch ($e->getCode()) { 88 | case 400: 89 | return Response::notFoundResourceError(); 90 | break; 91 | case 401: 92 | //401 配置文件中已填写access_token 初始化CoolQ对象时未传值 93 | return Response::accessTokenNoneError(); 94 | break; 95 | case 403: 96 | //403 验证access_token错误 97 | return Response::accessTokenError(); 98 | break; 99 | case 404: 100 | return Response::notFoundResourceError(); 101 | break; 102 | case 406: 103 | return Response::contentTypeError(); 104 | break; 105 | default: 106 | return Response::error([ 107 | 'message' => $e->getMessage() 108 | ]); 109 | break; 110 | } 111 | } catch (RequestException $e) { 112 | //在发送网络错误(连接超时、DNS错误等)时,将会抛出 GuzzleHttp\Exception\RequestException 异常。 113 | //一般为coolq-http-api插件未开启 接口地址无法访问 114 | switch ($e->getCode()) { 115 | case 0: 116 | return Response::pluginServerError($this->client); 117 | break; 118 | default: 119 | return Response::error([ 120 | 'message' => $e->getMessage() 121 | ]); 122 | break; 123 | } 124 | } 125 | 126 | return $response; 127 | 128 | } 129 | 130 | public function sendAsync($uri, $param = [], $method = 'POST'): Response 131 | { 132 | 133 | $promise = $this->client->requestAsync($method, $uri, array_merge($this->options, [ 134 | 'query' => $param 135 | ])); 136 | 137 | $promise->then( 138 | function (ResponseInterface $res) { 139 | echo $res->getBody() . "\n"; 140 | }, 141 | function (RequestException $e) { 142 | echo $e->getMessage() . "\n"; 143 | echo $e->getRequest()->getMethod(); 144 | })->wait(); 145 | 146 | // return $promise; 147 | } 148 | 149 | public function isValidated(): bool 150 | { 151 | $signature = http_server('HTTP_X_SIGNATURE'); 152 | $signature = $signature == '' ? '' : substr($signature, 5, strlen($signature)); 153 | $putParams = http_put(); 154 | if ($this->isSignature && !empty($signature) && (hash_hmac('sha1', \GuzzleHttp\json_encode($putParams, JSON_UNESCAPED_UNICODE), $this->secret) != $signature)) { 155 | //sha1验证失败 156 | return false; 157 | } 158 | return true; 159 | } 160 | 161 | /** 162 | * @return array 163 | * @throws Exception 164 | */ 165 | public function getContent(): array 166 | { 167 | $content = http_put(); 168 | if (empty($content)) { 169 | throw new Exception('put params not be empty'); 170 | } 171 | switch ($content['post_type']) { 172 | //收到消息 173 | case 'message': 174 | $message_type = $content['message_type']; 175 | switch ($message_type) { 176 | //私聊消息 177 | case "private": 178 | $this->content = [ 179 | 'message_type' => $content['message_type'], 180 | 'message_id' => $content['message_id'], 181 | 'font' => $content['font'], 182 | 'user_id' => $content['user_id'], 183 | 'message' => $content['message'], 184 | //消息子类型,如果是好友则是 "friend", 185 | //如果从群或讨论组来的临时会话则分别是 "group"、"discuss" 186 | //"friend"、"group"、"discuss"、"other" 187 | 'sub_type' => $content['sub_type'], 188 | ]; 189 | break; 190 | //群消息 191 | case "group": 192 | $this->content = [ 193 | 'message_type' => $content['message_type'], 194 | 'message_id' => $content['message_id'], 195 | 'font' => $content['font'], 196 | 'user_id' => $content['user_id'], 197 | 'message' => $content['message'], 198 | 'group_id' => $content['group_id'], 199 | //匿名用户显示名 200 | 'anonymous' => $content['anonymous'], 201 | //匿名用户 flag,在调用禁言 API 时需要传入 202 | 'anonymous_flag' => empty($content['anonymous']['flag']) ? '' : $content['anonymous']['flag'], 203 | ]; 204 | // {"reply":"message","block": true,"at_sender":true,"kick":false,"ban":false} 205 | break; 206 | //讨论组消息 207 | case "discuss": 208 | $this->content = [ 209 | 'message_type' => $content['message_type'], 210 | 'message_id' => $content['message_id'], 211 | 'font' => $content['font'], 212 | 'discuss_id' => $content['discuss_id'], 213 | 'user_id' => $content['user_id'], 214 | 'message' => $content['message'], 215 | ]; 216 | // {"reply":"message","block": true,"at_sender":true} 217 | break; 218 | } 219 | break; 220 | //群、讨论组变动等非消息类事件 221 | case 'notice': //兼容4.0 222 | case 'event': 223 | $event = empty($content['event']) ? $content['notice_type'] : $content['event'];//兼容4.0 224 | switch ($event) { 225 | //群管理员变动 226 | case "group_admin": 227 | $this->content = [ 228 | 'event' => empty($content['event']) ? $content['notice_type'] : $content['event'], 229 | //"set"、"unset" 事件子类型,分别表示设置和取消管理员 230 | 'sub_type' => $content['sub_type'], 231 | 'group_id' => $content['group_id'], 232 | 'user_id' => $content['user_id'], 233 | ]; 234 | break; 235 | //群成员减少 236 | case "group_decrease": 237 | $this->content = [ 238 | 'event' => empty($content['event']) ? $content['notice_type'] : $content['event'], 239 | //"leave"、"kick"、"kick_me" 事件子类型,分别表示主动退群、成员被踢、登录号被踢 240 | 'sub_type' => $content['sub_type'], 241 | 'group_id' => $content['group_id'], 242 | 'user_id' => $content['user_id'], 243 | 'operator_id' => $content['operator_id'], 244 | ]; 245 | break; 246 | //群成员增加 247 | case "group_increase": 248 | $this->content = [ 249 | 'event' => empty($content['event']) ? $content['notice_type'] : $content['event'], 250 | //"approve"、"invite" 事件子类型,分别表示管理员已同意入群、管理员邀请入群 251 | 'sub_type' => $content['sub_type'], 252 | 'group_id' => $content['group_id'], 253 | 'user_id' => $content['user_id'], 254 | 'operator_id' => $content['operator_id'], 255 | ]; 256 | break; 257 | //群文件上传 258 | case "group_upload": 259 | $this->content = [ 260 | 'event' => empty($content['event']) ? $content['notice_type'] : $content['event'], 261 | 'group_id' => $content['group_id'], 262 | 'user_id' => $content['user_id'], 263 | #字段名 数据类型 说明 264 | #id string 文件 ID 265 | #name string 文件名 266 | #size number 文件大小(字节数) 267 | #busid number busid(目前不清楚有什么作用) 268 | 'file' => $content['file'], 269 | ]; 270 | break; 271 | //好友添加 272 | case "friend_added": 273 | $this->content = [ 274 | 'event' => empty($content['event']) ? $content['notice_type'] : $content['event'], 275 | 'user_id' => $content['user_id'], 276 | ]; 277 | break; 278 | } 279 | break; 280 | //加好友请求、加群请求/邀请 281 | case 'request': 282 | $request_type = $content['request_type']; 283 | switch ($request_type) { 284 | case "friend": 285 | $this->content = [ 286 | 'request_type' => $content['request_type'], 287 | 'user_id' => $content['user_id'], 288 | 'message' => empty($content['message']) ? $content['comment'] : $content['message'],//兼容4.0 289 | 'flag' => $content['flag'], 290 | ]; 291 | //{"block": true,"approve":true,"reason":"就是拒绝你 不行啊"} 292 | break; 293 | case "group": 294 | $this->content = [ 295 | 'request_type' => $content['request_type'], 296 | //"add"、"invite" 请求子类型,分别表示加群请求、邀请登录号入群 297 | 'sub_type' => $content['sub_type'], 298 | 'group_id' => $content['group_id'], 299 | 'user_id' => $content['user_id'], 300 | 'message' => empty($content['message']) ? $content['comment'] : $content['message'],//兼容4.0 301 | 'flag' => $content['flag'], 302 | ]; 303 | //{"block": true,"approve":true,"reason":"就是拒绝你 不行啊"} 304 | break; 305 | } 306 | break; 307 | default: 308 | $this->content = $content; 309 | break; 310 | } 311 | $this->content['post_type'] = $content['post_type']; 312 | return $this->content; 313 | } 314 | 315 | public function returnApi(Response $response) 316 | { 317 | echo json_encode($response, JSON_UNESCAPED_UNICODE); 318 | exit(); 319 | } 320 | 321 | public function isCli(): bool 322 | { 323 | return false; 324 | } 325 | } -------------------------------------------------------------------------------- /src/Core/Protocols/Protocol.php: -------------------------------------------------------------------------------- 1 | code = $code; 24 | $this->message = $message; 25 | $this->errorCode = $errorCode; 26 | $this->errorMsg = $errorMsg; 27 | $this->data = $data; 28 | } 29 | 30 | /** 31 | * @return int 32 | */ 33 | public 34 | function getCode(): int 35 | { 36 | return $this->code; 37 | } 38 | 39 | /** 40 | * @return int 41 | */ 42 | public function getErrorCode(): int 43 | { 44 | return $this->errorCode; 45 | } 46 | 47 | 48 | /** 49 | * @return string 50 | */ 51 | public function getMessage(): string 52 | { 53 | return $this->message; 54 | } 55 | 56 | 57 | /** 58 | * @return array 59 | */ 60 | public function getData(): array 61 | { 62 | return $this->data; 63 | } 64 | 65 | 66 | public function getErrorMsg(): string 67 | { 68 | return $this->errorMsg; 69 | } 70 | 71 | 72 | public function toArray(): array 73 | { 74 | return [ 75 | 'code' => $this->getCode(), 76 | 'message' => $this->getMessage(), 77 | 'errorCode' => $this->getErrorCode(), 78 | 'errorMsg' => $this->getErrorMsg(), 79 | 'data' => $this->getData(), 80 | ]; 81 | } 82 | 83 | public function toJson(): string 84 | { 85 | return json_encode($this->toArray(), JSON_UNESCAPED_UNICODE); 86 | } 87 | 88 | public static function response($code, $errorCode, $data) 89 | { 90 | 91 | 92 | $message = [ 93 | 0 => '接口请求成功', 94 | 100 => '请参数参数错误', 95 | 200 => 'HTTP请求成功', 96 | 400 => '资源请求被拒绝', 97 | 500 => '服务器故障', 98 | 65535 => '未获取到上报事件发送的上报数据', 99 | ]; 100 | 101 | if (!array_key_exists($code, $message)) { 102 | $message = '未知状态码'; 103 | } 104 | 105 | $message = $message[$code]; 106 | 107 | $msg = [ 108 | -1 => '请求发送失败', 109 | -2 => '未收到服务器回复,可能未发送成功', 110 | -3 => '消息过长或为空', 111 | -4 => '消息解析过程异常', 112 | -5 => '日志功能未启用', 113 | -6 => '日志优先级错误', 114 | -7 => '数据入库失败', 115 | -8 => '不支持对系统帐号操作', 116 | -9 => '帐号不在该群内,消息无法发送', 117 | -10 => '该用户不存在/不在群内', 118 | -11 => '数据错误,无法请求发送', 119 | -12 => '不支持对匿名成员解除禁言', 120 | -13 => '无法解析要禁言的匿名成员数据', 121 | -14 => '由于未知原因,操作失败', 122 | -15 => '群未开启匿名发言功能,或匿名帐号被禁言', 123 | -16 => '帐号不在群内或网络错误,无法退出/解散该群', 124 | -17 => '帐号为群主,无法退出该群', 125 | -18 => '帐号非群主,无法解散该群', 126 | -19 => '临时消息已失效或未建立', 127 | -20 => '参数错误', 128 | -21 => '临时消息已失效或未建立', 129 | -22 => '获取QQ信息失败', 130 | -23 => '找不到与目标QQ的关系,消息无法发送', 131 | -99 => 'Air 不支持此操作', 132 | -101 => '应用过大', 133 | -102 => '不是合法的应用', 134 | -103 => '不是合法的应用', 135 | -104 => '应用不存在公开的Information函数', 136 | -105 => '无法载入应用信息', 137 | -106 => '文件名与应用ID不同', 138 | -107 => '返回信息解析错误', 139 | -108 => 'AppInfo返回的Api版本不支持直接加载,仅支持Api版本为9(及以上)的应用直接加载', 140 | -109 => 'AppInfo返回的AppID错误', 141 | -110 => '缺失AppInfo返回的AppID对应的[Appid].json文件', 142 | -111 => '[Appid].json文件内的AppID与其文件名不同', 143 | -120 => '无Api授权接收函数(Initialize)', 144 | -121 => 'Api授权接收函数(Initialize)返回值非0', 145 | -122 => '尝试恶意修改酷Q配置文件,将取消加载并关闭酷Q', 146 | -150 => '无法载入应用信息', 147 | -151 => '应用信息Json串解析失败,请检查Json串是否正确', 148 | -152 => 'Api版本过旧或过新', 149 | -153 => '应用信息错误或存在缺失', 150 | -154 => 'Appid不合法', 151 | -160 => '事件类型(Type)错误或缺失', 152 | -161 => '事件函数(Function)错误或缺失', 153 | -162 => '应用优先级不为10000、20000、30000、40000中的一个', 154 | -163 => '事件类型(Api)不支持应用Api版本', 155 | -164 => '应用Api版本大于8,但使用了新版本已停用的事件类型(Type):1(好友消息)、3(临时消息)', 156 | -165 => '事件类型为2(群消息)、4(讨论组消息)、21(私聊消息),但缺少正则表达式(regex)的表达式部分(expression)', 157 | -166 => '存在为空的正则表达式(regex)的key', 158 | -167 => '存在为空的正则表达式(regex)的表达式部分(expression)', 159 | -168 => '应用事件(event)id参数不存在或为0', 160 | -169 => '应用事件(event)id参数有重复=>', 161 | -180 => '应用状态(status)id参数不存在或为0', 162 | -181 => '应用状态(status)period参数不存在或设置错误', 163 | -182 => '应用状态(status)id参数有重复=>', 164 | -201 => '无法载入应用,可能是应用文件已损坏', 165 | -202 => 'Api版本过旧或过新', 166 | -997 => '应用未启用', 167 | -998 => '应用调用在Auth声明之外的 酷Q A。', 168 | -2333 => 'CQHTTP插件未开启,或插件服务器启动失败。访问被拒绝。请检测Docker端口是否开启,网络是否通畅。', 169 | -1000 => '无法找到swoole扩展,请先安装.', 170 | -1001 => 'must be used in PHP CLI mode.', 171 | -1002 => '无法找到Pcntl扩展,请先安装.', 172 | 0 => '同时 status 为 ok,表示操作成功', 173 | 1 => '同时 status 为 async,表示操作已进入异步执行,具体结果未知', 174 | 100 => '参数缺失或参数无效,通常是因为没有传入必要参数,某些接口中也可能因为参数明显无效(比如传入的 QQ 号小于等于 0,此时无需调用酷 Q 函数即可确定失败),此项和以下的 status 均为 failed', 175 | 102 => '酷 Q 函数返回的数据无效,一般是因为传入参数有效但没有权限,比如试图获取没有加入的群组的成员列表,或调取语音相关接口未安装语音组件', 176 | 103 => '操作失败,一般是因为用户权限不足,或文件系统异常、不符合预期', 177 | 201 => '工作线程池未正确初始化(无法执行异步任务)', 178 | 400 => ' POST 请求的正文格式不正确', 179 | 401 => 'access token 未提供', 180 | 403 => 'access token 或 HTTP_X_SIGNATURE 不符合', 181 | 404 => 'API 不存在', 182 | 405 => '该账号id已被禁止,无法对其进行操作', 183 | 406 => 'POST 请求的 Content-Type 不支持', 184 | 500 => '未知错误', 185 | 65535 => '未获取到上报事件发送的上报数据', 186 | ]; 187 | 188 | if (!array_key_exists($errorCode, $msg)) { 189 | $msg = '未知状态码'; 190 | } 191 | 192 | $msg = $msg[$errorCode]; 193 | 194 | $response = new Response($code, $message, $errorCode, $msg, $data); 195 | return $response; 196 | } 197 | 198 | public static function ok(array $data = []) 199 | { 200 | $response = new Response(0, 'success', 200, 'success', $data); 201 | return $response; 202 | } 203 | 204 | public static function notFoundResourceError() 205 | { 206 | return Response::response(400, 404, []); 207 | } 208 | 209 | public static function accessTokenError() 210 | { 211 | return Response::response(400, 403, []); 212 | } 213 | 214 | public static function signatureError() 215 | { 216 | return Response::response(400, 403, []); 217 | } 218 | 219 | public static function accessTokenNoneError() 220 | { 221 | return Response::response(400, 401, []); 222 | } 223 | 224 | public static function banAccountError($data = []) 225 | { 226 | return Response::response(400, 405, $data); 227 | } 228 | 229 | public static function contentTypeError() 230 | { 231 | return Response::response(400, 406, []); 232 | } 233 | 234 | public static function pluginServerError($data = []) 235 | { 236 | return Response::response(500, -2333, $data); 237 | } 238 | 239 | public static function eventMissParamsError($data = []) 240 | { 241 | return Response::response(100, 65535, $data); 242 | } 243 | 244 | 245 | public static function NotExitsSwoolError($data = []) 246 | { 247 | return Response::response(500, -1000, $data); 248 | } 249 | 250 | public static function NotBeCliError($data = []) 251 | { 252 | return Response::response(500, -1001, $data); 253 | } 254 | 255 | public static function NotExitsPcntlError($data = []) 256 | { 257 | return Response::response(500, -1002, $data); 258 | } 259 | 260 | public static function error($data = []) 261 | { 262 | return Response::response(500, 500, $data); 263 | } 264 | 265 | } -------------------------------------------------------------------------------- /src/Core/Whitelist.php: -------------------------------------------------------------------------------- 1 | isWhiteList; 45 | } 46 | 47 | /** 48 | * @param bool $isWhiteList 49 | */ 50 | public function setIsWhiteList(bool $isWhiteList) 51 | { 52 | $this->isWhiteList = $isWhiteList; 53 | } 54 | 55 | /** 56 | * @return array 57 | */ 58 | public function getPrivateWhiteList(): array 59 | { 60 | return $this->privateWhiteList; 61 | } 62 | 63 | /** 64 | * @param array $privateWhiteList 65 | */ 66 | public function setPrivateWhiteList(array $privateWhiteList) 67 | { 68 | $this->privateWhiteList = $privateWhiteList; 69 | } 70 | 71 | /** 72 | * @return array 73 | */ 74 | public function getGroupWhiteList(): array 75 | { 76 | return $this->groupWhiteList; 77 | } 78 | 79 | /** 80 | * @param array $groupWhiteList 81 | */ 82 | public function setGroupWhiteList(array $groupWhiteList) 83 | { 84 | $this->groupWhiteList = $groupWhiteList; 85 | } 86 | 87 | /** 88 | * @return array 89 | */ 90 | public function getDiscussWhiteList(): array 91 | { 92 | return $this->discussWhiteList; 93 | } 94 | 95 | /** 96 | * @param array $discussWhiteList 97 | */ 98 | public function setDiscussWhiteList(array $discussWhiteList) 99 | { 100 | $this->discussWhiteList = $discussWhiteList; 101 | } 102 | 103 | } -------------------------------------------------------------------------------- /src/Core/isCanSend.php: -------------------------------------------------------------------------------- 1 | isWhiteList() && !in_array($userId, $this->getPrivateWhiteList())) { 19 | return false; 20 | } 21 | if (!$this->isWhiteList() && $this->isBlackList() && in_array($userId, $this->getPrivateBlackList())) { 22 | return false; 23 | } 24 | return true; 25 | } 26 | 27 | public function isCanSendGroup(int $groupId): bool 28 | { 29 | if ($this->isWhiteList() && !in_array($groupId, $this->getGroupWhiteList())) { 30 | return false; 31 | } 32 | if (!$this->isWhiteList() && $this->isBlackList() && in_array($groupId, $this->getGroupBlackList())) { 33 | return false; 34 | } 35 | return true; 36 | } 37 | 38 | public function isCanSendDiscuss(int $discussId): bool 39 | { 40 | if ($this->isWhiteList() && !in_array($discussId, $this->getDiscussWhiteList())) { 41 | return false; 42 | } 43 | if (!$this->isWhiteList() && $this->isBlackList() && in_array($discussId, $this->getDiscussBlackList())) { 44 | return false; 45 | } 46 | return true; 47 | } 48 | } -------------------------------------------------------------------------------- /src/Light/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/functions.php: -------------------------------------------------------------------------------- 1 | QQ = new QQ(new GuzzleProtocol($this->url, $this->accessToken, $this->secret)); 43 | $this->faker = Factory::create(); 44 | } 45 | 46 | public function testSendPrivateMsg() 47 | { 48 | $text = $this->faker->text; 49 | $response = $this->QQ->sendPrivateMsg($this->devQQ, $text, false); 50 | $this->assertInstanceOf(Response::class, $response); 51 | } 52 | 53 | public function testSendPrivateMsgAsync() 54 | { 55 | $text = $this->faker->text; 56 | $response = $this->QQ->sendPrivateMsgAsync($this->devQQ, $text, false); 57 | $this->assertInstanceOf(Response::class, $response); 58 | } 59 | 60 | 61 | public function testSendGroupMsg() 62 | { 63 | $text = $this->faker->text; 64 | $response = $this->QQ->sendGroupMsg($this->devGroupId, $text, false); 65 | $this->assertInstanceOf(Response::class, $response); 66 | } 67 | 68 | public function testSendGroupMsgAsync() 69 | { 70 | $text = $this->faker->text; 71 | $response = $this->QQ->sendGroupMsgAsync($this->devGroupId, $text, false); 72 | $this->assertInstanceOf(Response::class, $response); 73 | } 74 | 75 | public function testSendDiscussMsg() 76 | { 77 | $text = $this->faker->text; 78 | $response = $this->QQ->sendDiscussMsg($this->devDiscussId, $text, false); 79 | $this->assertInstanceOf(Response::class, $response); 80 | } 81 | 82 | public function testSendDiscussMsgAsync() 83 | { 84 | $text = $this->faker->text; 85 | $response = $this->QQ->sendDiscussMsgAsync($this->devDiscussId, $text, false); 86 | $this->assertInstanceOf(Response::class, $response); 87 | } 88 | 89 | public function testSendMsg() 90 | { 91 | $text = $this->faker->text; 92 | $response = $this->QQ->sendMsg('private', $this->devQQ, $text, false); 93 | $this->assertInstanceOf(Response::class, $response); 94 | } 95 | 96 | public function testSendMsgAsync() 97 | { 98 | $text = $this->faker->text; 99 | $response = $this->QQ->sendMsgAsync('private', $this->devQQ, $text, false); 100 | $this->assertInstanceOf(Response::class, $response); 101 | } 102 | 103 | public function testDeleteMsg() 104 | { 105 | $text = $this->faker->text; 106 | $response = $this->QQ->sendPrivateMsg($this->devQQ, $text, false); 107 | $data = $response->getData(); 108 | $this->messageId = $data['message_id']; 109 | $response = $this->QQ->deleteMsg($this->messageId); 110 | $this->assertInstanceOf(Response::class, $response); 111 | } 112 | 113 | public function testDeleteGroupMsg() 114 | { 115 | $text = $this->faker->text; 116 | $response = $this->QQ->sendGroupMsg($this->devGroupId, $text, false); 117 | $data = $response->getData(); 118 | $this->messageGroupId = $data['message_id']; 119 | $response = $this->QQ->deleteGroupMsg($this->devGroupId, $this->messageGroupId); 120 | $this->assertInstanceOf(Response::class, $response); 121 | } 122 | 123 | public function testSendLike() 124 | { 125 | $response = $this->QQ->sendLike($this->devQQ, 1); 126 | $this->assertInstanceOf(Response::class, $response); 127 | } 128 | 129 | public function testSendShake() 130 | { 131 | $response = $this->QQ->sendShake($this->devQQ); 132 | $this->assertInstanceOf(Response::class, $response); 133 | } 134 | 135 | public function testSetQQSignature() 136 | { 137 | $text = $this->faker->text; 138 | $response = $this->QQ->setQQSignature($text); 139 | $this->assertInstanceOf(Response::class, $response); 140 | } 141 | 142 | public function testSetFriendName() 143 | { 144 | $name = $this->faker->name; 145 | $response = $this->QQ->setFriendName($this->devQQ, $name); 146 | $this->assertInstanceOf(Response::class, $response); 147 | } 148 | 149 | // public function testDeleteFriend() 150 | // { 151 | // $response = $this->QQ->deleteFriend($this->devQQ); 152 | // $this->assertInstanceOf(Response::class, $response); 153 | // } 154 | 155 | public function testAddFriend() 156 | { 157 | $name = $this->faker->name; 158 | $response = $this->QQ->addFriend($this->devQQ, $name); 159 | $this->assertInstanceOf(Response::class, $response); 160 | } 161 | 162 | public function testAddGroup() 163 | { 164 | $name = $this->faker->name; 165 | $response = $this->QQ->addGroup($this->devGroupId, $name); 166 | $this->assertInstanceOf(Response::class, $response); 167 | } 168 | 169 | // public function testSetGroupKick() 170 | // { 171 | // $name = $this->faker->name; 172 | // $response = $this->QQ->setGroupKick($this->devGroupId, $this->devQQ, $name); 173 | // $this->assertInstanceOf(Response::class, $response); 174 | // } 175 | 176 | public function testSetGroupBan() 177 | { 178 | $response = $this->QQ->setGroupBan($this->devGroupId, $this->devQQ, 1); 179 | $this->assertInstanceOf(Response::class, $response); 180 | } 181 | 182 | // public function testSetGroupAnonymousBan() 183 | // { 184 | // $response = $this->QQ->setGroupAnonymousBan($this->devGroupId, $this->flagId); 185 | // $this->assertInstanceOf(Response::class, $response); 186 | // } 187 | 188 | public function testSetGroupWholeBan() 189 | { 190 | $response = $this->QQ->setGroupWholeBan($this->devGroupId); 191 | $this->assertInstanceOf(Response::class, $response); 192 | } 193 | 194 | public function testSetGroupWholeOpen() 195 | { 196 | $response = $this->QQ->setGroupWholeBan($this->devGroupId, false); 197 | $this->assertInstanceOf(Response::class, $response); 198 | } 199 | 200 | public function testSetGroupAdmin() 201 | { 202 | $response = $this->QQ->setGroupAdmin($this->devGroupId, $this->devQQ); 203 | $this->assertInstanceOf(Response::class, $response); 204 | } 205 | 206 | public function testSetGroupAnonymous() 207 | { 208 | $response = $this->QQ->setGroupAnonymous($this->devGroupId); 209 | $this->assertInstanceOf(Response::class, $response); 210 | } 211 | 212 | public function testSetGroupCard() 213 | { 214 | $name = $this->faker->name; 215 | $response = $this->QQ->setGroupCard($this->devGroupId, $this->devQQ, $name); 216 | $this->assertInstanceOf(Response::class, $response); 217 | } 218 | 219 | // public function testSetGroupLeave() 220 | // { 221 | // $response = $this->QQ->setGroupLeave($this->devGroupId, false); 222 | // $this->assertInstanceOf(Response::class, $response); 223 | // } 224 | 225 | // public function testSetRemoveGroup() 226 | // { 227 | // $response = $this->QQ->setRemoveGroup($this->devGroupId); 228 | // $this->assertInstanceOf(Response::class, $response); 229 | // } 230 | 231 | public function testSetGroupSpecialTitle() 232 | { 233 | $name = $this->faker->name; 234 | $response = $this->QQ->setGroupSpecialTitle($this->devGroupId, $this->devQQ, $name); 235 | $this->assertInstanceOf(Response::class, $response); 236 | } 237 | 238 | public function testSetDiscussName() 239 | { 240 | $name = $this->faker->name; 241 | $response = $this->QQ->setDiscussName($this->devDiscussId, $name); 242 | $this->assertInstanceOf(Response::class, $response); 243 | } 244 | 245 | // public function testSetDiscussLeave() 246 | // { 247 | // $response = $this->QQ->setDiscussLeave($this->devDiscussId); 248 | // $this->assertInstanceOf(Response::class, $response); 249 | // } 250 | 251 | // public function testSetFriendAddRequest() 252 | // { 253 | // $remark = $this->faker->title; 254 | // $response = $this->QQ->setFriendAddRequest($this->flagId, true, $remark); 255 | // $this->assertInstanceOf(Response::class, $response); 256 | // } 257 | // 258 | // public function testSetGroupAddRequest() 259 | // { 260 | // $reason = $this->faker->title; 261 | // $response = $this->QQ->setGroupAddRequest($this->flagId, true, $reason); 262 | // $this->assertInstanceOf(Response::class, $response); 263 | // } 264 | 265 | public function testGetQQLoginInfo() 266 | { 267 | $response = $this->QQ->getQQLoginInfo(); 268 | $this->assertInstanceOf(Response::class, $response); 269 | } 270 | 271 | public function testGetStrangerInfo() 272 | { 273 | $response = $this->QQ->getStrangerInfo($this->devQQ); 274 | $this->assertInstanceOf(Response::class, $response); 275 | } 276 | 277 | public function testGetQQInfo() 278 | { 279 | $response = $this->QQ->getQQInfo($this->devQQ); 280 | $this->assertInstanceOf(Response::class, $response); 281 | } 282 | 283 | public function testGetGroupList() 284 | { 285 | $response = $this->QQ->getGroupList(); 286 | $this->assertInstanceOf(Response::class, $response); 287 | } 288 | 289 | public function testGetGroupMemberInfo() 290 | { 291 | $response = $this->QQ->getGroupMemberInfo($this->devGroupId, $this->devQQ); 292 | $this->assertInstanceOf(Response::class, $response); 293 | } 294 | 295 | public function testGetGroupMemberList() 296 | { 297 | $response = $this->QQ->getGroupMemberList($this->devGroupId); 298 | $this->assertInstanceOf(Response::class, $response); 299 | } 300 | 301 | public function testInviteFriendIntoGroup() 302 | { 303 | $response = $this->QQ->inviteFriendIntoGroup($this->devGroupId, $this->devQQ); 304 | $this->assertInstanceOf(Response::class, $response); 305 | } 306 | 307 | public function testGetCookies() 308 | { 309 | $response = $this->QQ->getCookies(); 310 | $this->assertInstanceOf(Response::class, $response); 311 | } 312 | 313 | public function testGetBkn() 314 | { 315 | $response = $this->QQ->getBkn(); 316 | $this->assertInstanceOf(Response::class, $response); 317 | } 318 | 319 | public function testGetClientKey() 320 | { 321 | $response = $this->QQ->getClientKey(); 322 | $this->assertInstanceOf(Response::class, $response); 323 | } 324 | 325 | public function testGetCredentials() 326 | { 327 | $response = $this->QQ->getCredentials(); 328 | $this->assertInstanceOf(Response::class, $response); 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /tests/GuzzleProtocolTest.php: -------------------------------------------------------------------------------- 1 | protocol = new GuzzleProtocol($this->url, $this->accessToken, $this->secret); 38 | $this->faker = Factory::create(); 39 | } 40 | 41 | public function testSend() 42 | { 43 | $message = $this->faker->text; 44 | $response = $this->protocol->send(Url::send_private_msg, [ 45 | 'user_id' => $this->devQQ, 46 | 'message' => $message, 47 | 'auto_escape' => false, 48 | ]); 49 | 50 | $this->assertInstanceOf(Response::class, $response); 51 | } 52 | 53 | } 54 | --------------------------------------------------------------------------------