错误返回值分析:
{"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;
}
}