常玩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,就可以实现你自己不可告人的需求了