上一篇介绍了如何借用webqq协议登陆qq,这一篇主要讲下如何实现群发消息。就目前我所知的消息类型有3种,分别是好友消息,群消息以及临时会话消息(这个一般是往群组成员群发)。3种消息分别对应3种方法(3个post方法),下面依次介绍。
1.群发好友消息
要想群发好友消息,首先要获取消息对象,也即好友列表。只有获取了每个QQ好友的标识,才知道往谁去发消息。所以,群发的第一步其实就是获取对象,这里也即获取好友列表。下面是获取好友列表的post请求信息:
Request URL:http://s.web2.qq.com/api/get_user_friends2 Request Method:POST Content-Type:application/x-www-form-urlencoded Referer:http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1 Form-Data:r={"vfwebqq":"23aedade67a2278b9a807015eb4bcad5771671c3a7c5a5e7c017034b9ba9d3f28407f179e5f34566","hash":"51C8576A0E1B5A7D"}
url地址很简单,看名字也知道意思,post方法。一定要记住要设置请求头Content-Type为application/x-www-form-urlencoded(不设置post请求就不成功即返回的retcode不为0)。同时也要设置请求头Referer,不然也会失败(后续很多方法都要设置Referer)。再就是表单数据,同之前一样,json字符串之前一定要带上"r="。vfwebqq即上篇中我所提到的,单独获取vfwebqq返回的值,而非第二次登陆成功所返回的值。hash是通过某种方式加密后获取到的值,同密码一样,是通过js文件加密的。这边先附上js的链接:http://0.web.qstatic.com/webqqpic/pubapps/0/50/eqq.all.js。加密方法就在这个js里面,将代码格式化后找到P
= function(b, j) 这个方法(参数可能不是b,j,方法每隔一段时间就会变),只将这个方法提取出来就好,加密用到的就是这个方法。b即登陆的QQ账号,j即之前登陆时获取的ptwebqq。下面先贴出我提取出后更改函数名后的hash加密方法(至少今天还是这个方法,以后即使方法换了,js文件地址也不变,方法结构大致也差不多,即使不懂如何加密的,在js文件里找类似的方法也容易):
function getHash(b, j) { for (var a = [], i = 0; i < j.length; i++) a[i % 4] ^= j.charCodeAt(i); var w = ["EC", "OK"], d = []; d[0] = b >> 24 & 255 ^ w[0].charCodeAt(0); d[1] = b >> 16 & 255 ^ w[0].charCodeAt(1); d[2] = b >> 8 & 255 ^ w[1].charCodeAt(0); d[3] = b & 255 ^ w[1].charCodeAt(1); w = []; for (i = 0; i < 8; i++) w[i] = i % 2 == 0 ? a[i >> 1] : d[i >> 1]; a = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"]; d = ""; for (i = 0; i < w.length; i++) d += a[w[i] >> 4 & 15], d += a[w[i] & 15]; return d };
下面附上Java调用js的方法:
/** * 获取QQ好友列表的post参数hash * * @param paras * @return * @throws ScriptException * @throws FileNotFoundException * @throws NoSuchMethodException */ public static String getHash(String QQ, String ptwebqq) { Object t = null; try { ScriptEngineManager m = new ScriptEngineManager(); ScriptEngine se = m.getEngineByName("javascript"); se.eval(new FileReader(new File("resources/js/eqq.all.js"))); t = se.eval("getHash(\"" + QQ + "\",\"" + ptwebqq + "\")"); return t.toString(); } catch (Exception e) { e.printStackTrace(); } return t.toString(); }
最后附上发送post请求的方法,首先拼装好发送的json字符串,这里切记,json数据要经过url转码(外面的"r="不需要转码)
getFriendListStr = "{\"vfwebqq\":\"" + vfwebqq + "\",\"hash\":\"" + hash + "\"}"; getFriendListStr = "r=" + URLEncoder.encode(getFriendListStr, "utf-8");
下面是post方法:
// 获取好友列表 public static boolean getFriendList() throws Exception { // post 请求 DefaultHttpClient client = new DefaultHttpClient(); HttpPost postjson = new HttpPost( "http://s.web2.qq.com/api/get_user_friends2"); postjson.setHeader("Referer", "http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1"); HttpClientParams.setCookiePolicy(client.getParams(), CookiePolicy.BROWSER_COMPATIBILITY); client.getParams().setParameter( CoreConnectionPNames.CONNECTION_TIMEOUT, 5000); client.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 5000); StringEntity entity = new StringEntity(getFriendListStr); entity.setContentType("application/x-www-form-urlencoded"); postjson.setEntity(entity); // 设置CookieStore if (cs != null) { client.setCookieStore(cs); } // 获得返回的json数据包 HttpResponse httpResponse = client.execute(postjson); HttpEntity httpent = httpResponse.getEntity(); // 保存CookieStore cs = client.getCookieStore(); String line; StringBuffer sb = new StringBuffer(); if (httpent != null) { BufferedReader br = new BufferedReader(new InputStreamReader( httpent.getContent(), "UTF-8")); while ((line = br.readLine()) != null) { sb.append(line); } br.close(); } JSONObject obj = new JSONObject(sb.toString()); if (obj.getInt("retcode") == 0) { JSONObject res = obj.getJSONObject("result"); QQMsgHandler.getInstance().getFriendList(res);// 处理信息,得到好友列表 return true; } else { return false; } }
请求成功返回的json数据如下:
{"retcode":0,"result":{"friends":[{"flag":0,"uin":2741681712,"categories":0},{"flag":0,"uin":2117029336,"categories":0}],"marknames":[],"categories":[],"vipinfo":[{"vip_level":0,"u":2741681712,"is_vip":0},{"vip_level":0,"u":2117029336,"is_vip":0}],"info":[{"face":303,"flag":294126146,"nick":" ScumVirus","uin":2741681712},{"face":564,"flag":8388608,"nick":"ScumVirus","uin":2117029336}]}}
在friends中有categories这个属性,可能是好友分组的标志位,不过这里我没研究,因为我只做了群发所有好友消息。有兴趣的朋友可以深入分析下好友分组,这样可以指定只给某一分组的好友发送消息,这样那些生活工作通用一个QQ的人群发消息会比较方便。info跟friends类似,不过没有带上分组信息,多了用户昵称。所以我只截取了info中的数据作为好友列表,uin即每一个好友的标识,nick即昵称。face不知道是什么,我没深入研究,flag也是一个标志位,但是具体有什么作用我也不知道,因为实现群发功能uin作为标识足矣。中间的vipinfo应该是与qq会员相关的信息,不过一般做QQ机器人都没有这方面需求,我直接无视了。
接下来就要发送好友消息了。其实群发消息也就是给每一个好友都发送一条消息(我没试过在json里以数组形式写上所有消息,不知道能不能实现一次群发)。
Request URL:http://d.web2.qq.com/channel/send_buddy_msg2 Request Method:POST Referer:http://d.web2.qq.com/proxy.html?v=20130916001&callback=1&id=2 Form-Data:r={"to":2741681712,"content":"[\"123\",[\"font\",{\"name\":\"宋体\",\"size\":10,\"style\":[0,0,0],\"color\":\"000000\"}]]","face":564,"clientid":53999199,"msg_id":38600001,"psessionid":"8368046764001d636f6e6e7365727665725f77656271714031302e3133392e372e313630000054bd00000bcd026e04003654298d6d0000000a404e364b386b707858676d000000286740432cf4f2628258046027ab71e7c932587058acf842d2c727a076b663ff32b4082a088f3c4521"}
依旧是post请求,不过要带上Referer,json字符串格式如上所示。to是发送对象即好友的uin,content里带的是发送文字的相关信息,我发的是最普通的消息,所有参数都是默认的,如果你想发点与众不同的, 改下参数即可。clientid同登陆一样,随机8-9位,随便写,我程序里这个参数都是写的固定的默认值。msg_id为发送消息的标识,随便写个数字,然后每发一条自增加1作为区分即可(即使每次都一样好像也没什么影响吧,不过最好+1吧),psessionid也是登陆成功返回的参数。代码同之前没什么区别,就不贴了。
2.群发群消息
同上,先获取对象,即群列表。·获取群消息的post请求如下:
Request URL:http://s.web2.qq.com/api/get_group_name_list_mask2 Request Method:POST Content-Type:application/x-www-form-urlencoded Referer: http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1 Form-Data:r={"vfwebqq":"23aedade67a2278b9a807015eb4bcad5771671c3a7c5a5e7c017034b9ba9d3f28407f179e5f34566","hash":"51C8576A0E1B5A7D"}
方法大致同获取好友列表时,只有url地址不一样,其他都一样(包括json数据)。返回的json数据如下:
{"retcode":0,"result":{"gmasklist":[],"gnamelist":[{"flag":1090520065,"name":"芭蕉躲在被窝不给h包","gid":4065055661,"code":818309331},{"flag":17826817,"name":"ScumVirus","gid":1068943425,"code":1244601995},{"flag":16778241,"name":"永丶夜","gid":818744232,"code":171813541},{"flag":16778241,"name":"ScumVirus机器人","gid":2646811562,"code":3760910371}],"gmarklist":[]}}
gid即群标识,也即我们发送群消息的对象。不过code也是群的一种标示,不过不同于gid。
这里简单讲解一下uin和gid分别是好友标识和群标识,针对同一个对象(1个好友或者1个群),同一账号每次登陆获取到的标识是一样的,但是不同账号获取的不同。比如说a,b在群c中,但是a登陆后获取到c的gid为1,b登陆后获取到c的gid可能就是2,反正不同于a获取到的。但是code不一样,每个人每次登陆获取到同一个群的gid是一样的,比如如上的永丶夜这个群,我2个账号登陆获取到的gid都是818744232这个值,一直不变。gid只能作为群消息发送的标识,若要获取群组成员的信息,则是code作为群组标识,这个在发送临时会话消息时会提到。
发送群消息的请求如下:
Request URL:http://d.web2.qq.com/channel/send_qun_msg2 Request Method:POST Content-Type:application/x-www-form-urlencoded Referer:http://d.web2.qq.com/proxy.html?v=20130916001&callback=1&id=2 Form-Data:r={"group_uin":1068943425,"content":"[\"123\",[\"font\",{\"name\":\"宋体\",\"size\":10,\"style\":[0,0,0],\"color\":\"000000\"}]]","face":564,"clientid":53999199,"msg_id":38600002,"psessionid":"8368046764001d636f6e6e7365727665725f77656271714031302e3133392e372e313630000054bd00000bcd026e04003654298d6d0000000a404e364b386b707858676d000000286740432cf4f2628258046027ab71e7c932587058acf842d2c727a076b663ff32b4082a088f3c4521"}
基本同发送好友消息一致,json数据中group_uin即之前获取到的gid,其他同发送好友消息无区别。
3.发送临时会话消息
这边的临时会话仅指和已有群的群内成员发送会话。毕竟发送消息要获取对象的标识,这个标识又不是qq账号,而是uin,如果你只知道一个人的QQ号,但是和他又不是好友,又不在同一个群里,那么是没法获取到他相对于你的uin的,即发送消息不成立。
回归正题,如上所说,我们是要通过群来获取群成员相对于自身的uin。首先,我们要先获取群内所有成员的信息,这里要用到之前提到的code。post请求如下:
Request URL:http://s.web2.qq.com/api/get_group_info_ext2?gcode=1244601995&vfwebqq=670f701a39ab1b21c05007564cf8936d6000b1f5a34093689aaa7ff67e8a3ede932d3ecb1b98f27c&t=1433068633058 Request Method:GET Referer:http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1
这里很简单,仅仅一个get方法就获取到的一个群内的所有成员信息,gcode即之前说的code,vfwebqq不谈了,t即时间戳,直接用System.currentTimeMillis()获取就好。
返回的json数据如下:
{"retcode":0,"result":{"stats":[{"client_type":41,"uin":2368295990,"stat":10}],"minfo":[{"nick":" ScumVirus","province":"湖北","gender":"male","uin":2741681712,"country":"中国","city":"武汉"},{"nick":"ScumVirus","province":"湖北","gender":"male","uin":570904041,"country":"中国","city":"武汉"},{"nick":"No Game No Bot","province":"湖北","gender":"female","uin":2368295990,"country":"中国","city":"武汉"},{"nick":"ScumVirus","province":"湖北","gender":"male","uin":2117029336,"country":"中国","city":"武汉"}],"ginfo":{"face":0,"memo":"yy108371257","class":25,"fingermemo":"","code":1244601995,"createtime":1321090723,"flag":17826817,"level":0,"name":"ScumVirus","gid":1068943425,"owner":2741681712,"members":[{"muin":2741681712,"mflag":192},{"muin":570904041,"mflag":0},{"muin":2368295990,"mflag":0},{"muin":2117029336,"mflag":0}],"option":2},"vipinfo":[{"vip_level":0,"u":2741681712,"is_vip":0},{"vip_level":0,"u":570904041,"is_vip":0},{"vip_level":0,"u":2368295990,"is_vip":0},{"vip_level":0,"u":2117029336,"is_vip":0}]}}
这里只关注minfo即可,里面既有用户昵称,居住地,性别,也有我们要的uin标识。
接下来就是发送临时消息了,不过在这个方法比之前的2种方法要多出一步,他要获取group_sid这个参数才能发送临时会话消息,不过这个参数也很好拿到,一个get请求就行了。请求如下:
Request URL:http://d.web2.qq.com/channel/get_c2cmsg_sig2?id=<span style="font-family: Arial, Helvetica, sans-serif;">1068943425</span>&to_uin=570904041&clientid=53999199&psessionid=8368046764001d636f6e6e7365727665725f77656271714031302e3133392e372e313630000054bd00000bcd026e04003654298d6d0000000a404e364b386b707858676d000000285f293205986c0c9381ea5471ac8bdfd996fb8ca4765bcebab0230c3b0fb85cf513378825ebe2ade8&service_type=0&t=1433069146558 Request Method:GET Referer:http://d.web2.qq.com/proxy.html?v=20130916001&callback=1&id=2
id即群标识gid,to_uin即你要发送对象的uin标识,service_type我也不知道是什么,默认填0也不会错,其他参数之前都提过不多说了。
返回的json数据如下:
{"retcode":0,"result":{"type":0,"value":"b1a949941bc21ea0e56529d6c71b9065083e0ab74ca8ee8cedb0135440fe17b54fb8a807e202a0e491f40cf403d5e22e","flags":{"text":1,"pic":1,"file":1,"audio":1,"video":1}}}
只取其中的value,即是我们要的group_sig。
下面就到发送消息了,请求如下:
Request URL:http://d.web2.qq.com/channel/send_sess_msg2 Request Method:POST Referer:http://d.web2.qq.com/proxy.html?v=20130916001&callback=1&id=2 Form-Data:r={"to":570904041,"content":"[\"123\",[\"font\",{\"name\":\"宋体\",\"size\":10,\"style\":[0,0,0],\"color\":\"000000\"}]]","face":564,"clientid":53999199,"msg_id":93440002,"psessionid":"8368046764001d636f6e6e7365727665725f77656271714031302e3133392e372e313630000054bd00000bcd026e04003654298d6d0000000a404e364b386b707858676d00000028d173ff0fccee9875162f70075476dfb3712e59456440efca2a0db0fb063216d74084919fd7185f16","group_sig":"b1a949941bc21ea0e56529d6c71b9065083e0ab74ca8ee8cedb0135440fe17b54fb8a807e202a0e491f40cf403d5e22e","service_type":0}
to即目标uin,group_sig即我们刚获取到的参数,service_type默认填0吧,反正不会错。
3种消息发送都有返回值,只判断recode是否为0就可知道是否发送成功。以上即是本篇介绍的所有内容。
注:若消息内容带回车,则可能发送失败。我目前也不知道回车字符该如何转码,反正怎么发都是失败,易语言论坛有人在15年1月写了一个解决方案被采纳了,不过我按照java的方式写了还是发送失败,因为我不懂易语言,所以可能我理解错了。
再稍微说点其他的。
目前我见到过2种QQ机器人,一种是酷Q,它是仿QQ老版界面做的,可以看到好友列表(这个上面我也提到过如何获取好友列表,它只是将列表显示出来了而已,群也一样),如果想给指定的某些人或群发送消息,那么你也可以做个列表,每个item前加上个jcheckbox让用户选择即可。另外一种是晨风QQ机器人。不过那个跟我做的差不多,没有列表展示,只是一堆功能设置放在上面。不过方法都有了,界面如何设计就看用户的需求了。
1068943425