├── README.md ├── index.php ├── src └── wechat.php ├── static ├── chat.css ├── images │ ├── bg.jpg │ └── nango.jpg └── jquery-1.7.2.js └── tpl ├── chat.php └── qrcode.php /README.md: -------------------------------------------------------------------------------- 1 | # webchat-robot 2 | 通过分析网页版微信通讯过程,开发出的微信网页版机器人 3 | /** 4 | * 参考文章http://www.tanhao.me/talk/1466.html/ 5 | * 通讯步骤 6 | * 1,微信服务器返回一个会话ID 7 | * 2.通过会话ID获得二维码 8 | * 3.轮询手机端是否已经扫描二维码并确认在Web端登录 9 | * 4.访问登录地址,获得uin和sid 10 | * 5.初使化微信信息 11 | * 6.获得所有的好友列表 12 | * 7.保持与服务器的信息同步 13 | * 8.获得别人发来的消息 14 | * 9.向用户发送消息 15 | **/ 16 | 17 | 首先简单介绍下使用: 18 | clone项目目录到你的www目录,然后浏览器地址栏输入localhost/webchat-robot/index.php 19 | 扫码,手机端确认登陆,此时你会进入主面板,点击某个好友即可和他聊天。好友回复后机器人将会根据消息进行自动回复。 20 | 21 | 该项目目前只实现了纯文本的消息回复,后续可能会持续开发,另外也会增加机器人的功能,比如添加成语接龙(让你的微信成语接龙所向披靡)、天气查询、快递查询等 22 | 23 | 上边文章提到的通讯过程对于目前的网页版微信的通讯有些出入,有些步骤无法走通,所以还需要自己分析,如果你有兴趣也可以通过firfox、chrome浏览器,F12后,进行分析,那将会更直观,让你也会认识更加清晰。 24 | 25 | 该机器人目前可能会有些bug,但整体是可以使用的,略微有些瑕疵,比如无法获取好友头像(后续会进行开发) 26 | 27 | 另外,抽空我也会专门写篇文章进行简单介绍该项目和网页版微信的通信过程,有兴趣的同学可以关注 www.5none.com 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | getLoginStatus(); 7 | print_r($res);die;*/ 8 | switch ($act) { 9 | case 'index': 10 | //登录页 11 | $uuid = $wechat->getUuid(); 12 | $qrcode = "https://login.weixin.qq.com/qrcode/{$uuid}?t=webwx"; 13 | include_once('tpl/qrcode.php'); 14 | break; 15 | case 'status': 16 | //获取登录状态 17 | $uuid = $_GET['uuid']; 18 | $res = $wechat->getLoginStatus($uuid); 19 | if($res == 201){ 20 | //已扫描,待确认 21 | $data = array('status' => 1); 22 | }elseif (substr_count($res, 'http')) { 23 | //确认成功 24 | $data = array('status' => 2); 25 | }else{ 26 | //待扫描 27 | $data = array('status' => 0); 28 | } 29 | $data['msg'] = $res; 30 | exit(json_encode($data)); 31 | break; 32 | case 'cookies': 33 | //获取用户uin 和 sid 34 | $url = $_POST['url']; 35 | $wxinfo = $wechat->getCookies($url); 36 | $wxinfo['status'] = 1; 37 | 38 | exit(json_encode($wxinfo)); 39 | break; 40 | case 'chat': 41 | //主聊天框页面 42 | include_once('tpl/chat.php'); 43 | break; 44 | case 'init': 45 | //初使化微信信息 46 | $json_info = $wechat->initWebchat(); 47 | exit($json_info); 48 | break; 49 | case 'users': 50 | //获取所有好友列表 51 | $users = $wechat->getContact(); 52 | echo $users; 53 | break; 54 | case 'sync': 55 | //服务器同步 56 | $synckey = $_POST['synckey']; 57 | $message = $wechat->wxsync($synckey); 58 | 59 | exit($message); 60 | break; 61 | case 'send': 62 | $toUsername = $_POST['toUsername']; 63 | $content = $_POST['content']; 64 | 65 | $res = $wechat->sendMessage($toUsername, $content); 66 | exit($res); 67 | break; 68 | case 'avatar': 69 | $uri = $_GET['uri']; 70 | $res = $wechat->getAvatar($uri); 71 | header('Content-Type: image/jpeg'); 72 | imagejpeg($res); 73 | break; 74 | case 'tuling': 75 | //图灵机器人接管消息 76 | $toUsername = $_POST['toUsername']; 77 | $content = $_POST['content']; 78 | if($toUsername != $_SESSION['username']){ 79 | $mes = $wechat->sendMessageToTuling($content); 80 | $res = $wechat->sendMessage($toUsername, $mes); 81 | //拼接上机器人的回话 82 | $tlCon = json_decode($res,true); 83 | $tlCon['tlc'] = $mes; 84 | $tlCon['status'] = 1; 85 | exit(json_encode($tlCon)); 86 | } 87 | exit(json_encode(array('status' => 0))); 88 | break; 89 | default: 90 | # code... 91 | break; 92 | } 93 | 94 | ?> -------------------------------------------------------------------------------- /src/wechat.php: -------------------------------------------------------------------------------- 1 | getMillisecond(); 120 | $str = $this->get($url); 121 | preg_match('/"(.*?)"/',$str,$match); 122 | $_SESSION['uuid'] = $match[1]; 123 | return $match[1]; 124 | } 125 | 126 | /** 127 | * 通过会话ID获得二维码 128 | * @access public 129 | * @return string 130 | **/ 131 | public function getQrcode($uuid){ 132 | $url = 'https://login.weixin.qq.com/qrcode/'.$uuid.'?t=webwx'; 133 | return ""; 134 | } 135 | 136 | /** 137 | * 轮询手机端是否已经扫描二维码并确认在Web端登录 138 | * @access public 139 | * @param $uuid string 用户会话id 140 | * @return mixed 141 | **/ 142 | public function getLoginStatus($uuid = ''){ 143 | $url = sprintf("https://login.wx2.qq.com/cgi-bin/mmwebwx-bin/login?uuid=%s&tip=1&_=%s", $uuid, $this->getMillisecond()); 144 | $res = $this->get($url); 145 | preg_match('/=(.*?);/',$res,$match); 146 | if($match[1] == 200){ 147 | //登陆成功 148 | preg_match('/redirect_uri="(.*?)";/',$res,$match2); 149 | return $match2[1]; 150 | } 151 | return $match[1]; 152 | } 153 | 154 | /** 155 | * 访问登录地址,获得uin和sid,并且保存cookies 156 | * @access public 157 | * @param $url string 登录地址 158 | * @return array 159 | **/ 160 | public function getCookies($url){ 161 | $cookie_jar = dirname(__FILE__)."/".$_SESSION['uuid'].".cookie"; 162 | 163 | $ch = curl_init($url); 164 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 对认证证书来源的检查 165 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 从证书中检查SSL加密算法是否存在 166 | curl_setopt($ch, CURLOPT_HEADER,1);//如果你想把一个头包含在输出中, 167 | curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);//将 curl_exec()获取的信息以文件流的形式返回,而不是直接输出。设置为0是直接输出 168 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); 169 | curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); 170 | curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_jar);//获取的cookie 保存到指定的 文件路径 171 | $content=curl_exec($ch); 172 | if(curl_errno($ch)){ 173 | $info = array('status' => 0, 'msg' => 'Curl error: '.curl_error($ch)); 174 | return $info;//这里是设置个错误信息的反馈 175 | } 176 | 177 | if($content==false){ 178 | $info = array('status' => 0, 'msg' => '无法获取cookies'); 179 | return $info;//这里是设置个错误信息的反馈 180 | } 181 | 182 | //正则匹配出wxuin、wxsid 183 | preg_match('/wxuin=;/iU',$content,$uin); 184 | preg_match('/wxsid=(.*);/iU',$content,$sid); 185 | preg_match('/webwx_data_ticket=(.*);/iU',$content,$webwx); 186 | //@TODO将wxuin、wxsid、webwx_data_ticket存入cookies,以便获取微信头像----暂无效 187 | /*if(preg_match_all('/Set-Cookie:[\s]+([^=]+)=([^;]+)/i', $content,$match)) { 188 | foreach ($match[1] as $key => $cookieKey ) { 189 | setcookie($cookieKey,$match[2][$key],'36000','','.wx.qq.com'); 190 | } 191 | }*/ 192 | //将wxuin、wxsid、webwx_data_ticket存入session 193 | $_SESSION['uin'] = @$uin[1]; 194 | $_SESSION['sid'] = @$sid[1]; 195 | $wxinfo = array( 196 | 'uin' => @$uin[1], 197 | 'sid' => @$sid[1] 198 | ); 199 | curl_close($ch); 200 | return $wxinfo; 201 | } 202 | 203 | /** 204 | * 登录成功,初始化微信信息 205 | * @access public 206 | * @param $uin string 用户uin 207 | * @param $sid string 用户sid 208 | * @return mixed 209 | **/ 210 | public function initWebchat($uin = '', $sid = ''){ 211 | $cookie_jar = dirname(__FILE__)."/".$_SESSION['uuid'].".cookie"; 212 | $url = sprintf("https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=%s", $this->getMillisecond()); 213 | 214 | if(!$uin || !$sid){ 215 | $uin = $_SESSION['uin']; 216 | $sid = $_SESSION['sid']; 217 | } 218 | $data['BaseRequest'] = array( 219 | 'Uin' => $uin, 220 | 'Sid' => $sid, 221 | 'Skey' => '', 222 | 'DeviceID' => 'e189320295398756' 223 | ); 224 | $res = $this->post($url, json_encode($data),$cookie_jar); 225 | //将登陆用户username、nickname存入session中 226 | $user = json_decode($res, true); 227 | $_SESSION['username'] = $user['User']['UserName']; 228 | $_SESSION['nickname'] = $user['User']['NickName']; 229 | 230 | return $res; 231 | } 232 | 233 | /** 234 | * 获取全部联系人 235 | * @access public 236 | * @param $uin string 用户uin 237 | * @param $sid string 用户sid 238 | * @return mixed 239 | **/ 240 | public function getContact($uin = '', $sid = ''){ 241 | $cookie_jar = dirname(__FILE__)."/".$_SESSION['uuid'].".cookie"; 242 | $url = sprintf("https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?lang=zh_CN&r=%s&seq=0", $this->getMillisecond()); 243 | if(!$uin || !$sid){ 244 | 245 | $uin = $_SESSION['uin']; 246 | $sid = $_SESSION['sid']; 247 | } 248 | 249 | $res = $this->post($url, '{}',$cookie_jar); 250 | return $res; 251 | } 252 | 253 | 254 | /** 255 | * 登录成功,保持与服务器的信息同步,获取是否有推送消息等 256 | * @access public 257 | * @param $synckey string 258 | * @return mixed 259 | **/ 260 | public function wxsync($synckey){ 261 | 262 | $uin = $_SESSION['uin']; 263 | $sid = $_SESSION['sid']; 264 | $cookie_jar = dirname(__FILE__)."/".$_SESSION['uuid'].".cookie"; 265 | $url = sprintf("https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=%s", $sid); 266 | 267 | $data['BaseRequest'] = array( 268 | 'Uin' => $uin, 269 | 'Sid' => $sid, 270 | 'Skey' => '', 271 | 'DeviceID' => 'e189320295398756' 272 | ); 273 | $data['SyncKey'] = json_decode($synckey); 274 | $data['rr'] = time(); 275 | $res = $this->post($url, json_encode($data),$cookie_jar); 276 | return $res; 277 | } 278 | 279 | /** 280 | * 发送消息 281 | * @access public 282 | * @param $toUsername string 283 | * @return mixed 284 | **/ 285 | public function sendMessage($toUsername = '', $content = ''){ 286 | $uin = $_SESSION['uin']; 287 | $sid = $_SESSION['sid']; 288 | $cookie = dirname(__FILE__)."/".$_SESSION['uuid'].".cookie"; 289 | $url = sprintf("https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?sid=%s&r=%s",$sid,$this->getMillisecond()); 290 | 291 | $data['BaseRequest'] = array( 292 | 'Uin' => $uin, 293 | 'Sid' => $sid, 294 | 'Skey' => "", 295 | 'DeviceID' => "e287276317582836" 296 | ); 297 | $data['Msg'] = array( 298 | 'ClientMsgId' => $this->getMillisecond(), 299 | 'Content' => $content, 300 | 'FromUserName' => $_SESSION['username'], 301 | 'LocalID' => $this->getMillisecond(), 302 | 'ToUserName' => $toUsername, 303 | 'Type' => 1 304 | ); 305 | $data['Scene'] = 0; 306 | //json_encode JSON_UNESCAPED_UNICODE防止将汉字转义为unicode字符 307 | $res = $this->post($url, json_encode($data,JSON_UNESCAPED_UNICODE),$cookie); 308 | return $res; 309 | } 310 | 311 | /** 312 | * 获取头像 313 | * @access public 314 | * @param $uri string 头像地址 315 | * @return mixed 316 | **/ 317 | public function getAvatar($uri = ''){ 318 | $cookie = dirname(__FILE__)."/".$_SESSION['uuid'].".cookie"; 319 | $url = "https://wx.qq.com".$uri; 320 | $res = $this->get($url, $cookie); 321 | echo $res; 322 | } 323 | 324 | /** 325 | * 图灵机器人 =》文本 326 | * @access public 327 | * @param $toUsername string 328 | * @return mixed 329 | **/ 330 | public function sendMessageToTuling($content = ''){ 331 | $data = array( 332 | 'key' => $this->tlAppkey, 333 | 'info' => $content, 334 | 'userid' => '123456789' 335 | ); 336 | $res = $this->post($this->tlApi,json_encode($data,JSON_UNESCAPED_UNICODE),'',1); 337 | $r = json_decode($res,true); 338 | //文本类 339 | if(isset($r['url'])){ 340 | //存在链接则发送链接 341 | return $r['url']; 342 | } 343 | return $r['text']; 344 | 345 | 346 | } 347 | 348 | //转换为UTF-8 349 | public function characet($data){ 350 | if( !empty($data) ){ 351 | $fileType = mb_detect_encoding($data , array('UTF-8','GBK','LATIN1','BIG5')) ; 352 | if( $fileType != 'UTF-8'){ 353 | $data = mb_convert_encoding($data ,'utf-8' , $fileType); 354 | } 355 | } 356 | return $data; 357 | } 358 | } -------------------------------------------------------------------------------- /static/chat.css: -------------------------------------------------------------------------------- 1 | *, *:before, *:after { 2 | box-sizing: border-box; 3 | } 4 | body, html { 5 | height: 100%; 6 | overflow: hidden; 7 | } 8 | body, ul { 9 | margin: 0; 10 | padding: 0; 11 | } 12 | body { 13 | color: #4d4d4d; 14 | font: 14px/1.4em 'Helvetica Neue', Helvetica, 'Microsoft Yahei', Arial, sans-serif; 15 | background: #f5f5f5 url('images/bg.jpg') no-repeat center; 16 | background-size: cover; 17 | font-smoothing: antialiased; 18 | } 19 | ul { 20 | list-style: none; 21 | } 22 | #chat { 23 | margin: 20px auto; 24 | width: 800px; 25 | height: 600px; 26 | } 27 | #chat { 28 | overflow:hidden; 29 | border-radius:3px 30 | } 31 | #chat .main, #chat .sidebar { 32 | height:100% 33 | } 34 | #chat .sidebar { 35 | float:left; 36 | width:200px; 37 | color:#f4f4f4; 38 | background-color:#2e3238 39 | } 40 | #chat .main { 41 | position:relative; 42 | overflow:hidden; 43 | background-color:#eee 44 | } 45 | #chat .m-text { 46 | position:absolute; 47 | width:100%; 48 | bottom:-30px; 49 | left:0 50 | } 51 | #chat .m-message { 52 | height:calc(100% - 10pc) 53 | } 54 | .m-card { 55 | padding:9pt; 56 | border-bottom:1px solid #24272c 57 | } 58 | .m-card footer { 59 | margin-top:10px 60 | } 61 | .m-card .avatar, .m-card .name { 62 | vertical-align:middle 63 | } 64 | .m-card .avatar { 65 | border-radius:2px 66 | } 67 | .m-card .name { 68 | display:inline-block; 69 | margin:0 0 0 15px; 70 | font-size:1pc; 71 | overflow: hidden; 72 | } 73 | .m-card .search { 74 | padding:0 10px; 75 | width:100%; 76 | font-size:9pt; 77 | color:#fff; 78 | height:30px; 79 | line-height:30px; 80 | border:1px solid #3a3a3a; 81 | border-radius:4px; 82 | outline:0; 83 | background-color:#26292e 84 | } 85 | .m-list li { 86 | padding:9pt 15px; 87 | border-bottom:1px solid #292c33; 88 | cursor:pointer; 89 | -webkit-transition:background-color .1s; 90 | transition:background-color .1s 91 | } 92 | .m-list li:hover { 93 | background-color:hsla(0, 0%, 100%, .03) 94 | } 95 | .m-list li.active { 96 | background-color:hsla(0, 0%, 100%, .1) 97 | } 98 | .m-list .avatar, .m-list .name { 99 | vertical-align:middle 100 | } 101 | .m-list .avatar { 102 | border-radius:2px 103 | } 104 | .m-list .name { 105 | display:inline-block; 106 | margin:0 0 0 15px; 107 | overflow: hidden; 108 | } 109 | .m-text { 110 | height:10pc; 111 | border-top:1px solid #ddd 112 | } 113 | .m-text textarea { 114 | padding:10px; 115 | height:100%; 116 | width:100%; 117 | border:none; 118 | outline:0; 119 | font-family:Micrsofot Yahei; 120 | resize:none 121 | } 122 | .m-message { 123 | padding:10px 15px; 124 | overflow-y:scroll 125 | } 126 | .m-message li { 127 | margin-bottom:15px 128 | } 129 | .m-message .time { 130 | margin:7px 0; 131 | text-align:center 132 | } 133 | .m-message .time>span { 134 | display:inline-block; 135 | padding:0 18px; 136 | font-size:9pt; 137 | color:#fff; 138 | border-radius:2px; 139 | background-color:#dcdcdc 140 | } 141 | .m-message .avatar { 142 | float:left; 143 | margin:10px 10px 0 0; 144 | border-radius:3px 145 | } 146 | .m-message .text { 147 | display:inline-block; 148 | position:relative; 149 | padding:0 10px; 150 | max-width:calc(100% - 40px); 151 | min-height:30px; 152 | line-height:2.5; 153 | font-size:9pt; 154 | text-align:left; 155 | word-break:break-all; 156 | background-color:#fafafa; 157 | border-radius:4px 158 | } 159 | .m-message .text:before { 160 | content:" "; 161 | position:absolute; 162 | top:4px; 163 | right:100%; 164 | border:6px solid transparent; 165 | border-right-color:#fafafa 166 | } 167 | .m-message .self { 168 | text-align:right 169 | } 170 | .m-message .self .avatar { 171 | float:right; 172 | margin:10px 0 0 10px 173 | } 174 | .m-message .self .text { 175 | background-color:#b2e281 176 | } 177 | .m-message .self .text:before { 178 | right:inherit; 179 | left:100%; 180 | border-right-color:transparent; 181 | border-left-color:#b2e281 182 | } 183 | .m-message .nick{ 184 | font-size:12px; 185 | color:#C5C5AD; 186 | } 187 | .users{ 188 | height: 30px; 189 | width: 100%; 190 | position: absolute; 191 | z-index: 9; 192 | background: #fff; 193 | padding: 5px; 194 | } 195 | -------------------------------------------------------------------------------- /static/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nangge/webchat-robot/80b7eb64f195ad151bc10edd0168b9db19e264ff/static/images/bg.jpg -------------------------------------------------------------------------------- /static/images/nango.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nangge/webchat-robot/80b7eb64f195ad151bc10edd0168b9db19e264ff/static/images/nango.jpg -------------------------------------------------------------------------------- /tpl/chat.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | web chat 5 | 6 | 7 | 8 | 9 | 10 |
11 | 22 |
23 |

24 |
25 |
    26 | 27 |
28 |
29 | 30 |
31 | 32 |
33 |
34 |
35 | 36 | 276 | -------------------------------------------------------------------------------- /tpl/qrcode.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | WEB,微信机器人 8 | 9 | 10 | 11 |

让你的微信成为智能机器人

12 |

13 |

请扫描二维码登录

14 |
15 | 16 | 62 | --------------------------------------------------------------------------------