├── screenshot ├── hotest.png ├── menu.png ├── random.png ├── recent.png ├── search.png ├── visit_wp.png ├── weishequ.png └── custom_menu.png ├── LICENSE ├── README.md ├── functions.php ├── wechat-robot.php └── wechat.php /screenshot/hotest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangvsa/wechat-robot/HEAD/screenshot/hotest.png -------------------------------------------------------------------------------- /screenshot/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangvsa/wechat-robot/HEAD/screenshot/menu.png -------------------------------------------------------------------------------- /screenshot/random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangvsa/wechat-robot/HEAD/screenshot/random.png -------------------------------------------------------------------------------- /screenshot/recent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangvsa/wechat-robot/HEAD/screenshot/recent.png -------------------------------------------------------------------------------- /screenshot/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangvsa/wechat-robot/HEAD/screenshot/search.png -------------------------------------------------------------------------------- /screenshot/visit_wp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangvsa/wechat-robot/HEAD/screenshot/visit_wp.png -------------------------------------------------------------------------------- /screenshot/weishequ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangvsa/wechat-robot/HEAD/screenshot/weishequ.png -------------------------------------------------------------------------------- /screenshot/custom_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangvsa/wechat-robot/HEAD/screenshot/custom_menu.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 王辰 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Wechat Robot 2 | ============ 3 | Wechat Robot是一个WordPress插件,实现了WP站点和微信公众号的连接,支持自动回复、文章搜索、订阅回复、微社区入口等功能。 4 | Wechat Robot使用PHP语言,稍作改动可以即可以在非Wordpress环境下使用。 5 | 6 | 7 | 8 | ## 功能列表 9 | - 自定义菜单 10 | - 访问WP网站 11 | - 查看最新文章 12 | - 查看随机文章 13 | - 查看热门文章 14 | - 关键字搜索 15 | - 访问FB社区 16 | - 订阅时显示欢迎菜单 17 | 18 | 19 | 20 | ## 功能截图 21 | 本插件原为[Freebuf](http://www.freebuf.com)编写,在这直接用Freebuf公众号演示,大家也可以微信关注Freebuf自己体验。 22 | 23 | - 自定义菜单
24 | ![自定义菜单](https://github.com/wangvsa/wechat-robot/blob/master/screenshot/custom_menu.png) 25 | - 显示欢迎菜单,输入任何不被识别的字符串均可
26 | ![菜单](https://github.com/wangvsa/wechat-robot/blob/master/screenshot/menu.png) 27 | - 回复0可以访问主站
28 | ![访问主站](https://github.com/wangvsa/wechat-robot/blob/master/screenshot/visit_wp.png) 29 | - 回复1查看最新文章
30 | ![最新文章](https://github.com/wangvsa/wechat-robot/blob/master/screenshot/recent.png)) 31 | - 回复2查看随机文章
32 | ![随机文章](https://github.com/wangvsa/wechat-robot/blob/master/screenshot/random.png) 33 | - 回复3查看热门文章(本月)
34 | ![热门文章](https://github.com/wangvsa/wechat-robot/blob/master/screenshot/hotest.png) 35 | - 回复4+关键字进行搜索
36 | ![搜索文章](https://github.com/wangvsa/wechat-robot/blob/master/screenshot/search.png) 37 | - 回复5访问FB社区
38 | ![访问FB社区](https://github.com/wangvsa/wechat-robot/blob/master/screenshot/weishequ.png) 39 | 40 | 41 | 42 | ## 安装使用 43 | 1. 下载本插件,编辑文件wechat-robot.php
44 | * 修改WechatRobot构造函数参数中的`appid`和`secret` 45 | * 若支持菜单则参照[微信API](http://mp.weixin.qq.com/wiki/index.php?title=%E8%87%AA%E5%AE%9A%E4%B9%89%E8%8F%9C%E5%8D%95%E5%88%9B%E5%BB%BA%E6%8E%A5%E5%8F%A3)填写`create_menu`中的菜单参数,然后按需修改`onClick`函数来响应菜单点击事件。 46 | * 若不支持菜单,则注释掉`create_menu`调用即可。 47 | * 按需修改`onText`函数和`onSubscribe`函数,响应用户信息和订阅事件。 48 | 2. 将wechat-robot目录上传到WP目录`/wp-content/plugins/`,然后在WP后台开启本插件。 49 | 3. 进入微信公众账号后台,开启高级功能->开发模式,填写token和url
50 | token填写`wechat`,url填写`你的网站地址/?wechat`,比如`http://www.freebuf.com/?wechat`,注意不要省略`http://` 51 | 52 | ## 详细文档 53 | 见[API wiki](https://github.com/wangvsa/wechat-robot/wiki/API文档) 54 | 55 | -------------------------------------------------------------------------------- /functions.php: -------------------------------------------------------------------------------- 1 | ID); 38 | if ( $thumbnail_id ) { 39 | $thumb = wp_get_attachment_image_src($thumbnail_id, $size); 40 | $thumb = $thumb[0]; 41 | } 42 | 43 | if (empty($thumb)) { 44 | $thumb = 'http://www.freebuf.com/buf/themes/freebuf/images/logo2.jpg'; 45 | } 46 | 47 | return $thumb; 48 | } 49 | 50 | /** 51 | * GET 请求 52 | * @param string $url 53 | * @return string $result 54 | */ 55 | 56 | function http_get($url) { 57 | $oCurl = curl_init(); 58 | if(stripos($url,"https://")!==FALSE) { 59 | curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, false); 60 | curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, false); 61 | } 62 | 63 | curl_setopt($oCurl, CURLOPT_URL, $url); 64 | curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1); 65 | 66 | $result = curl_exec($oCurl); 67 | $status = curl_getinfo($oCurl); 68 | curl_close($oCurl); 69 | 70 | if(intval($status["http_code"])==200) 71 | return $result; 72 | return $status["http_code"]; 73 | } 74 | 75 | 76 | /** 77 | * POST 请求 78 | * @param string $url 79 | * @param array $param 80 | * @return string $result 81 | */ 82 | function http_post($url, $param) { 83 | 84 | $oCurl = curl_init(); 85 | 86 | if(stripos($url,"https://")!==FALSE){ 87 | curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, false); 88 | curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, false); 89 | } 90 | 91 | if (is_string($param)) { 92 | $strPOST = $param; 93 | } else { 94 | $aPOST = array(); 95 | foreach($param as $key=>$val) { 96 | $aPOST[] = $key."=".urlencode($val); 97 | } 98 | $strPOST = join("&", $aPOST); 99 | } 100 | 101 | curl_setopt($oCurl, CURLOPT_URL, $url); 102 | curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1 ); 103 | curl_setopt($oCurl, CURLOPT_POST,true); 104 | curl_setopt($oCurl, CURLOPT_POSTFIELDS,$strPOST); 105 | 106 | $result = curl_exec($oCurl); 107 | $status = curl_getinfo($oCurl); 108 | curl_close($oCurl); 109 | if(intval($status["http_code"])==200) 110 | return $result; 111 | return $status["http_code"]; 112 | } 113 | ?> 114 | -------------------------------------------------------------------------------- /wechat-robot.php: -------------------------------------------------------------------------------- 1 | create_menu('{ 19 | "button":[ 20 | { 21 | "name":"阅读文章", 22 | "sub_button":[ 23 | { 24 | "type":"click", 25 | "name":"最新文章", 26 | "key":"MENU_RECENT_POSTS" 27 | }, 28 | { 29 | "type":"click", 30 | "name":"随机文章", 31 | "key":"MENU_RANDOM_POSTS" 32 | }, 33 | { 34 | "type":"click", 35 | "name":"热门文章", 36 | "key":"MENU_HOTEST_POSTS" 37 | } 38 | ] 39 | }, 40 | { 41 | "type":"view", 42 | "name":"FB主页", 43 | "url":"http://www.freebuf.com" 44 | }, 45 | { 46 | "type":"view", 47 | "name":"微社区", 48 | "url":"http://wx.wsq.qq.com/187329269" 49 | }] 50 | }'); 51 | $robot->run(); 52 | } 53 | } 54 | 55 | 56 | class WechatRobot extends Wechat { 57 | 58 | protected function queryAndResponse( $arg ) { 59 | $the_query = new WP_Query( $arg ); 60 | if( $the_query->have_posts() ) { 61 | 62 | $counter = 0; 63 | $items = array(); 64 | 65 | while( $the_query->have_posts() ) { 66 | $the_query->the_post(); 67 | global $post; 68 | 69 | $title = get_the_title(); 70 | $link = get_permalink(); 71 | $excerpt = wechat_get_excerpt( get_the_excerpt() ); 72 | 73 | if ( $counter == 0 ) { 74 | $thumb = wechat_get_thumb($post, array(640, 320)); 75 | } else { 76 | $thumb = wechat_get_thumb($post, array(80, 80)); 77 | } 78 | 79 | $new_item = new NewsResponseItem($title, $excerpt, $thumb, $link); 80 | array_push($items, $new_item); 81 | 82 | // 最多显示3篇 83 | if ( ++$counter == 3 ) 84 | break; 85 | } 86 | 87 | $this->responseNews($items); 88 | 89 | } else { 90 | $this->responseText("不好意思~没有找到您想要的东东~请换个关键字再试试?"); 91 | } 92 | 93 | wp_reset_postdata(); 94 | 95 | } 96 | 97 | protected function searchPosts( $key ) { 98 | $arg = array( 's' => $key ); 99 | $this->queryAndResponse( $arg ); 100 | } 101 | 102 | protected function recentPosts() { 103 | $arg = array( 'ignore_sticky_posts'=>1, 'showposts' => 3 ); 104 | $this->queryAndResponse( $arg ); 105 | } 106 | 107 | protected function randomPosts() { 108 | $arg = array ( 'ignore_sticky_posts' => 1, 'orderby' => 'rand' ); 109 | $this->queryAndResponse( $arg ); 110 | } 111 | 112 | protected function hotestPosts() { // 本月最热门 113 | $arg = array ( 114 | 'ignore_sticky_posts' => 1, 115 | 'orderby' => 'comment_count', 116 | 'year' => date('Y'), 117 | 'monthnum' => date('m') 118 | ); 119 | $this->queryAndResponse( $arg ); 120 | } 121 | 122 | 123 | protected function onText() { 124 | $msg = $this->getRequest('content'); 125 | if( $msg == '0' ) { 126 | $this->responseText("访问FreeBuf黑客与极客,请点击下面连接:\n http://www.freebuf.com "); 127 | } else if( $msg == '5' ) { 128 | $this->responseText("访问FB社区,请点击下面连接:\n http://wx.wsq.qq.com/187329269 "); 129 | } else if( $msg == '1' ) { 130 | $this->recentPosts(); 131 | } else if ( $msg == '2' ) { 132 | $this->randomPosts(); 133 | } else if ( $msg == '3' ) { 134 | $this->hotestPosts(); 135 | } else if ( strncmp($msg, '4', 1)==0 ) { // starts with '4' 136 | if( strlen($msg) == 1 ) { 137 | $this->responseText("您没有输入关键字,要输入[4关键字]进行搜索哦,比如 4黑客 "); 138 | } else { 139 | $this->searchPosts( substr($msg, 1, strlen($msg)-1) ); 140 | } 141 | } else { 142 | $this->responseText("欢迎关注FreeBuf黑客与极客\n 回复[0]访问FreeBuf\n 回复[1]查看最新文章". 143 | "\n 回复[2]查看随机文章\n 回复[3]查看热门文章\n 回复[4关键字]搜索文章\n 回复[5]访问FB社区\n 回复其他内容查看本菜单"); 144 | } 145 | } 146 | 147 | protected function onSubscribe() { 148 | $this->responseText("欢迎关注FreeBuf黑客与极客\n 回复[0]访问FreeBuf\n 回复[1]查看最新文章". 149 | "\n 回复[2]查看随机文章\n 回复[3]查看热门文章\n 回复[4关键字]搜索文章\n 回复[5]访问FB社区 回复其他内容查看本菜单"); 150 | } 151 | 152 | // 点击菜单 153 | protected function onClick() { 154 | $key = $this->getRequest('EventKey'); 155 | if($key=="MENU_RECENT_POSTS") { 156 | $this->recentPosts(); 157 | } else if($key=="MENU_RANDOM_POSTS") { 158 | $this->randomPosts(); 159 | } else if($key=="MENU_HOTEST_POSTS") { 160 | $this->hotestPosts(); 161 | } 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /wechat.php: -------------------------------------------------------------------------------- 1 | appid = $appid; 35 | $this->secret = $secret; 36 | if (!$this->validateSignature($token)) { 37 | exit('签名验证失败'); 38 | } 39 | 40 | if ($this->isValid()) { 41 | // 网址接入验证 42 | exit($_GET['echostr']); 43 | } 44 | 45 | if (!isset($GLOBALS['HTTP_RAW_POST_DATA'])) { 46 | exit('缺少数据'); 47 | } 48 | 49 | $this->debug = $debug; 50 | set_error_handler(array(&$this, 'errorHandler')); 51 | // 设置错误处理函数,将错误通过文本消息回复显示 52 | 53 | $xml = (array) simplexml_load_string($GLOBALS['HTTP_RAW_POST_DATA'], 'SimpleXMLElement', LIBXML_NOCDATA); 54 | 55 | $this->request = array_change_key_case($xml, CASE_LOWER); 56 | // 将数组键名转换为小写,提高健壮性,减少因大小写不同而出现的问题 57 | } 58 | 59 | /** 60 | * 判断此次请求是否为验证请求 61 | * 62 | * @return boolean 63 | */ 64 | private function isValid() { 65 | return isset($_GET['echostr']); 66 | } 67 | 68 | /** 69 | * 验证此次请求的签名信息 70 | * 71 | * @param string $token 验证信息 72 | * @return boolean 73 | */ 74 | private function validateSignature($token) { 75 | $signature = $_GET["signature"]; 76 | $timestamp = $_GET["timestamp"]; 77 | $nonce = $_GET["nonce"]; 78 | 79 | $tmpArr = array($token, $timestamp, $nonce); 80 | sort($tmpArr, SORT_STRING); 81 | $tmpStr = implode( $tmpArr ); 82 | $tmpStr = sha1( $tmpStr ); 83 | 84 | if( $tmpStr == $signature ){ 85 | return true; 86 | } else { 87 | return false; 88 | } 89 | } 90 | 91 | /** 92 | * 获取本次请求中的参数,不区分大小 93 | * 94 | * @param string $param 参数名,默认为无参 95 | * @return mixed 96 | */ 97 | protected function getRequest($param = FALSE) { 98 | if ($param === FALSE) { 99 | return $this->request; 100 | } 101 | 102 | $param = strtolower($param); 103 | 104 | if (isset($this->request[$param])) { 105 | return $this->request[$param]; 106 | } 107 | 108 | return NULL; 109 | } 110 | 111 | /** 112 | * 获取token,使用文件缓存机制 113 | * 首先检查文件是否存在,存在则检查token是否过期 114 | * 若过期或文件不存在,则向服务器请求,然后存入文件 115 | * 116 | */ 117 | public function get_access_token() { 118 | $path = WEIXIN_ROBOT_PLUGIN_DIR."/access_token.json"; 119 | // 检查文件并查看token是否过期 120 | if(file_exists($path)) { 121 | $json = file_get_contents($path); 122 | if(!isset($json['access_token']) || !isset($json['time']) || !isset($json['expires_in'])) 123 | return false; 124 | 125 | $array = json_decode($json, true); 126 | $expires_time = intval($array["time"]) + intval($array["expires_in"]) - 100; 127 | $now = time(); 128 | if($now < $expires_time) 129 | return $array["access_token"]; 130 | } 131 | 132 | // 如果文件不存在或者token已经过期则向服务器请求 133 | $result = $this->http_get("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=".$this->appid."&secret=".$this->secret); 134 | if($result) { 135 | $json = json_decode($result, true); 136 | if(!$json || isset($json['errcode'])) 137 | return false; 138 | 139 | $json["time"] = time(); 140 | $json = json_encode($json); 141 | 142 | // 写入文件 143 | $file = fopen($path, "wb"); 144 | if($file!==false) { 145 | fwrite($file, $json); 146 | fclose($file); 147 | } 148 | return $json["access_token"]; 149 | } 150 | return false; 151 | 152 | } 153 | 154 | 155 | /** 156 | * 使用curl实现GET请求 157 | */ 158 | private function http_get($url) { 159 | $oCurl = curl_init(); 160 | if(stripos($url, "https://")!==FALSE) { 161 | curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE); 162 | curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, FALSE); 163 | } 164 | curl_setopt($oCurl, CURLOPT_URL, $url); 165 | curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1); 166 | $content = curl_exec($oCurl); 167 | $status = curl_getinfo($oCurl); 168 | curl_close($oCurl); 169 | if(intval($status["http_code"])==200) 170 | return $content; 171 | else 172 | return false; 173 | } 174 | 175 | 176 | // 创建菜单,根据微信api传入菜单json 177 | public function create_menu($menu_json) { 178 | $access_token = $this->get_access_token(); 179 | $res = http_post("https://api.weixin.qq.com/cgi-bin/menu/create?access_token=".$access_token, $menu_json); 180 | return $res; 181 | } 182 | 183 | public function fetch_menu() { 184 | $access_token = $this->get_access_token(); 185 | $res = http_get("https://api.weixin.qq.com/cgi-bin/menu/get?access_token=".$access_token); 186 | return $res; 187 | } 188 | 189 | 190 | 191 | /** 192 | * 用户关注时触发,用于子类重写 193 | * 194 | * @return void 195 | */ 196 | protected function onSubscribe() {} 197 | 198 | /** 199 | * 用户取消关注时触发,用于子类重写 200 | * 201 | * @return void 202 | */ 203 | protected function onUnsubscribe() {} 204 | 205 | /** 206 | * 收到文本消息时触发,用于子类重写 207 | * 208 | * @return void 209 | */ 210 | protected function onText() {} 211 | 212 | /** 213 | * 收到图片消息时触发,用于子类重写 214 | * 215 | * @return void 216 | */ 217 | protected function onImage() {} 218 | 219 | /** 220 | * 收到地理位置消息时触发,用于子类重写 221 | * 222 | * @return void 223 | */ 224 | protected function onLocation() {} 225 | 226 | /** 227 | * 收到链接消息时触发,用于子类重写 228 | * 229 | * @return void 230 | */ 231 | protected function onLink() {} 232 | 233 | /** 234 | * 收到自定义菜单消息时触发,用于子类重写 235 | * 236 | * @return void 237 | */ 238 | protected function onClick() {} 239 | 240 | /** 241 | * 收到地理位置事件消息时触发,用于子类重写 242 | * 243 | * @return void 244 | */ 245 | protected function onEventLocation() {} 246 | 247 | /** 248 | * 收到语音消息时触发,用于子类重写 249 | * 250 | * @return void 251 | */ 252 | protected function onVoice() {} 253 | 254 | /** 255 | * 扫描二维码时触发,用于子类重写 256 | * 257 | * @return void 258 | */ 259 | protected function onScan() {} 260 | 261 | /** 262 | * 收到未知类型消息时触发,用于子类重写 263 | * 264 | * @return void 265 | */ 266 | protected function onUnknown() {} 267 | 268 | /** 269 | * 回复文本消息 270 | * 271 | * @param string $content 消息内容 272 | * @param integer $funcFlag 默认为0,设为1时星标刚才收到的消息 273 | * @return void 274 | */ 275 | protected function responseText($content, $funcFlag = 0) { 276 | exit(new TextResponse($this->getRequest('fromusername'), $this->getRequest('tousername'), $content, $funcFlag)); 277 | } 278 | 279 | /** 280 | * 回复音乐消息 281 | * 282 | * @param string $title 音乐标题 283 | * @param string $description 音乐描述 284 | * @param string $musicUrl 音乐链接 285 | * @param string $hqMusicUrl 高质量音乐链接,Wi-Fi 环境下优先使用 286 | * @param integer $funcFlag 默认为0,设为1时星标刚才收到的消息 287 | * @return void 288 | */ 289 | protected function responseMusic($title, $description, $musicUrl, $hqMusicUrl, $funcFlag = 0) { 290 | exit(new MusicResponse($this->getRequest('fromusername'), $this->getRequest('tousername'), $title, $description, $musicUrl, $hqMusicUrl, $funcFlag)); 291 | } 292 | 293 | /** 294 | * 回复图文消息 295 | * @param array $items 由单条图文消息类型 NewsResponseItem() 组成的数组 296 | * @param integer $funcFlag 默认为0,设为1时星标刚才收到的消息 297 | * @return void 298 | */ 299 | protected function responseNews($items, $funcFlag = 0) { 300 | exit(new NewsResponse($this->getRequest('fromusername'), $this->getRequest('tousername'), $items, $funcFlag)); 301 | } 302 | 303 | /** 304 | * 分析消息类型,并分发给对应的函数 305 | * 306 | * @return void 307 | */ 308 | public function run() { 309 | switch ($this->getRequest('msgtype')) { 310 | case 'event': 311 | switch ($this->getRequest('event')) { 312 | case 'subscribe': 313 | $this->onSubscribe(); 314 | break; 315 | 316 | case 'unsubscribe': 317 | $this->onUnsubscribe(); 318 | break; 319 | 320 | case 'SCAN': 321 | $this->onScan(); 322 | break; 323 | 324 | case 'LOCATION': 325 | $this->onEventLocation(); 326 | break; 327 | 328 | case 'CLICK': 329 | $this->onClick(); 330 | break; 331 | } 332 | break; 333 | 334 | case 'text': 335 | $this->onText(); 336 | break; 337 | 338 | case 'image': 339 | $this->onImage(); 340 | break; 341 | 342 | case 'location': 343 | $this->onLocation(); 344 | break; 345 | 346 | case 'link': 347 | $this->onLink(); 348 | break; 349 | 350 | case 'voice': 351 | $this->onVoice(); 352 | break; 353 | 354 | default: 355 | $this->onUnknown(); 356 | break; 357 | } 358 | } 359 | 360 | 361 | /** 362 | * 自定义的错误处理函数,将 PHP 错误通过文本消息回复显示 363 | * @param int $level 错误代码 364 | * @param string $msg 错误内容 365 | * @param string $file 产生错误的文件 366 | * @param int $line 产生错误的行数 367 | * @return void 368 | */ 369 | protected function errorHandler($level, $msg, $file, $line) { 370 | if ( ! $this->debug) { 371 | return; 372 | } 373 | 374 | $error_type = array( 375 | // E_ERROR => 'Error', 376 | E_WARNING => 'Warning', 377 | // E_PARSE => 'Parse Error', 378 | E_NOTICE => 'Notice', 379 | // E_CORE_ERROR => 'Core Error', 380 | // E_CORE_WARNING => 'Core Warning', 381 | // E_COMPILE_ERROR => 'Compile Error', 382 | // E_COMPILE_WARNING => 'Compile Warning', 383 | E_USER_ERROR => 'User Error', 384 | E_USER_WARNING => 'User Warning', 385 | E_USER_NOTICE => 'User Notice', 386 | E_STRICT => 'Strict', 387 | E_RECOVERABLE_ERROR => 'Recoverable Error', 388 | E_DEPRECATED => 'Deprecated', 389 | E_USER_DEPRECATED => 'User Deprecated', 390 | ); 391 | 392 | $template = <<responseText(sprintf($template, 401 | $error_type[$level], 402 | $msg, 403 | $file, 404 | $line 405 | )); 406 | } 407 | 408 | } 409 | 410 | /** 411 | * 用于回复的基本消息类型 412 | */ 413 | abstract class WechatResponse { 414 | 415 | protected $toUserName; 416 | protected $fromUserName; 417 | protected $funcFlag; 418 | protected $template; 419 | 420 | public function __construct($toUserName, $fromUserName, $funcFlag) { 421 | $this->toUserName = $toUserName; 422 | $this->fromUserName = $fromUserName; 423 | $this->funcFlag = $funcFlag; 424 | } 425 | 426 | abstract public function __toString(); 427 | } 428 | 429 | /** 430 | * 用于回复的文本消息类型 431 | */ 432 | class TextResponse extends WechatResponse { 433 | 434 | protected $content; 435 | 436 | public function __construct($toUserName, $fromUserName, $content, $funcFlag = 0) { 437 | parent::__construct($toUserName, $fromUserName, $funcFlag); 438 | 439 | $this->content = $content; 440 | $this->template = << 442 | 443 | 444 | %s 445 | 446 | 447 | %s 448 | 449 | XML; 450 | } 451 | 452 | public function __toString() { 453 | return sprintf($this->template, 454 | $this->toUserName, 455 | $this->fromUserName, 456 | time(), 457 | $this->content, 458 | $this->funcFlag 459 | ); 460 | } 461 | 462 | } 463 | 464 | /** 465 | * 用于回复的音乐消息类型 466 | */ 467 | class MusicResponse extends WechatResponse { 468 | 469 | protected $title; 470 | protected $description; 471 | protected $musicUrl; 472 | protected $hqMusicUrl; 473 | 474 | public function __construct($toUserName, $fromUserName, $title, $description, $musicUrl, $hqMusicUrl, $funcFlag) { 475 | parent::__construct($toUserName, $fromUserName, $funcFlag); 476 | 477 | $this->title = $title; 478 | $this->description = $description; 479 | $this->musicUrl = $musicUrl; 480 | $this->hqMusicUrl = $hqMusicUrl; 481 | $this->template = << 483 | 484 | 485 | %s 486 | 487 | 488 | <![CDATA[%s]]> 489 | 490 | 491 | 492 | 493 | %s 494 | 495 | XML; 496 | } 497 | 498 | public function __toString() { 499 | return sprintf($this->template, 500 | $this->toUserName, 501 | $this->fromUserName, 502 | time(), 503 | $this->title, 504 | $this->description, 505 | $this->musicUrl, 506 | $this->hqMusicUrl, 507 | $this->funcFlag 508 | ); 509 | } 510 | 511 | } 512 | 513 | /** 514 | * 用于回复的图文消息类型 515 | */ 516 | class NewsResponse extends WechatResponse { 517 | 518 | protected $items = array(); 519 | 520 | public function __construct($toUserName, $fromUserName, $items, $funcFlag) { 521 | parent::__construct($toUserName, $fromUserName, $funcFlag); 522 | 523 | $this->items = $items; 524 | $this->template = << 526 | 527 | 528 | %s 529 | 530 | %s 531 | 532 | %s 533 | 534 | %s 535 | 536 | XML; 537 | } 538 | 539 | public function __toString() { 540 | return sprintf($this->template, 541 | $this->toUserName, 542 | $this->fromUserName, 543 | time(), 544 | count($this->items), 545 | implode($this->items), 546 | $this->funcFlag 547 | ); 548 | } 549 | 550 | } 551 | 552 | /** 553 | * 单条图文消息类型 554 | */ 555 | class NewsResponseItem { 556 | 557 | protected $title; 558 | protected $description; 559 | protected $picUrl; 560 | protected $url; 561 | protected $template; 562 | 563 | public function __construct($title, $description, $picUrl, $url) { 564 | $this->title = $title; 565 | $this->description = $description; 566 | $this->picUrl = $picUrl; 567 | $this->url = $url; 568 | $this->template = << 570 | <![CDATA[%s]]> 571 | 572 | 573 | 574 | 575 | XML; 576 | } 577 | 578 | public function __toString() { 579 | return sprintf($this->template, 580 | $this->title, 581 | $this->description, 582 | $this->picUrl, 583 | $this->url 584 | ); 585 | } 586 | 587 | } 588 | 589 | ?> 590 | --------------------------------------------------------------------------------