BeautyFuture

微信web协议分析和实现微信机器人(微信网页版 wx2.qq.com)

 
 参考:https://www.jianshu.com/p/43f54e4b3dc1  http://www.07net01.com/2016/01/1201188.html  http://www.cnblogs.com/xiaozhi_5638/p/4923811.html https://segmentfault.com/a/1190000011996725?utm_source=tuicool&utm_medium=referral
 错误返回值分析:
{"Ret": 0,"ErrMsg": ""} 成功
{"Ret": -14,"ErrMsg": ""} ticket 错误
{"Ret": 1,"ErrMsg": ""} 传入参数 错误
{"Ret": 1100"ErrMsg": ""}未登录提示
{"Ret": 1101,"ErrMsg": ""}(可能:1未检测到登陆?)
{"Ret": 1102,"ErrMsg": ""}(可能:cookie值无效?)
(若返回为空,则说明协议头存在问题)
1.打开首页,分配一个随机uuid,
2.根据该uuid获取二维码图片。
3.微信客户端扫描该图片,在客户端确认登录。
4.浏览器不停的调用一个接口,如果返回登录成功,则调用登录接口
5.此时可以获取联系人列表,可以发送消息。然后不断调用同步接口。
6.如果同步接口有返回,则可以获取新消息,然后继续调用同步接口。

 

 

WebWechat API

1. 获取UUID(参考方法 getUUID)

 

 

2. 显示二维码(参考方法 showQrCode)

 

 

3. 等待登录(参考方法 waitForLogin)这里是微信确认登录

 

 

 

4. 登录获取Cookie(参考方法 login)

5. 微信初始化(参考方法 wxInit)

返回数据(JSON):

{
    "BaseResponse": {
        "Ret": 0,
        "ErrMsg": ""
    },
    "Count": 11,
    "ContactList": [...],
    "SyncKey": {
        "Count": 4,
        "List": [
            {
                "Key": 1,
                "Val": 635705559
            },
            ...
        ]
    },
    "User": {
        "Uin": xxx,
        "UserName": xxx,
        "NickName": xxx,
        "HeadImgUrl": xxx,
        "RemarkName": "",
        "PYInitial": "",
        "PYQuanPin": "",
        "RemarkPYInitial": "",
        "RemarkPYQuanPin": "",
        "HideInputBarFlag": 0,
        "StarFriend": 0,
        "Sex": 1,
        "Signature": "Apt-get install B",
        "AppAccountFlag": 0,
        "VerifyFlag": 0,
        "ContactFlag": 0,
        "WebWxPluginSwitch": 0,
        "HeadImgFlag": 1,
        "SnsFlag": 17
    },
    "ChatSet": xxx,
    "SKey": xxx,
    "ClientVersion": 369297683,
    "SystemTime": 1453124908,
    "GrayScale": 1,
    "InviteStartCount": 40,
    "MPSubscribeMsgCount": 2,
    "MPSubscribeMsgList": [...],
    "ClickReportInterval": 600000
}

//这一步中获取 SyncKey, User 后面的消息监听用。

6. 开启微信状态通知(参考方法 wxStatusNotify)

7. 获取联系人列表(参考方法 getContact)

返回数据(JSON):

{
    "BaseResponse": {
        "Ret": 0,
        "ErrMsg": ""
    },
    "MemberCount": 334,
    "MemberList": [
        {
            "Uin": 0,
            "UserName": xxx,
            "NickName": "Urinx",
            "HeadImgUrl": xxx,
            "ContactFlag": 3,
            "MemberCount": 0,
            "MemberList": [],
            "RemarkName": "",
            "HideInputBarFlag": 0,
            "Sex": 0,
            "Signature": "我是二蛋",
            "VerifyFlag": 8,
            "OwnerUin": 0,
            "PYInitial": "URINX",
            "PYQuanPin": "Urinx",
            "RemarkPYInitial": "",
            "RemarkPYQuanPin": "",
            "StarFriend": 0,
            "AppAccountFlag": 0,
            "Statues": 0,
            "AttrStatus": 0,
            "Province": "",
            "City": "",
            "Alias": "Urinxs",
            "SnsFlag": 0,
            "UniFriend": 0,
            "DisplayName": "",
            "ChatRoomId": 0,
            "KeyWord": "gh_",
            "EncryChatRoomId": ""
        },
        ...
    ],
    "Seq": 0
}

8.消息检查(参考方法 syncCheck)

返回数据(String):

window.synccheck={retcode:"xxx",selector:"xxx"}

retcode:
    0 正常
    1100 失败/登出微信
