├── 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 |