微信實現qq群的qq小冰功能
常玩qq的人應該知道qq群裡可以引入一個“聊天機器人”qq小冰,而後可以在群裡通過@qq小冰來達到調戲的目的
然而尿性的騰訊除了qq之外還有微信這樣一款聊天的軟體
本文目的就是註冊一個微訊號(來作為我們的機器人),將其拉到微信群裡然後通過艾特的功能來實現個人聊天或者客服的目的
做為一個不合格的軟開,怎麼能不用別人的輪子呢(手動斜眼笑)
ok,給出輪子連結https://github.com/cncoder/WeChatBotJava(用Java語言來實現的)
通過這個輪子可以登陸你的微信,獲取聯絡人、監聽訊息並自動回覆
我們的功能是基於此輪子進行的修改
閒話少說
找到類me.biezhi.wechat.service.WechatServiceImpl,其getContact(WechatMeta)方法是用來獲取聯絡人的
此方法中迴圈處理memberlist過濾其中公告號、群聊等賬號,拿到所有使用者contactList
因為我們的目的是實現群聊機器人,因此定義群聊聯絡人groupList並在群聊的判斷語句裡將當前contact加入到list中
迴圈結束後將該list注入wechatContact中去
getContact()方法最後呼叫了私有的getGroup()方法,起初不懂這是要幹嘛,後來見名知意發現這個就是用來獲取群聯絡人的(group嘛)
重寫該方法,呼叫微信的webwxbatchgetcontact api,關於該api的具體使用可以自行百度
下面是該方法的程式碼
/** * 獲取群成員 * @param wechatMeta * @param wechatContact */ private void getGroup(WechatMeta wechatMeta, WechatContact wechatContact) { String url = wechatMeta.getBase_uri() + "/webwxbatchgetcontact?" +"type=ex" +"&r=" +DateKit.getCurrentUnixTime() +"&lang=zh_CN" +"&pass_ticket="+wechatMeta.getPass_ticket(); JSONObject body = new JSONObject(); JSONArray groupList = wechatContact.getGroupList(); List<Map<String, String>> list = new ArrayList<Map<String, String>>(); body.put("BaseRequest", wechatMeta.getBaseRequest()); body.put("Count", groupList.size()); for (int i = 0; i < groupList.size(); i++) { HashMap<String, String> map = new HashMap<String, String>(); map.put("UserName", groupList.get(i).asJSONObject().getString("UserName")); map.put("EncryChatRoomId", ""); list.add(map); } body.put("List", list); HttpRequest request = HttpRequest.post(url).contentType("application/json;charset=utf-8") .header("Cookie", wechatMeta.getCookie()).send(body.toString()); LOGGER.debug(request.toString()); String res = request.body(); request.disconnect(); if (StringKit.isBlank(res)) { throw new WechatException("獲取群資訊失敗"); } LOGGER.debug(res); try { JSONObject jsonObject = JSONKit.parseObject(res); JSONObject BaseResponse = jsonObject.get("BaseResponse").asJSONObject(); if (null != BaseResponse) { int ret = BaseResponse.getInt("Ret", -1); if (ret == 0) { JSONArray contactList = jsonObject.get("ContactList").asArray(); if (null != contactList) { groupList = new JSONArray(); for (int i = 0, len = contactList.size(); i < len; i++) { JSONObject contact = contactList.get(i).asJSONObject(); if (contact.getString("UserName").indexOf("@@") != -1) { JSONArray memberList =contact.get("MemberList").asArray(); for(JSONValue value:memberList) { groupList.add(value); } } } wechatContact.setGroupList(groupList); } } } } catch (Exception e) { throw new WechatException(e); } }
(注意1:groupLIst在這段程式碼中出現兩次,第一次是JSONArray groupList = wechatContact.getGroupList();這裡的groupList是群的相關資訊,比如群名字,群成員列表等,第二次出現時在groupList.add(value);這裡的groupLIst得到的就是群聊中所有成員的資訊,包括暱稱、使用者名稱、群暱稱等
注意2:在實際使用中可能發現這個wechatContact.getGroupList();得到的groupList為空,或者value裡沒有你想要的群的訊息,這是因為這裡得到的是被儲存在通訊錄裡的群的列表,開啟微信的某個群,在“聊天資訊”裡有一個“儲存到通訊錄”功能,這裡開關要開啟,然後才能獲取到裡面的值)
拿到了群成員列表之後,豈不就可以為所欲為了嗎^_^
上文說到獲得的groupList中的每個元素都對應著群中的一個成員,本專案中需要使用的屬性為DisplayName和NickName,其中DisplayName為群暱稱,就是設定的在這個群的暱稱,NickName是個人暱稱也就是微信暱稱,對於設定了群暱稱的群成員就艾特DisplayName,否則艾特NickName。
而群成員在聊天的時候獲得的content為Username+內容的組合形式,也就是通過content的username部分獲得是哪個使用者艾特的機器人,然後根據這個username找到對應grouplist中的某個元素,再得到其displayname或nickname
結尾就是針對該類WechatServiceImpl的完整程式碼
package me.biezhi.wechat.service;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.blade.kit.DateKit;
import com.blade.kit.FileKit;
import com.blade.kit.StringKit;
import com.blade.kit.http.HttpRequest;
import com.blade.kit.json.JSONArray;
import com.blade.kit.json.JSONKit;
import com.blade.kit.json.JSONObject;
import com.blade.kit.json.JSONValue;
import me.biezhi.wechat.Constant;
import me.biezhi.wechat.exception.WechatException;
import me.biezhi.wechat.model.WechatContact;
import me.biezhi.wechat.model.WechatMeta;
import me.biezhi.wechat.robot.MoLiRobot;
import me.biezhi.wechat.robot.Robot;
import me.biezhi.wechat.util.Matchers;
import me.cncoder.record.RecordCon;
public class WechatServiceImpl implements WechatService {
private static final Logger LOGGER = LoggerFactory.getLogger(WechatService.class);
// 茉莉機器人
private Robot robot = new MoLiRobot();
/**
* Step7:獲取聯絡人
* @url https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact
* @method POST
* @data JSON
* @header ContentType: application/json; charset=UTF-8
* @params BaseRequest
* @return JSON
*/
@Override
public WechatContact getContact(WechatMeta wechatMeta) {
String url = wechatMeta.getBase_uri() + "/webwxgetcontact?pass_ticket=" + wechatMeta.getPass_ticket() + "&skey="
+ wechatMeta.getSkey() + "&r=" + DateKit.getCurrentUnixTime();
JSONObject body = new JSONObject();
body.put("BaseRequest", wechatMeta.getBaseRequest());
HttpRequest request = HttpRequest.post(url).contentType("application/json;charset=utf-8")
.header("Cookie", wechatMeta.getCookie()).send(body.toString());
LOGGER.debug(request.toString());
String res = request.body();
request.disconnect();
if (StringKit.isBlank(res)) {
throw new WechatException("獲取聯絡人失敗");
}
LOGGER.debug(res);
WechatContact wechatContact = new WechatContact();
try {
JSONObject jsonObject = JSONKit.parseObject(res);
JSONObject BaseResponse = jsonObject.get("BaseResponse").asJSONObject();
if (null != BaseResponse) {
int ret = BaseResponse.getInt("Ret", -1);
if (ret == 0) {
//成員列表
JSONArray memberList = jsonObject.get("MemberList").asArray();
//聯絡人列表
JSONArray contactList = new JSONArray();
//群成員列表
JSONArray groupList = new JSONArray();
if (null != memberList) {
for (int i = 0, len = memberList.size(); i < len; i++) {
JSONObject contact = memberList.get(i).asJSONObject();
// 公眾號/服務號
if (contact.getInt("VerifyFlag", 0) == 8) {
continue;
}
// 特殊聯絡人
if (Constant.FILTER_USERS.contains(contact.getString("UserName"))) {
continue;
}
// 群聊
if (contact.getString("UserName").indexOf("@@") != -1) {
groupList.add(contact);
}
// 自己
if (contact.getString("UserName").equals(wechatMeta.getUser().getString("UserName"))) {
continue;
}
contactList.add(contact);
}
wechatContact.setContactList(contactList);
wechatContact.setMemberList(memberList);
wechatContact.setGroupList(groupList);
this.getGroup(wechatMeta, wechatContact);
System.out.println(wechatContact.toString());
return wechatContact;
}
}
}
} catch (Exception e) {
throw new WechatException(e);
}
return null;
}
/**
* 獲取群成員
* @param wechatMeta
* @param wechatContact
*/
private void getGroup(WechatMeta wechatMeta, WechatContact wechatContact) {
String url = wechatMeta.getBase_uri() + "/webwxbatchgetcontact?"
+"type=ex"
+"&r=" +DateKit.getCurrentUnixTime()
+"&lang=zh_CN"
+"&pass_ticket="+wechatMeta.getPass_ticket();
JSONObject body = new JSONObject();
JSONArray groupList = wechatContact.getGroupList();
List<Map<String, String>> list = new ArrayList<Map<String, String>>();
body.put("BaseRequest", wechatMeta.getBaseRequest());
body.put("Count", groupList.size());
for (int i = 0; i < groupList.size(); i++) {
HashMap<String, String> map = new HashMap<String, String>();
map.put("UserName", groupList.get(i).asJSONObject().getString("UserName"));
map.put("EncryChatRoomId", "");
list.add(map);
}
body.put("List", list);
HttpRequest request = HttpRequest.post(url).contentType("application/json;charset=utf-8")
.header("Cookie", wechatMeta.getCookie()).send(body.toString());
LOGGER.debug(request.toString());
String res = request.body();
request.disconnect();
if (StringKit.isBlank(res)) {
throw new WechatException("獲取群資訊失敗");
}
LOGGER.debug(res);
try {
JSONObject jsonObject = JSONKit.parseObject(res);
JSONObject BaseResponse = jsonObject.get("BaseResponse").asJSONObject();
if (null != BaseResponse) {
int ret = BaseResponse.getInt("Ret", -1);
if (ret == 0) {
JSONArray contactList = jsonObject.get("ContactList").asArray();
if (null != contactList) {
groupList = new JSONArray();
for (int i = 0, len = contactList.size(); i < len; i++) {
JSONObject contact = contactList.get(i).asJSONObject();
if (contact.getString("UserName").indexOf("@@") != -1) {
JSONArray memberList =contact.get("MemberList").asArray();
for(JSONValue value:memberList) {
groupList.add(value);
}
}
}
wechatContact.setGroupList(groupList);
}
}
}
} catch (Exception e) {
throw new WechatException(e);
}
}
/**
* Step1:獲取UUID uuid是服務端用來標識一次登陸的通訊
* @url https://login.weixin.qq.com/jslogin
* @method Get
* @data URL Encode
* @params
<b>appid</b> : wx782c26e4c19acffb 這個值不變,表示來自微信網頁版
<b>fun</b> : new
<b>lang</b>: zh_CN
<b>_</b> : 時間戳
* @return window.QRLogin.code = 200; window.QRLogin.uuid = "xxx"
*/
@Override
public String getUUID() throws WechatException {
HttpRequest request = HttpRequest.get(Constant.JS_LOGIN_URL, true, "appid", "wx782c26e4c19acffb", "fun", "new",
"lang", "zh_CN", "_", DateKit.getCurrentUnixTime());
LOGGER.debug(request.toString());
String res = request.body();
request.disconnect();
if (StringKit.isNotBlank(res)) {
String code = Matchers.match("window.QRLogin.code = (\\d+);", res);
if (null != code) {
if (code.equals("200")) {
return Matchers.match("window.QRLogin.uuid = \"(.*)\";", res);
} else {
throw new WechatException("錯誤的狀態碼: " + code);
}
}
}
throw new WechatException("獲取UUID失敗");
}
/**
*Step6: 開啟狀態提醒
*@url https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxstatusnotify
*@method POST
*@data JSON
*@header Content-Type: application/json; charset=UTF-8
*@params {
* BaseRequest: { Uin: xxx, Sid: xxx, Skey: xxx, DeviceID: xxx },
* Code: 3,
* FromUserName: 自己的ID,
* ToUserName: 自己的ID,
* ClientMsgId: 時間戳
* }
* @return JSON
*/
@Override
public void openStatusNotify(WechatMeta wechatMeta) throws WechatException {
String url = wechatMeta.getBase_uri() + "/webwxstatusnotify?lang=zh_CN&pass_ticket=" + wechatMeta.getPass_ticket();
JSONObject body = new JSONObject();
body.put("BaseRequest", wechatMeta.getBaseRequest());
body.put("Code", 3);
body.put("FromUserName", wechatMeta.getUser().getString("UserName"));
body.put("ToUserName", wechatMeta.getUser().getString("UserName"));
body.put("ClientMsgId", DateKit.getCurrentUnixTime());
HttpRequest request = HttpRequest.post(url).contentType("application/json;charset=utf-8")
.header("Cookie", wechatMeta.getCookie()).send(body.toString());
LOGGER.debug("" + request);
String res = request.body();
request.disconnect();
if (StringKit.isBlank(res)) {
throw new WechatException("狀態通知開啟失敗");
}
try {
JSONObject jsonObject = JSONKit.parseObject(res);
JSONObject BaseResponse = jsonObject.get("BaseResponse").asJSONObject();
if (null != BaseResponse) {
int ret = BaseResponse.getInt("Ret", -1);
if (ret != 0) {
throw new WechatException("狀態通知開啟失敗,ret:" + ret);
}
}
} catch (Exception e) {
throw new WechatException(e);
}
}
/**
* Step5:微信初始化
* @url https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxinit
* @method POST
* @data JSON
* @header Content-Type: application/json; charset=UTF-8
* @params 上一步登陸獲得的BaseRequest
* @return JSON 這一步中獲取 SyncKey, User 後面的訊息監聽用。
*/
@Override
public void wxInit(WechatMeta wechatMeta) throws WechatException {
String url = wechatMeta.getBase_uri() + "/webwxinit?r=" + DateKit.getCurrentUnixTime() + "&pass_ticket="
+ wechatMeta.getPass_ticket() + "&skey=" + wechatMeta.getSkey();
JSONObject body = new JSONObject();
body.put("BaseRequest", wechatMeta.getBaseRequest());
HttpRequest request = HttpRequest.post(url).contentType("application/json;charset=utf-8")
.header("Cookie", wechatMeta.getCookie()).send(body.toString());
LOGGER.debug("" + request);
String res = request.body();
request.disconnect();
if (StringKit.isBlank(res)) {
throw new WechatException("微信初始化失敗");
}
try {
JSONObject jsonObject = JSONKit.parseObject(res);
if (null != jsonObject) {
JSONObject BaseResponse = jsonObject.get("BaseResponse").asJSONObject();
if (null != BaseResponse) {
int ret = BaseResponse.getInt("Ret", -1);
if (ret == 0) {
wechatMeta.setSyncKey(jsonObject.get("SyncKey").asJSONObject());
wechatMeta.setUser(jsonObject.get("User").asJSONObject());
StringBuffer synckey = new StringBuffer();
JSONArray list = wechatMeta.getSyncKey().get("List").asArray();
for (int i = 0, len = list.size(); i < len; i++) {
JSONObject item = list.get(i).asJSONObject();
synckey.append("|" + item.getInt("Key", 0) + "_" + item.getInt("Val", 0));
}
wechatMeta.setSynckey(synckey.substring(1));
}
}
}
} catch (Exception e) {
}
}
/**
* 選擇同步線路
*/
@Override
public void choiceSyncLine(WechatMeta wechatMeta) throws WechatException {
boolean enabled = false;
for(String syncUrl : Constant.SYNC_HOST){
int[] res = this.syncCheck(syncUrl, wechatMeta);
if(res[0] == 0){
String url = "https://" + syncUrl + "/cgi-bin/mmwebwx-bin";
wechatMeta.setWebpush_url(url);
LOGGER.info("選擇線路:[{}]", syncUrl);
enabled = true;
break;
}
}
if(!enabled){
throw new WechatException("同步線路不通暢");
}
}
/**
* Step8:訊息檢測/檢測心跳
* @url https://webpush2.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck
* @method GET
* @data JSON
* @header ContentType: application/json; charset=UTF-8
* @params BaseRequest
* @return window.synccheck={retcode:"xxx",selector:"xxx"}
* 其中retcode:
0 正常
1100 失敗/登出微信
selector:
0 正常
2 新的訊息
7 進入/離開聊天介面
*/
@Override
public int[] syncCheck(WechatMeta wechatMeta) throws WechatException{
return this.syncCheck(null, wechatMeta);
}
/**
* 檢測心跳
*/
private int[] syncCheck(String url, WechatMeta meta) throws WechatException{
if(null == url){
url = meta.getWebpush_url() + "/synccheck";
} else{
url = "https://" + url + "/cgi-bin/mmwebwx-bin/synccheck";
}
JSONObject body = new JSONObject();
body.put("BaseRequest", meta.getBaseRequest());
HttpRequest request = HttpRequest
.get(url, true, "r", DateKit.getCurrentUnixTime() + StringKit.getRandomNumber(5), "skey",
meta.getSkey(), "uin", meta.getWxuin(), "sid", meta.getWxsid(), "deviceid",
meta.getDeviceId(), "synckey", meta.getSynckey(), "_", System.currentTimeMillis())
.header("Cookie", meta.getCookie());
LOGGER.debug(request.toString());
String res = request.body();
request.disconnect();
int[] arr = new int[]{-1, -1};
if (StringKit.isBlank(res)) {
return arr;
}
String retcode = Matchers.match("retcode:\"(\\d+)\",", res);
String selector = Matchers.match("selector:\"(\\d+)\"}", res);
if (null != retcode && null != selector) {
arr[0] = Integer.parseInt(retcode);
arr[1] = Integer.parseInt(selector);
return arr;
}
return arr;
}
/**
* 處理訊息
*/
@Override
public void handleMsg(WechatMeta wechatMeta, JSONObject data) {
if (null == data) {
return;
}
JSONArray AddMsgList = data.get("AddMsgList").asArray();
for (int i = 0, len = AddMsgList.size(); i < len; i++) {
// LOGGER.info("你有新的訊息,請注意查收");
JSONObject msg = AddMsgList.get(i).asJSONObject();
int msgType = msg.getInt("MsgType", 0);
String content = msg.getString("Content");
String name = getUserRemarkName(content.split(":")[0]);
if (msgType == 1 ) {
if (msg.getString("FromUserName").indexOf("@@") != -1) {
LOGGER.info(name + ": " + content);
String ans = "@";
// webwxsendmsg(wechatMeta, ans+name+" 抓包成功", msg.getString("FromUserName"));
LOGGER.info("自動回覆 " + name + ":" + name);
}
} else if (msgType == 3) {
String imgDir = Constant.config.get("app.img_path");
String msgId = msg.getString("MsgId");
FileKit.createDir(imgDir, false);
String imgUrl = wechatMeta.getBase_uri() + "/webwxgetmsgimg?MsgID=" + msgId + "&skey="
+ wechatMeta.getSkey() + "&type=slave";
HttpRequest.get(imgUrl).header("Cookie", wechatMeta.getCookie())
.receive(new File(imgDir + "/" + msgId + ".jpg"));
// webwxsendmsg(wechatMeta, "無法檢視圖片", msg.getString("FromUserName"));
} else if (msgType == 34) {
// webwxsendmsg(wechatMeta, "語音也聽不懂", msg.getString("FromUserName"));
}
}
}
/**
* 傳送訊息
*/
private void webwxsendmsg(WechatMeta meta, String content, String to) {
String url = meta.getBase_uri() + "/webwxsendmsg?lang=zh_CN&pass_ticket=" + meta.getPass_ticket();
JSONObject body = new JSONObject();
//寫入當前回覆物件UserName
RecordCon.cache.add(to);
String clientMsgId = DateKit.getCurrentUnixTime() + StringKit.getRandomNumber(5);
JSONObject Msg = new JSONObject();
Msg.put("Type", 1);
Msg.put("Content", content);
Msg.put("FromUserName", meta.getUser().getString("UserName"));
Msg.put("ToUserName", to);
Msg.put("LocalID", clientMsgId);
Msg.put("ClientMsgId", clientMsgId);
body.put("BaseRequest", meta.getBaseRequest());
body.put("Msg", Msg);
HttpRequest request = HttpRequest.post(url).contentType("application/json;charset=utf-8")
.header("Cookie", meta.getCookie()).send(body.toString());
//LOGGER.info("傳送訊息...");
//LOGGER.debug("" + request);
request.body();
request.disconnect();
}
private String getUserRemarkName(String id) {
String name = "這個人物名字未知";
for (int i = 0, len = Constant.CONTACT.getGroupList().size(); i < len; i++) {
JSONObject member = Constant.CONTACT.getGroupList().get(i).asJSONObject();
if (member.getString("UserName").equals(id)) {
if (StringKit.isNotBlank(member.getString("RemarkName"))) {
name = member.getString("RemarkName");
}else if(StringKit.isNotBlank(member.getString("DisplayName"))){
name = member.getString("DisplayName");
}else {
name = member.getString("NickName");
}
return name;
}
}
return name;
}
@Override
public JSONObject webwxsync(WechatMeta meta) throws WechatException{
String url = meta.getBase_uri() + "/webwxsync?skey=" + meta.getSkey() + "&sid=" + meta.getWxsid();
JSONObject body = new JSONObject();
body.put("BaseRequest", meta.getBaseRequest());
body.put("SyncKey", meta.getSyncKey());
body.put("rr", DateKit.getCurrentUnixTime());
HttpRequest request = HttpRequest.post(url).contentType("application/json;charset=utf-8")
.header("Cookie", meta.getCookie()).send(body.toString());
LOGGER.debug(request.toString());
String res = request.body();
request.disconnect();
if (StringKit.isBlank(res)) {
throw new WechatException("同步syncKey失敗");
}
JSONObject jsonObject = JSONKit.parseObject(res);
JSONObject BaseResponse = jsonObject.get("BaseResponse").asJSONObject();
if (null != BaseResponse) {
int ret = BaseResponse.getInt("Ret", -1);
if (ret == 0) {
meta.setSyncKey(jsonObject.get("SyncKey").asJSONObject());
StringBuffer synckey = new StringBuffer();
JSONArray list = meta.getSyncKey().get("List").asArray();
for (int i = 0, len = list.size(); i < len; i++) {
JSONObject item = list.get(i).asJSONObject();
synckey.append("|" + item.getInt("Key", 0) + "_" + item.getInt("Val", 0));
}
meta.setSynckey(synckey.substring(1));
return jsonObject;
}
}
return null;
}
}
其實針對這個輪子還修改了RecordCon類,該類是用來將聯絡人列表寫入本地檔案的,不過因為這個不是核心處理類,而且邏輯比較簡單,這裡就不摘了,最後只需要修改返回值ans比如呼叫圖靈機器人api,就可以實現你自己不可告人的需求了