selector:
    0 正常
    2 新的消息
    7 进入/离开聊天界面

9. 获取最新消息(参考方法 webwxsync)

返回数据(JSON):

{
    \'BaseResponse\': {\'ErrMsg\': \'\', \'Ret\': 0},
    \'SyncKey\': {
        \'Count\': 7,
        \'List\': [
            {\'Val\': 636214192, \'Key\': 1},
            ...
        ]
    },
    \'ContinueFlag\': 0,
    \'AddMsgCount\': 1,
    \'AddMsgList\': [
        {
            \'FromUserName\': \'\',
            \'PlayLength\': 0,
            \'RecommendInfo\': {...},
            \'Content\': "", 
            \'StatusNotifyUserName\': \'\',
            \'StatusNotifyCode\': 5,
            \'Status\': 3,
            \'VoiceLength\': 0,
            \'ToUserName\': \'\',
            \'ForwardFlag\': 0,
            \'AppMsgType\': 0,
            \'AppInfo\': {\'Type\': 0, \'AppID\': \'\'},
            \'Url\': \'\',
            \'ImgStatus\': 1,
            \'MsgType\': 51,
            \'ImgHeight\': 0,
            \'MediaId\': \'\', 
            \'FileName\': \'\',
            \'FileSize\': \'\',
            ...
        },
        ...
    ],
    \'ModChatRoomMemberCount\': 0,
    \'ModContactList\': [],
    \'DelContactList\': [],
    \'ModChatRoomMemberList\': [],
    \'DelContactCount\': 0,
    ...
}

10. 发送消息(参考方法 webwxsendmsg)

返回数据(JSON):

{
    "BaseResponse": {
        "Ret": 0,
        "ErrMsg": ""
    },
    ...
}

 

 

