这是最近因为感兴趣才写的小东西,网上大多是易语言版或C #版,java仅有的版本也偏老,老版webqq协议早失效了,所以现在我写了一个最新版本的。要实现群发和自动回复消息以及更多自定义功能,首先要实现登陆QQ,这边主要介绍一下如何分析QQ协议以及如何登陆。

我并没有使用很专业的抓包工具,事实上现在的浏览器一般都能查看到get,post请求的主要内容,而我们所需要的也就是请求的内容和地址,所以一个360浏览器或者google浏览器足够我们分析了。

首先分析流程,再讲方法。第一步登陆webqq的网站,我们会看到登陆界面,打开F12,每隔一段时间会执行一个请求,大概是判断左侧二维码是否失效的方法吧,不过这个与登陆无关,直接忽略。

填写完账号,失去输入框的焦点后,又会触发一个请求,返回了一串字符串,返回的字符串可能是执行某个js方法吧(大概,我也不清楚),不过这也并不重要,先看图:

这个请求是为了判断该账号是否需要填写图片验证码才能登陆。不看外面的方法名,里面第一个参数0则代表不需要填写图片验证码登陆,第二个参数要记下,相当于登陆时要用的验证码(不过它不同于图片验证码,如果需要图片验证码则还需要发个请求来获取图片,然后根据图片中的字母来填写验证码,而此处相当于省略了这一段环节,可以理解为服务器直接告诉你了验证码是什么,下面会介绍需要图片验证码的流程),第三个参数为你账号的十六进制值,不过与登陆环节无关吧,最后2个参数有什么用我也不知道,不过与主要登陆环节无关。

下图为需要验证码登陆返回的数据,第一个参数为1代表需要验证码(这时就要再发一个请求来获取验证码图片了),第二个参数要记下,等下要作为获取图片验证码请求的参数传递到服务器。

这时就需要发送一个请求来获取图片验证码了,如下图:

获取到验证码后就该填写密码登陆了,只不过登陆也分为几步,首先第一次登陆,第一个参数返回是否成功,0即成功可以往下执行,4即验证码错误,3即账号密码错误。第3个参数为成功后的回调方法,也即你在第一步登陆成功后紧接着要发送的请求。如果第一步登陆失败,是不会有回调方法的,第三个参数返回的是0吧,记不清了。

如果第一步登陆成功,紧接着就要发送下一个请求,请求的地址为第一步登陆成功后返回的第3个参数(url链接)。这一步是必须的,要更新cookie(后续介绍),不然第二步登陆肯定失败。

这一步没什么返回结果,只是用来更新cookie,来进行第二步登陆。

接下来要获取一个参数vfwebqq,这个参数与登陆无关,但你要获取QQ好友列表和群列表时必须要带上(注意:第二步登陆也会返回这个参数,但与这个请求获取到的vfwebqq不同,但是真正获取好友列表和群列表的参数是这一步获取到的,可能是最近更新的结果)。

接下来就是最后一步了,第二步登陆,如果返回成功,则已登陆的QQ会被挤下线,第一个参数为0即代表登陆成功(在QQ登陆的过程中,返回的json数据里retcode基本代表返回结果,0即成功),返回的参数在后续的方法中介绍。

总结一下登陆的过程即:

1.判断是否需要验证码登陆,若需要则先获取验证码图片。

2.填写表单信息完成后进行第一次登陆。

3.执行第一步登陆成功后的回调方法(发送请求)。

4.获取vfwebqq。(如果只想做一个自动回复的机器人的话,这个参数是不需要的,但如果要做群发软件则需要获取好友列表和群列表,则必须要获取这个参数;至于为什么这一步放在第一步登陆与第二步中间是因为网页qq里是按这个顺序发送请求的,改变顺序会有什么影响我不知道,但是至少不会影响第二次登陆)。

5.第二步登陆,若成功即登陆成功。

以上只是流程分析,若以后webqq协议再变化,理论上是可以按照此过程去抓包再分析的,应该也就是改改参数,改改方法链接上的小问题,至少13年到现在流程上是没什么太大的变化。

下面介绍Java实现的方法,整个通信过程都是通过发送http请求实现的,我看网上介绍的方法大多是自己封装HttpURLConnection类发送http请求的,不过我都是用的DefaultHttpClient这个类,需要引用额外的jar包。我只以我写方法做介绍吧。

1.检测是否需要验证码

Request URL:https://ssl.ptlogin2.qq.com/check?pt_tea=1&uin=2368295990&appid=501004106&js_ver=10124&js_type=0&login_sig=&u1=http%3A%2F%2Fw.qq.com%2Fproxy.html&r=0.7602819891180843
Request Method:GET

此处为GET请求,除了uin要设置为自己的账号之外,其他参数都可不变,有些是固定的参数,有些是随机的参数,最保险的是都不变化....

返回字符串为,如果不需要验证码,则记录第二个参数(即!KBK,每次访问获取的值都不一样)。

ptui_checkVC('0','!KBK','\x00\x00\x00\x00\x8d\x29\x54\x36','2c6bf125c7708d33cc7c9bea99a9b5f6fd7e7d3f3f8e676cbff24bb0845de8cefaf75b88ea5efd5d36caa2d34b997d0ee8c7b638757af32a','0');

若需要验证码,即第一个参数返回1,则记录第二个参数,作为下次请求发送的参数(即JFMGYUOLfsTYRBTMi9I2UI8APugejxO4A1l3OAJ1ksu3VKqfDz8W8g**)

ptui_checkVC('1','JFMGYUOLfsTYRBTMi9I2UI8APugejxO4A1l3OAJ1ksu3VKqfDz8W8g**','\x00\x00\x00\x00\xc3\x54\x60\x81','','0');

发送请求获取验证码图片:

Request URL:https://ssl.captcha.qq.com/getimage?aid=501004106&r=0.008850367739796638&uin=3277086849&cap_cd=JFMGYUOLfsTYRBTMi9I2UI8APugejxO4A1l3OAJ1ksu3VKqfDz8W8g**
Request Method:GET

请求依旧为GET,uin为账号,cap_cd为刚才的参数。此时返回的是图片的二进制数据流,再就看如何处理图片了,我先附上我处理图片的方法,其实就是将数据流写进图片文件,保存在本地,再显示到界面上。验证码的像素一般是130*53,zoomInImage(path)是为了缩小图片,方便显示到界面,不然在前台改变分辨率图片总是显示不全。因为我不太会处理swing界面的图片,所以处理手法比较拙劣,直接忽略吧。

/*** @title 根据二进制字符串生成图片* @param data*            生成图片的二进制字符串* @param fileName*            图片名称(完整路径)* @param type*            图片类型* @return*/
public static void saveImage(String data, String fileName, String type) {BufferedImage image = new BufferedImage(130, 53,BufferedImage.TYPE_BYTE_BINARY);ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();try {ImageIO.write(image, type, byteOutputStream);byte[] bytes = hex2byte(data);String path = System.getProperty("user.dir") + "/resources/img/"+ fileName;String resPath = System.getProperty("user.dir")+ "/resources/img/temp.jpg";RandomAccessFile file = new RandomAccessFile(path, "rw");file.write(bytes);file.close();zoomInImage(path);// 缩小图片} catch (IOException e) {e.printStackTrace();}
}/*** 反格式化byte* * @param s* @return*/
public static byte[] hex2byte(String s) {byte[] src = s.toLowerCase().getBytes();byte[] ret = new byte[src.length / 2];for (int i = 0; i < src.length; i += 2) {byte hi = src[i];byte low = src[i + 1];hi = (byte) ((hi >= 'a' && hi <= 'f') ? 0x0a + (hi - 'a'): hi - '0');low = (byte) ((low >= 'a' && low <= 'f') ? 0x0a + (low - 'a'): low - '0');ret[i / 2] = (byte) (hi << 4 | low);}return ret;
}

再附上发送http请求的方法:

private static CookieStore cs = null;// 存储最近一次的cookie 下次发送http请求的时候带上此cookie
// 接收消息和cookie
public static Object[] getHttpDataAndCookie(String url) throws Exception {DefaultHttpClient client = new DefaultHttpClient();HttpGet httpGet = new HttpGet(url);HttpClientParams.setCookiePolicy(client.getParams(),CookiePolicy.BROWSER_COMPATIBILITY);// 设置CookieStoreif (cs != null) {client.setCookieStore(cs);}HttpResponse httpResponse = client.execute(httpGet);// 保存CookieStorecs = client.getCookieStore();HttpEntity httpent = httpResponse.getEntity();String code = String.valueOf(httpResponse.getStatusLine().getStatusCode());String line;StringBuffer sb = new StringBuffer();// 获取cookieList<Cookie> cookies = ((AbstractHttpClient) client).getCookieStore().getCookies();HashMap<String, String> map = new HashMap<String, String>();if (!cookies.isEmpty()) {for (int i = 0; i < cookies.size(); i++) {map.put(cookies.get(i).getName(), cookies.get(i).getValue());}}if (httpent != null) {BufferedReader br = new BufferedReader(new InputStreamReader(httpent.getContent(), "UTF-8"));while ((line = br.readLine()) != null) {sb.append(line);}br.close();}return new Object[] { sb.toString(), code, map };
}

这里一定要注意cs这个参数,即最近一次请求所获取到的cookie,下次发送请求时一定要带上!前面几步可能不会出错,但后面几步一直不成功就有可能是cookie没综合的原因(当初我卡在这里好久)。map是我将返回的cookie做了处理转成了HashMap,是因为有些地方存储返回的cookie字段,作为下次请求的参数。在第一步整体过程中,需要获取一个参数 ptvfsession。如果不需要验证码登陆,则在检测验证码返回的cookie中提取出这个参数,如下:

ptvfsession = ((HashMap<String, String>) response[2]).get("ptvfsession");

如果需要验证码登陆,则在获取图片返回的cookie中提取,方法同上,即在发送请求后获取cookie,然后获取指定字段的值。
2.第一次登陆

Request URL:https://ssl.ptlogin2.qq.com/login?u=3277086849&p=nRjIiseX0-13YK3Oc5IBhwkIdogP3pBAdO0FxhVCVxZ49bom8qvdrKvrSLQ0EVw*vUIgBQZIELIR18cW*q5nv9ZR4vSXIWkO99LChJvug3DiJiAsJ5iB9zvZuVHwD7lSvCY8dGvsaodgjQf0WG0iZApVevfsGIWzCuQ7xsdRgezjrYPXCueJB5oFekAKREHbX9csjq5zVbuAsd8jqPNvew__&verifycode=uwno&webqq_type=10&remember_uin=1&login2qq=1&aid=501004106&u1=http%3A%2F%2Fw.qq.com%2Fproxy.html%3Flogin2qq%3D1%26webqq_type%3D10&h=1&ptredirect=0&ptlang=2052&daid=164&from_ui=1&pttype=1&dumy=&fp=loginerroralert&action=0-21-1678643&mibao_css=m_webqq&t=1&g=1&js_type=0&js_ver=10124&login_sig=&pt_randsalt=0&pt_vcode_v1=0&pt_verifysession_v1=h02b7eJxn9dCCZ7wQZlVNWbqweqLVaYgWImcVohr2v5ZchM7IPhi63jNnSDf3O5gQ7SErLwoT_CD_iJoNLBiZH3WypEcSVAUNo1
Request Method:GET

依旧是GET请求,u即账号,p即加密后的密码(等下介绍如何加密),verifycode即验证码(4位,如果有图片验证码,则填写图片中的字母,不需要则填写之前返回的值,即!开头的4位),中间一串参数可以不变化,结果的参数ptvfsession为第一步我们获取到的ptvfsession。
这一步返回的字符串为:

ptuiCB('0','0','http://ptlogin4.web2.qq.com/check_sig?pttype=1&uin=3277086849&service=login&nodirect=0&ptsigx=a8f07aea84f23d76363c625b3aa1da48fbb21288078e3229e331955dd64727440da4b0892214efa457a69685a910a2592fb29aa6896121bfbcac6e0bf88dff1e&s_url=http%3A%2F%2Fw.qq.com%2Fproxy.html%3Flogin2qq%3D1%26webqq_type%3D10&f_url=&ptlang=2052&ptredirect=100&aid=501004106&daid=164&j_later=0&low_login_hour=0&regmaster=0&pt_login_type=1&pt_aid=0&pt_aaid=0&pt_light=0','0','登录成功!', 'ScumVirus');

0即成功,第3个参数为回调方法的url地址。

这一步依旧要根据返回的cookie获取一个参数——ptwebqq。

下面介绍如何加密密码,加密方法是在js上实现的,之后附上js文件(可能隔一段时间会变化一次,但是总有大神会破解的吧)。我是通过直接调用js里的getEncryption()方法来加密密码的,附上调用js的方法:

/*** 执行js函数,得到需要的值的值* * @param paras* @return* @throws ScriptException* @throws FileNotFoundException* @throws NoSuchMethodException*/
public static String mdP(String p, String account, String code) {Object t = null;try {ScriptEngineManager m = new ScriptEngineManager();ScriptEngine se = m.getEngineByName("javascript");se.eval(new FileReader(new File("resources/js/QQRSA.js")));t = se.eval("getEncryption(\"" + p + "\",\"" + account + "\",\""+ code + "\")");return t.toString();} catch (Exception e) {e.printStackTrace();}return t.toString();
}

其中p为未加密的密码,account即QQ账号,code是verifyCode,即验证码,同上。返回的字符串即加密后的密码。附上js文件下载地址: QQRSA.js

3.执行回调方法更新cookie

Request URL:http://ptlogin4.web2.qq.com/check_sig?pttype=1&uin=3277086849&service=login&nodirect=0&ptsigx=a8f07aea84f23d76363c625b3aa1da48fbb21288078e3229e331955dd64727440da4b0892214efa457a69685a910a2592fb29aa6896121bfbcac6e0bf88dff1e&s_url=http%3A%2F%2Fw.qq.com%2Fproxy.html%3Flogin2qq%3D1%26webqq_type%3D10&f_url=&ptlang=2052&ptredirect=100&aid=501004106&daid=164&j_later=0&low_login_hour=0&regmaster=0&pt_login_type=1&pt_aid=0&pt_aaid=0&pt_light=0
Request Method:GET

还是GET方法,url地址即为第一步登陆返回的地址,不需要记录任何参数,只要别忘了更新一遍cookie即可。

4.获取vfwebqq,姑且写在这,免得之后再介绍

Request URL:http://s.web2.qq.com/api/getvfwebqq?ptwebqq=9d37ec0c729300bb5dbd927c917f0e851794662e9164c2e6fadab64cea4f6208&clientid=53999199&psessionid=&t=1432729913114
Request Method:GET
Referer:http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1

方法依旧是GET,ptwebqq第一步登陆获取到,clientid为8-9位任意值,psessionid为空,t为当前时间戳。不过此处要多加一个request头:

httpGet.setHeader("Referer","http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1");

返回值为json格式,解析后直接获取ptwebqq。

{"retcode":0,"result":{"vfwebqq":"a57e6b8ea3409910fb139d5949b850e47879e71f133b6aa5e7c593c1724343ae8789f9f0c5286f91"}}

5.第二步登陆

Request URL:http://d.web2.qq.com/channel/login2
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={"ptwebqq":"9d37ec0c729300bb5dbd927c917f0e851794662e9164c2e6fadab64cea4f6208","clientid":53999199,"psessionid":"","status":"online"}

post请求,url连接很简单,但是必须设置content-type为application/x-www-form-urlencoded,设置Referer:http://d.web2.qq.com/proxy.html?v=20130916001&callback=1&id=2,附上我的代码:

public static synchronized String[] postHttpData(String url, String data)throws Exception {// post 请求DefaultHttpClient client = new DefaultHttpClient();HttpPost postjson = new HttpPost(url);postjson.setHeader("Referer","http://d.web2.qq.com/proxy.html?v=20130916001&callback=1&id=2");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(data);entity.setContentType("application/x-www-form-urlencoded");postjson.setEntity(entity);// 设置CookieStoreif (cs != null) {client.setCookieStore(cs);}// 获得返回的json数据包HttpResponse httpResponse = client.execute(postjson);HttpEntity httpent = httpResponse.getEntity();// 保存CookieStorecs = client.getCookieStore();String code = String.valueOf(httpResponse.getStatusLine().getStatusCode());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();}return new String[] { sb.toString(), code };
}

下面是post所带的参数:

String obj = "r={\"ptwebqq\":\"" + ptwebqq + "\",\"clientid\":"+ clientid + ",\"psessionid\":\"\",\"status\":\"online\"}";

其中"r="不能掉,不然不识别,不能登陆成功。前面3个参数前面都介绍了,最后一个参数即登陆状态,online即在线,hidden即隐身。
若登陆成功,则返回:

{"retcode":0,"result":{"uin":3277086849,"cip":1899593934,"index":1075,"port":53012,"status":"online","vfwebqq":"0f28abebb129b3e77be531d95640217425dfcb9b65faf36108f136239b4df2efdb69865776ba26f0","psessionid":"8368046764001d636f6e6e7365727665725f77656271714031302e3133392e372e313630000012a800000aef026e0400816054c36d0000000a404256456156376766746d000000280f28abebb129b3e77be531d95640217425dfcb9b65faf36108f136239b4df2efdb69865776ba26f0","user_state":0,"f":0}}

其中只需记录下 psessionid,以后发送消息要用到。

若最后一步成功,那么你已经成功登陆QQ了。之后有时间会再介绍群发消息和自动回复功能。

qq聊天机器人 群发工具 (java版) (一)相关推荐

  1. qq聊天机器人 群发工具 (java版) (二)

    上一篇介绍了如何借用webqq协议登陆qq,这一篇主要讲下如何实现群发消息.就目前我所知的消息类型有3种,分别是好友消息,群消息以及临时会话消息(这个一般是往群组成员群发).3种消息分别对应3种方法( ...

  2. webqq2协议分析和qq聊天机器人简单实现(转)

    webqq2协议分析和qq聊天机器人简单实现 转之http://hfutxf.javaeye.com/blog/800866 通过webqq接口,可以实现发送qq消息接收qq消息等,这样,想实现一个q ...

  3. webqq2协议分析和qq聊天机器人简单实现

    转之http://hfutxf.javaeye.com/blog/800866 通过webqq接口,可以实现发送qq消息接收qq消息等,这样,想实现一个qq聊天机器人,就不是什么难事情了了,下面开始一 ...

  4. QQ 聊天机器人小薇 2.0.0 发布!

    本次发布主要加入了支持讨论组聊天,并增强了稳定性.另外,官方小薇 QQ 机器人已经下线,大家要体验的话请 自建私服~ 简介 XiaoV(小薇)是一个用 Java 写的 QQ 聊天机器人 Web 服务, ...

  5. QQ 聊天机器人小薇 2.1.0 发布!

    本次发布加入了支持茉莉机器人,并且更容易搭建开发环境,在线显示登录二维码~ 简介 XiaoV(小薇)是一个用 Java 写的 QQ 聊天机器人 Web 服务,可以用于社群互动: 监听多个 QQ 群消息 ...

  6. 机器人聊天软件c#_C#制作简易QQ聊天机器人

    最近对QQ聊天机器人比较感兴趣,奈何一直没找到C#的源码,就自己摸索,好了废话不多说了,开始正题. 首先我们要准备的是C# 的SDK下载地址:http://pan.baidu.com/s/1geW0X ...

  7. python qq聊天机器人_Python QQBot库的QQ聊天机器人

    本文实例为大家分享了Python QQBot库的QQ聊天机器人的具体代码,供大家参考,具体内容如下 1.安装 pip install qqbot 2.主动发出消息 from qqbot import ...

  8. 基于PaddleHub的QQ聊天机器人

    基于PaddleHub的QQ聊天机器人 一. 项目背景 本项目是参加[AI达人特训营]的作品. 当你在和朋友在QQ群里划水摸鱼时,你是否会感到有一丝枯燥,没事别担心.为了增加群友们的划水乐趣,提高群友 ...

  9. python qqbot库_Python QQBot库的QQ聊天机器人

    本文实例为大家分享了Python QQBot库的QQ聊天机器人的具体代码,供大家参考,具体内容如下 项目地址:https://github.com/pandolia/qqbot 1.安装 pip in ...

最新文章

  1. 飞鱼科技游戏开发岗面试经验
  2. [文摘]上软解散相关
  3. [Pku 2774] 字符串(六) {后缀数组的构造}
  4. java 搭建 web服务器 socket实现
  5. 深度思考 Spring Cloud + Alibaba Sentinel 源码原理
  6. checkcode.aspx 生成随即验证码
  7. MySqlBackup.NET——用于C#,VB.NET,ASP.NET的MySQL备份解决方案
  8. java digester_digester解析xml文件
  9. 图音80系列车载导航/DVD分体机安装DSA
  10. Wordpress 网站搭建及性能监控方法详解!
  11. 简述Android模拟机和真机的区别,详解android模拟器emulator
  12. HL7 2.6解析转XML(C#版)
  13. 阿里腾讯裁员30%,互联网大厂此举预示着什么?
  14. QQ商家(QQ在线咨询,QQ推广等)
  15. 7-3 打印九九口诀表
  16. HTML.初学.更新
  17. 深度学习OSSIM关联分析(附源码注解)
  18. 合肥ibm服务器维修,合肥IBM/thinkpad笔记本维修
  19. matlab求pi值的三种方法
  20. VS Code按住ctrl不能跳转到定义/函数(Python)

热门文章

  1. VM虚拟机 运行openglES egl崩溃的问题
  2. node.js的安装包 14.17版本
  3. C语言 科学计算器 后缀表达式 解析字符串 仿JS的eval函数
  4. 搜索引擎终极名单大全
  5. navicat数据库管理软件延长试用期
  6. 关于微信小程序request请求fail不执行的问题
  7. DC靶场系列--DC1
  8. 极米CEO钟波:电视智能之后将是无屏化
  9. 基于51单片机的舞蹈机器人步进机仿真设计
  10. 新一轮商业革命将至,张勇用“敏捷组织”率先交出答卷