├── .gitignore ├── src ├── Constants.php ├── Config.php ├── GroupChat.php ├── Messages │ ├── TextMessage.php │ ├── FeedCardMessage.php │ ├── BaseMessage.php │ ├── LinkMessage.php │ ├── ActionCardMessage.php │ ├── ButtonsActionCardMessage.php │ └── MarkdownMessage.php ├── At.php └── ChatBot.php ├── .editorconfig ├── composer.json ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | .idea 3 | .DS_Store 4 | .php_cs.cache 5 | composer.lock 6 | -------------------------------------------------------------------------------- /src/Constants.php: -------------------------------------------------------------------------------- 1 | '***', 16 | 'other' => '***', 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "baiyutang/dingtalk-chatbot", 3 | "description": "The sdk for dingtalk chat robot. developed by object-oriented method. 钉钉群自定义机器人: 采用面向对象的开发方法", 4 | "homepage": "https://github.com/baiyutang/dingtalk-robot", 5 | "keywords": [ 6 | "ding talk", 7 | "dingtalk", 8 | "dingtalk-notice", 9 | "dingtalk-robot", 10 | "dingtalk-chatbot" 11 | ], 12 | "authors": [ 13 | { 14 | "name": "baiyutang" 15 | } 16 | ], 17 | "require": { 18 | "php": ">=5.3.0", 19 | "ext-curl": "*", 20 | "ext-json": "*" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "DingTalkRobot\\": "src/" 25 | } 26 | }, 27 | "type": "library", 28 | "license": "MIT" 29 | } 30 | -------------------------------------------------------------------------------- /src/GroupChat.php: -------------------------------------------------------------------------------- 1 | setToken($name); 23 | } 24 | 25 | /** 26 | * @return string 27 | */ 28 | public function getToken() 29 | { 30 | return $this->token; 31 | } 32 | 33 | /** 34 | * @param string $param 35 | */ 36 | private function setToken($param) 37 | { 38 | $this->token = isset(Config::$groups[$param]) ? Config::$groups[$param] : $param; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Messages/TextMessage.php: -------------------------------------------------------------------------------- 1 | msgBody['content'] = $content; 24 | } 25 | 26 | return $this; 27 | } 28 | 29 | /** 30 | * 选填 31 | * @param At $at 32 | * @return $this 33 | */ 34 | public function setAt($at) 35 | { 36 | $this->at = $at->get(); 37 | 38 | return $this; 39 | } 40 | 41 | protected function validate() 42 | { 43 | } 44 | 45 | /** 46 | * @return void 47 | */ 48 | protected function setMsgType() 49 | { 50 | $this->msgType = 'text'; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/At.php: -------------------------------------------------------------------------------- 1 | data['atMobiles'][] = $mobile; 27 | } 28 | 29 | return $this; 30 | } 31 | 32 | /** 33 | * 选填 34 | * 设置 @ 所有人时,@ 单独手机号会失效 35 | * @param bool $isAtAll @所有人时:true,否则为:false 36 | * @return $this 37 | */ 38 | public function setAtAll($isAtAll) 39 | { 40 | $this->data['isAtAll'] = (bool)$isAtAll; 41 | 42 | return $this; 43 | } 44 | 45 | /** 46 | * @return array 获取设置的 @ 数据 47 | */ 48 | public function get() 49 | { 50 | return $this->data; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 白玉堂 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 | -------------------------------------------------------------------------------- /src/Messages/FeedCardMessage.php: -------------------------------------------------------------------------------- 1 | msgBody['links'][] = array( 25 | 'title' => $title, 26 | 'messageURL' => $messageUrl, 27 | 'picURL' => $picUrl, 28 | ); 29 | } 30 | 31 | return $this; 32 | } 33 | 34 | protected function validate() 35 | { 36 | } 37 | /** 38 | * @return void 39 | */ 40 | protected function setMsgType() 41 | { 42 | $this->msgType = 'feedCard'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Messages/BaseMessage.php: -------------------------------------------------------------------------------- 1 | setMsgType(); 29 | } 30 | 31 | /** 32 | * @return array 33 | */ 34 | final public function packageData() 35 | { 36 | $this->validate(); 37 | $data = array(); 38 | $data['msgtype'] = $this->msgType; 39 | $data[$this->msgType] = $this->msgBody; 40 | !empty($this->at) && $data['at'] = $this->at; 41 | 42 | return $data; 43 | } 44 | 45 | /** 46 | * 子类需要实现该方法定义消息类型 47 | * @return void 48 | */ 49 | abstract protected function setMsgType(); 50 | 51 | /** 52 | * 做一些校验类的工作,具体逻辑各自处理 53 | * @return mixed 54 | */ 55 | abstract protected function validate(); 56 | } 57 | -------------------------------------------------------------------------------- /src/Messages/LinkMessage.php: -------------------------------------------------------------------------------- 1 | msgBody['title'] = $title; 21 | } 22 | 23 | return $this; 24 | } 25 | 26 | /** 27 | * 必填 28 | * @param string $text 消息内容,如果太长只会部分展示 29 | * @return $this 30 | */ 31 | public function setText($text) 32 | { 33 | if ($text) { 34 | $this->msgBody['text'] = $text; 35 | } 36 | 37 | return $this; 38 | } 39 | 40 | /** 41 | * 必填 42 | * @param string $url 点击消息跳转的URL 43 | * @return $this 44 | */ 45 | public function setActionUrl($url) 46 | { 47 | if ($url) { 48 | $this->msgBody['messageUrl'] = $url; 49 | } 50 | 51 | return $this; 52 | } 53 | 54 | /** 55 | * 选填 56 | * @param string $url 图片URL 57 | * @return $this 58 | */ 59 | public function setPicUrl($url) 60 | { 61 | if ($url) { 62 | $this->msgBody['picUrl'] = $url; 63 | } 64 | 65 | return $this; 66 | } 67 | 68 | protected function validate() 69 | { 70 | } 71 | /** 72 | * @return void 73 | */ 74 | protected function setMsgType() 75 | { 76 | $this->msgType = 'link'; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Messages/ActionCardMessage.php: -------------------------------------------------------------------------------- 1 | msgBody['title'] = $title; 20 | 21 | return $this; 22 | } 23 | 24 | /** 25 | * 必填 26 | * @param string $text markdown格式的消息 27 | * @return $this 28 | */ 29 | public function setText($text) 30 | { 31 | $text && $this->msgBody['text'] = $text; 32 | 33 | return $this; 34 | } 35 | 36 | /** 37 | * 必填 38 | * @param string $singleTitle 39 | * @return $this 40 | */ 41 | public function setSingleTitle($singleTitle) 42 | { 43 | $singleTitle && $this->msgBody['singleTitle'] = $singleTitle; 44 | 45 | return $this; 46 | } 47 | 48 | /** 49 | * 必填 50 | * @param string $singleURL 点击singleTitle按钮触发的URL 51 | * @return $this 52 | */ 53 | public function setSingleURL($singleURL) 54 | { 55 | $singleURL && $this->msgBody['singleURL'] = $singleURL; 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * @param string $hideAvatar 0-正常发消息者头像,1-隐藏发消息者头像 62 | * @return $this 63 | */ 64 | public function setHideAvatar($hideAvatar) 65 | { 66 | $this->msgBody['hideAvatar'] = (int)$hideAvatar; 67 | 68 | return $this; 69 | } 70 | 71 | /** 72 | * @return void 73 | */ 74 | protected function setMsgType() 75 | { 76 | $this->msgType = 'actionCard'; 77 | } 78 | 79 | protected function validate() 80 | { 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DingtalkChatBot 钉钉群自定义机器人 2 | 3 | 采用面向对象的开发方法 4 | 5 | ## 功能列表 6 | - [x] 实现原始 text / markdown / link / action card 类型消息发送 7 | - [x] 消息设置可支持链式调用 8 | - [x] 单独设置 @ 的数据,亦可链式调用设置多个 @ 的手机号。注意:设置 @ 所有人时,@ 单独手机号会失效 9 | - [x] 发送群可指根据配置随意指定 10 | - [x] 机器人token均可配置 11 | 12 | ## 用法 13 | > 1. `git clone git@github.com:baiyutang/dingtalk-robot.git` 或 `composer require baiyutang/dingtalk-robot` 14 | > 2. `src/config.php` 文件中 `$groups` 数组中,修改钉钉群机器人为推送的目标群 token 15 | > 3. 参照示例组装代码 16 | 17 | ## 配置钉钉群的两种方式 18 | * 修改 `Config.php` 文件中数组,配置 `array('group_name'=>'...your token...')`,然后 `$client = new ChatBot('group_name')`; 19 | * 支持直接指定token,当 `$client = new ChatBot('param')`,当构造函数接收到的参数不在 `Config.php` 的 `$groups` 数组的键中,会被认定是一个 token 20 | 21 | ## 示例 22 | ```php 23 | // text 类型 24 | use DingTalkRobot\At; 25 | use DingTalkRobot\GroupChat; 26 | use DingTalkRobot\Messages\TextMessage; 27 | use DingTalkRobot\ChatBot; 28 | 29 | // 链式调用设置 @ 多个手机号 30 | $at = new At(); 31 | $at->setMobile('181****3753') 32 | ->setMobile('181****3751'); 33 | 34 | // 链式调用设置消息内容 35 | $message = new TextMessage(); 36 | $message->setContent('我就是我, 是不一样的烟火') 37 | ->setAt($at); 38 | 39 | $client = new ChatBot(); 40 | // 可以指定群,若不设置则发送默认的群 41 | $client->send($message, new GroupChat('other')); 42 | ``` 43 | --- 44 | ```php 45 | // markdown 类型 46 | use DingTalkRobot\GroupChat; 47 | use DingTalkRobot\Messages\MarkdownMessage; 48 | use DingTalkRobot\ChatBot; 49 | 50 | $markdown = new MarkdownMessage(); 51 | $markdown->setTitle('杭州天气') 52 | ->setText("#### 杭州天气\n" . 53 | "> 9度,西北风1级,空气良89,相对温度73%\n" . 54 | "> ![screenshot](http://tinyurl.com/y4lbucte)\n" . 55 | "> 10点20分发布 [天气](http://www.thinkpage.cn/)"); 56 | 57 | $client = new ChatBot(); 58 | 59 | $client->send($markdown, new GroupChat()); 60 | ``` 61 | ## 相关 62 | * [钉钉开发文档:自定义机器人](https://open-doc.dingtalk.com/microapp/serverapi2/qf2nxq#-9) 63 | 64 | ## License 65 | [MIT license](LICENSE) 66 | -------------------------------------------------------------------------------- /src/Messages/ButtonsActionCardMessage.php: -------------------------------------------------------------------------------- 1 | msgBody['title'] = $title; 20 | 21 | return $this; 22 | } 23 | 24 | /** 25 | * 必填 26 | * @param string $text markdown格式的消息 27 | * @return $this 28 | */ 29 | public function setText($text) 30 | { 31 | $text && $this->msgBody['text'] = $text; 32 | 33 | return $this; 34 | } 35 | 36 | 37 | /** 38 | * 必填 39 | * 调用多次设置多个按钮 40 | * @param array $button ['title' => '按钮显示文本', 'actionURL' => '按钮跳转地址'] 41 | */ 42 | public function setButton(array $button) 43 | { 44 | $this->msgBody['btns'][] = $button; 45 | } 46 | 47 | /** 48 | * @param string $hideAvatar 0-正常发消息者头像,1-隐藏发消息者头像 49 | * @return $this 50 | */ 51 | public function setHideAvatar($hideAvatar) 52 | { 53 | $this->msgBody['hideAvatar'] = (int)$hideAvatar; 54 | 55 | return $this; 56 | } 57 | 58 | /** 59 | * @param string $btnOrientation 0-按钮竖直排列,1-按钮横向排列 60 | * @return $this 61 | */ 62 | public function setBtnOrientation($btnOrientation) 63 | { 64 | $this->msgBody['btnOrientation'] = (int)$btnOrientation; 65 | 66 | return $this; 67 | } 68 | 69 | /** 70 | * @return void 71 | */ 72 | protected function validate() 73 | { 74 | } 75 | 76 | /** 77 | * @return void 78 | */ 79 | protected function setMsgType() 80 | { 81 | $this->msgType = 'actionCard'; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Messages/MarkdownMessage.php: -------------------------------------------------------------------------------- 1 | msgBody['title'] = $title; 23 | } 24 | 25 | return $this; 26 | } 27 | 28 | /** 29 | * 必填 30 | * @see https://open-doc.dingtalk.com/microapp/serverapi2/qf2nxq#-6 31 | * @param string $text markdown格式的消息,目前只支持md语法的子集 32 | * @return $this 33 | */ 34 | public function setText($text) 35 | { 36 | if ($text) { 37 | $this->msgBody['text'] = $text; 38 | } 39 | 40 | return $this; 41 | } 42 | 43 | /** 44 | * 选填 45 | * @param At $at 46 | * @return $this 47 | */ 48 | public function setAt($at) 49 | { 50 | $this->at = $at->get(); 51 | 52 | return $this; 53 | } 54 | 55 | protected function validate() 56 | { 57 | $this->handleAt(); 58 | } 59 | 60 | protected function handleAt() 61 | { 62 | if (empty($this->at)) { 63 | return null; 64 | } 65 | 66 | if (isset($this->at['isAtAll']) && $this->at['isAtAll'] === true) { 67 | unset($this->at['atMobiles']); 68 | $this->msgBody['text'] .= "\n\n@all"; 69 | } else { 70 | if (!empty($this->at['atMobiles'])) { 71 | $mobiles = array_unique($this->at['atMobiles']); 72 | $this->msgBody['text'] .= "\n\n@" . implode(" @", $mobiles); 73 | } 74 | } 75 | } 76 | 77 | /** 78 | * @return void 79 | */ 80 | protected function setMsgType() 81 | { 82 | $this->msgType = 'markdown'; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/ChatBot.php: -------------------------------------------------------------------------------- 1 | packageData(); 21 | $url = str_replace('ACCESS_TOKEN', $group->getToken(), Constants::URL); 22 | return $this->postJson($url, $data); 23 | } 24 | 25 | /** 26 | * @param string $url 27 | * @param array $params 28 | * @return bool|false|string 29 | */ 30 | private function postJson($url, $params) 31 | { 32 | try { 33 | $ret = $this->curlJson($url, $params); 34 | } catch (\Exception $exception) { 35 | $ret = json_encode(array('errcode' => $exception->getCode(), 'errmsg' => $exception->getMessage())); 36 | } 37 | 38 | return $ret; 39 | } 40 | 41 | /** 42 | * @param $url 43 | * @param array $postFields 44 | * @return bool|string 45 | * @throws \Exception 46 | */ 47 | private function curlJson($url, $postFields = array()) 48 | { 49 | $postFields = json_encode($postFields); 50 | $header = array( 51 | "Content-Type: application/json; charset=utf-8", 52 | "Content-Length:" . strlen($postFields) 53 | ); 54 | $ch = curl_init(); 55 | if ($ch === false) { 56 | return false; 57 | } 58 | curl_setopt($ch, CURLOPT_URL, $url); 59 | curl_setopt($ch, CURLOPT_FAILONERROR, false); 60 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 61 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 62 | curl_setopt($ch, CURLOPT_POST, true); 63 | curl_setopt($ch, CURLOPT_HTTPHEADER, $header); 64 | $postFields && curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields); 65 | $response = curl_exec($ch); 66 | if (curl_errno($ch)) { 67 | throw new \Exception(curl_error($ch), 0); 68 | } else { 69 | $httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); 70 | if (200 !== $httpStatusCode) { 71 | throw new \Exception($response, $httpStatusCode); 72 | } 73 | } 74 | curl_close($ch); 75 | return $response; 76 | } 77 | } 78 | --------------------------------------------------------------------------------