实例代码:

 <?php
    /**
    * Created by Marcelo.
    * User: Marcelo
    * Date: 16/9/30
    * Time: 下午4:28
    * mail:462025277@qq.com
    */

    namespace Home\Api;
    class Wx{
        function getMillisecond()
        {
            list($t1, $t2) = explode(\' \', microtime());
            return $t2 . ceil(($t1 * 1000));
        }

        private $appid = \'wx782c26e4c19acffb\';

        /**
         * 获取唯一的uuid用于生成二维码
         * @return $uuid
         */
        public function get_uuid()
        {
            $url = \'https://login.weixin.qq.com/jslogin\';
            $url .= \'?appid=\' . $this->appid;
            $url .= \'&fun=new\';
            $url .= \'&lang=zh_CN\';
            $url .= \'&_=\' . time();

            $content = $this->curlPost($url);
            //也可以使用正则匹配
            $content = explode(\';\', $content);

            $content_uuid = explode(\'"\', $content[1]);

            $uuid = $content_uuid[1];

            return $uuid;
        }

        /**
         * 生成二维码
         * @param $uuid
         * @return img
         */
        public function qrcode($uuid)
        {
            $url = \'https://login.weixin.qq.com/qrcode/\' . $uuid . \'?t=webwx\';
            $img = "<img class=\'img\' src=" . $url . "/>";
            return $img;
        }

        /**
         * 扫描登录
         * @param $uuid
         * @param string $icon
         * @return array code 408:未扫描;201:扫描未登录;200:登录成功; icon:用户头像
         */
        public function login($uuid, $icon = \'true\')
        {
            $url = \'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=\' . $icon . \'&r=\' . ~time() . \'&uuid=\' . $uuid . \'&tip=0&_=\' . getMillisecond();
            $content = $this->curlPost($url);
            preg_match(\'/\d+/\', $content, $match);
            $code = $match[0];
            preg_match(\'/([\\'"])([^\\'"\.]*?)\1/\', $content, $icon);
            $user_icon = $icon[2];
            if ($user_icon) {
                $data = array(
                    \'code\' => $code,
                    \'icon\' => $user_icon,
                );
            } else {
                $data[\'code\'] = $code;
            }
            echo json_encode($data);
        }

        /**
         * 登录成功回调
         * @param $uuid
         * @return array $callback
         */
        public function get_uri($uuid)
        {
            $url = \'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?uuid=\' . $uuid . \'&tip=0&_=e\' . time();
            $content = $this->curlPost($url);
            $content = explode(\';\', $content);
            $content_uri = explode(\'"\', $content[1]);
            $uri = $content_uri[1];

            preg_match("~^https:?(//([^/?#]*))?~", $uri, $match);
            $https_header = $match[0];
            $post_url_header = $https_header . "/cgi-bin/mmwebwx-bin";

            $new_uri = explode(\'scan\', $uri);
            $uri = $new_uri[0] . \'fun=new&scan=\' . time();
            $getXML = $this->curlPost($uri);

            $XML = simplexml_load_string($getXML);

            $callback = array(
                \'post_url_header\' => $post_url_header,
                \'Ret\' => (array)$XML,
            );
            return $callback;
        }

        /**
         * 获取post数据
         * @param array $callback
         * @return object $post
         */
        public function post_self($callback)
        {
            $post = new \stdClass;
            $Ret = $callback[\'Ret\'];
            $status = $Ret[\'ret\'];
            if ($status == \'1203\') {
                $this->error(\'未知错误,请2小时后重试\');
            }
            if ($status == \'0\') {
                $post->BaseRequest = array(
                    \'Uin\' => $Ret[\'wxuin\'],
                    \'Sid\' => $Ret[\'wxsid\'],
                    \'Skey\' => $Ret[\'skey\'],
                    \'DeviceID\' => \'e\' . rand(10000000, 99999999) . rand(1000000, 9999999),
                );

                $post->skey = $Ret[\'skey\'];

                $post->pass_ticket = $Ret[\'pass_ticket\'];

                $post->sid = $Ret[\'wxsid\'];

                $post->uin = $Ret[\'wxuin\'];

                return $post;
            }
        }

        /**
         * 初始化
         * @param $post
         * @return json $json
         */
        public function wxinit($post)
        {

            $url = $_SESSION[\'https_header\'] . \'/cgi-bin/mmwebwx-bin/webwxinit?pass_ticket=\' . $post->pass_ticket . \'&skey=\' . $post->skey . \'&r=\' . time();

            $post = array(
                \'BaseRequest\' => $post->BaseRequest,
            );
            $json = $this->curlPost($url, $post);

            return $json;
        }

        /**
         * 获取MsgId
         * @param $post
         * @param $json
         * @param $post_url_header
         * @return array $data
         */
        public
        function wxstatusnotify($post, $json, $post_url_header)
        {
            $init = json_decode($json, true);

            $User = $init[\'User\'];
            $url = $post_url_header . \'/webwxstatusnotify?lang=zh_CN&pass_ticket=\' . $post->pass_ticket;

            $params = array(
                \'BaseRequest\' => $post->BaseRequest,
                "Code" => 3,
                "FromUserName" => $User[\'UserName\'],
                "ToUserName" => $User[\'UserName\'],
                "ClientMsgId" => time()
            );

            $data = $this->curlPost($url, $params);

            $data = json_decode($data, true);

            return $data;
        }

        /**
         * 获取联系人
         * @param $post
         * @param $post_url_header
         * @return array $data
         */
        public function webwxgetcontact($post, $post_url_header)
        {

            $url = $post_url_header . \'/webwxgetcontact?pass_ticket=\' . $post->pass_ticket . \'&seq=0&skey=\' . $post->skey . \'&r=\' . time();

            $params[\'BaseRequest\'] = $post->BaseRequest;

            $data = $this->curlPost($url, $params);

            return $data;
        }

        /**
         * 获取当前活跃群信息
         * @param $post
         * @param $post_url_header
         * @param $group_list 从获取联系人和初始化中获取
         * @return array $data
         */
        public function webwxbatchgetcontact($post, $post_url_header, $group_list)
        {

            $url = $post_url_header . \'/webwxbatchgetcontact?type=ex&lang=zh_CN&r=\' . time() . \'&pass_ticket=\' . $post->pass_ticket;

            $params[\'BaseRequest\'] = $post->BaseRequest;

            $params[\'Count\'] = count($group_list);

            foreach ($group_list as $key => $value) {
                if ($value[MemberCount] == 0) {
                    $params[\'List\'][] = array(
                        \'UserName\' => $value[\'UserName\'],
                        \'ChatRoomId\' => "",
                    );
                }
                $params[\'List\'][] = array(
                    \'UserName\' => $value[\'UserName\'],
                    \'EncryChatRoomId\' => "",
                );

            }

            $data = $this->curlPost($url, $params);

            $data = json_decode($data, true);

            return $data;
        }

        /**
         * 心跳检测 0正常;1101失败/登出;2新消息;7不要耍手机了我都收不到消息了;
         * @param $post
         * @param $SyncKey 初始化方法中获取
         * @return array $status
         */
        public
        function synccheck($post, $SyncKey)
        {
            if (!$SyncKey[\'List\']) {
                $SyncKey = $_SESSION[\'json\'][\'SyncKey\'];
            }
            foreach ($SyncKey[\'List\'] as $key => $value) {
                if ($key == 1) {
                    $SyncKey_value = $value[\'Key\'] . \'_\' . $value[\'Val\'];
                } else {
                    $SyncKey_value .= \'|\' . $value[\'Key\'] . \'_\' . $value[\'Val\'];
                }

            }

            $header = array(
                \'0\' => \'https://webpush.wx2.qq.com\',
                \'1\' => \'https://webpush.wx.qq.com\',
            );

            foreach ($header as $key => $value) {

                $url = $value . "/cgi-bin/mmwebwx-bin/synccheck?r=" . getMillisecond() . "&skey=" . urlencode($post->skey) . "&sid=" . $post->sid . "&deviceid=" . $post->BaseRequest[\'DeviceID\'] . "&uin=" . $post->uin . "&synckey=" . urlencode($SyncKey_value) . "&_=" . getMillisecond();

                $data[] = $this->curlPost($url);
            }

            foreach ($data as $k => $val) {

                $rule = \'/window.synccheck={retcode:"(\d+)",selector:"(\d+)"}/\';

                preg_match($rule, $data[$k], $match);

                if ($match[1] == \'0\') {
                    $retcode = $match[1];
                    $selector = $match[2];
                }
            }

            $status = array(
                \'ret\' => $retcode,
                \'sel\' => $selector,
            );

            return $status;
        }

        /**
         * 获取最新消息
         * @param $post
         * @param $post_url_header
         * @param $SyncKey
         * @return array $data
         */
        public
        function webwxsync($post, $post_url_header, $SyncKey)
        {
            $url = $post_url_header . \'/webwxsync?sid=\' . $post->sid . \'&skey=\' . $post->skey . \'&pass_ticket=\' . $post->pass_ticket;

            $params = array(
                \'BaseRequest\' => $post->BaseRequest,
                \'SyncKey\' => $SyncKey,
                \'rr\' => ~time(),
            );
            $data = $this->curlPost($url, $params);

            return $data;
        }


        /**
         * 发送消息
         * @param $post
         * @param $post_url_header
         * @param $to 发送人
         * @param $word
         * @return array $data
         */
        public
        function webwxsendmsg($post, $post_url_header, $to, $word)
        {

            header("Content-Type: text/html; charset=UTF-8");

            $url = $post_url_header . \'/webwxsendmsg?pass_ticket=\' . $post->pass_ticket;

            $clientMsgId = getMillisecond() * 1000 + rand(1000, 9999);

            $params = array(
                \'BaseRequest\' => $post->BaseRequest,
                \'Msg\' => array(
                    "Type" => 1,
                    "Content" => $word,
                    "FromUserName" => $post->User[\'UserName\'],
                    "ToUserName" => $to,
                    "LocalID" => $clientMsgId,
                    "ClientMsgId" => $clientMsgId
                ),
                \'Scene\' => 0,
            );

            $data = $this->curlPost($url, $params, 1);

            return $data;
        }

        /**
         *退出登录
         * @param $post
         * @param $post_url_header
         * @return bool
         */
        public function wxloginout($post, $post_url_header)
        {
            $url = $post_url_header . \'/webwxlogout?redirect=1&type=1&skey=\' . urlencode($post->skey);
            $param = array(
                \'sid\' => $post->sid,
                \'uin\' => $post->uin,
            );
            $this->curlPost($url, $param);

            return true;
        }


        public function curlPost($url, $data, $is_gbk, $timeout = 30, $CA = false)
        {
            $cacert = getcwd() . \'/cacert.pem\'; //CA根证书

            $SSL = substr($url, 0, 8) == "https://" ? true : false;

            $header = \'ContentType: application/json; charset=UTF-8\';

            $ch = curl_init();
            curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout - 2);
            if ($SSL && $CA) {
                curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);   // 只信任CA颁布的证书
                curl_setopt($ch, CURLOPT_CAINFO, $cacert); // CA根证书(用来验证的网站证书是否是CA颁布)
                curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); // 检查证书中是否设置域名,并且是否与提供的主机名匹配
            } else if ($SSL && !$CA) {
                curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 信任任何证书
                curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, true); // 检查证书中是否设置域名
            }
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_HTTPHEADER, array(\'Expect:\')); //避免data数据过长问题
            if ($data) {
                if ($is_gbk) {
                    $data = json_encode($data);

                } else {
                    $data = json_encode($data);
                }
                curl_setopt($ch, CURLOPT_POST, true);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
            }

            //curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); //data with URLEncode
            $ret = curl_exec($ch);
            curl_close($ch);
            return $ret;
        }

    }

 

分类:

技术点:

相关文章: