WebQQ获取好友的hash参数算法C#版(2013/8/17) -> python动态获取版本

在使用web qq的接口进行好友列表获取的时候,需要post一个参数:hash
在对其js文件进行分析之后,发现计算hash的函数位于:
http://0.web.qstatic.com/webqqpic/pubapps/0/50/eqq.all.js

[2013/8/17]这是一个极其易变的参数,由于其易变性,因此本帖只能在一段时间内有效,请及时更具具体情况更换并关注本站最新算法。

  1. <script type=”text/javascript”>
  2. var b=function(b,i){
  3. this.s=b||0;
  4. this.e=i||0
  5. }
  6. var p= function(i,a){
  7. var r=[];
  8. r[0]=i>>24&255;
  9. r[1]=i>>16&255;
  10. r[2]=i>>8&255;
  11. r[3]=i&255;
  12. for(var j=[],e=0;
  13. e<a.length;
  14. ++e)j.push(a.charCodeAt(e));
  15. e=[];
  16. for(e.push(new b(0,j.length-1));
  17. e.length>0;
  18. ){
  19. var c=e.pop();
  20. if(!(c.s>=c.e||c.s<0||c.e>=j.length))if(c.s+1==c.e){
  21. if(j[c.s]>j[c.e]){
  22. var l=j[c.s];
  23. j[c.s]=j[c.e];
  24. j[c.e]=l
  25. }
  26. }
  27. else{
  28. for(var l=c.s,J=c.e,f=j[c.s];
  29. c.s<c.e;
  30. ){
  31. for(;
  32. c.s<c.e&&j[c.e]>=f;
  33. )c.e–,r[0]=r[0]+3&255;
  34. c.s<c.e&&(j[c.s]=j[c.e],c.s++,r[1]=r[1]*13+43&255);
  35. for(;
  36. c.s<c.e&&j[c.s]<=f;
  37. )c.s++,r[2]=r[2]-3&255;
  38. c.s<c.e&&(j[c.e]=j[c.s],c.e–,r[3]=(r[0]^r[1]^r[2]^r[3]+1)&255)
  39. }
  40. j[c.s]=f;
  41. e.push(new b(l,c.s-1));
  42. e.push(new b(c.s+1,J))
  43. }
  44. }
  45. j=[“0″,”1″,”2″,”3″,”4”,
  46. “5”,”6″,”7″,”8″,”9″,”A”,”B”,”C”,”D”,”E”,”F”];
  47. e=””;
  48. for(c=0;
  49. c<r.length;
  50. c++)e+=j[r[c]>>4&15],e+=j[r[c]&15];
  51. return e
  52. }
  53. </script>

 

以下是C#最新版本(20131021 ):
该C#WebQQ协议好友Hash 算法当前时间节点,使用的东西与以前不同,须传入 unit 和 ptwebqq 

using System;
using System.Collections.Generic;
using System.Text;

namespace Seirsoft.Common
{
public class SQQFriendHash
{
public QQFriendHash(int b, int i)
{
this.s = b | 0;
this.e = i | 0;
}
public int e;
public int s;

public static string GetHash(uint uin, string ptwebqq)
{
uint[] r = new uint[4];
r[0] = uin >> 24 & 255;
r[1] = uin >> 16 & 255;
r[2] = uin >> 8 & 255;
r[3] = uin & 255;
var ptArray = new List<int>();//int[] j=new int[64];
for (int e = 0; e < ptwebqq.Length; ++e)
ptArray.Add((Encoding.Default.GetBytes(ptwebqq)[e]));//(Encoding.Default.GetBytes(ptwebqq)[j + x]

Stack<QQFriendHash> e1 = new Stack<QQFriendHash>();
for (e1.Push(new QQFriendHash(0, ptArray.Count – 1)); e1.Count > 0; )
{
var c = e1.Pop();
if (!(c.s >= c.e || c.s < 0 || c.e >= ptArray.Count))
{
if (c.s + 1 == c.e)
{
if (ptArray[c.s] > ptArray[c.e])
{
var l = ptArray[c.s];
ptArray[c.s] = ptArray[c.e];
ptArray[c.e] = l;
}

}
else
{
int f = ptArray[c.s];
int l = c.s;
int J = c.e;
for (; c.s < c.e; )
{
for (; c.s < c.e && ptArray[c.e] >= f; )
{
c.e–; r[0] = r[0] + 3 & 255;
}
if (c.s < c.e)
{
ptArray[c.s] = ptArray[c.e]; c.s++; r[1] = r[1] * 13 + 43 & 255;
}
for (; c.s < c.e && ptArray[c.s] <= f; )
{
c.s++;
r[2] = r[2] – 3 & 255;
}
if (c.s < c.e)
{
ptArray[c.e] = ptArray[c.s]; c.e–; r[3] = (r[0] ^ r[1] ^ r[2] ^ r[3] + 1) & 255;
}
}
ptArray[c.s] = f;
e1.Push(new QQFriendHash(l, c.s – 1));
e1.Push(new QQFriendHash(c.s + 1, J));
}

}
}
string[] j1 = new string[]{“0″,”1″,”2″,”3″,”4”,
“5”,”6″,”7″,”8″,”9″,”A”,”B”,”C”,”D”,”E”,”F”};
string e2 = “”;
for (int c1 = 0; c1 < r.Length; c1++)
{
e2 += j1[r[c1] >> 4 & 15]; e2 += j1[r[c1] & 15];
}
return e2;
}

}
}

 

QQ计算hash的函数P一直在变,总不能一直去手动修改吧?
解决办法:动态获取这个js文件—》获取计算hash的js函数P的代码–》使用PyV8执行js代码获取hash
  1. def hash_p(self,b=”,i=”):
  2.     if b==” and i==”:
  3.         b=self.qq
  4.         i=self.ptwebqq
  5.     data = urllib2.urlopen(“http://0.web.qstatic.com/webqqpic/pubapps/0/50/eqq.all.js”).read()
  6.     data = data.split(“P=function”)[1].split(“,r=function”)[0]
  7.     data = “P=function” + data
  8.     import PyV8
  9.     js = PyV8.JSContext()          # create a context with an implicit global object
  10.     js.enter()                     # enter the context (also support with statement)
  11.     data = data + ‘;P(“‘ + b + ‘”,”‘ + i + ‘”);’
  12.     print data
  13.     s = js.eval(data)
  14.     print  s
  15.     return s

WebQQ 上线之后获取好友列表、群列表等资料

还没有完成webqq登录以及发送心跳包的朋友,请尽快搞定,可以参考这里 “ WebQQ协议 模拟登录(精解)”,如果还没搞定,可以参考一下这篇文章“关于WebQQ登录问题各种错误的总结 ”。
这里我们来分析获取资料。
1.获取好友资料:

这个请求是一个POST提交数据的链接。要提交的数据包括:

r={“h”:”hello”,”hash”:”6F7DCB78″,”vfwebqq”:”5d9df670d9c718a315075560f100c0f9d7c25a175cafabb1850d6d6601a9827d6784e97ad4a1287f”}

这里的 h参数,固定为“hello”
hash参数,具体算法为:WebQQ获取好友的hash参数算法[Js,c#]实现[2013/8/17] 。
vfwebqq的结果是之前在登录步骤的最后一步返回结果中获取的参数。
要注意的地方,那就是这里的r的值,也就是后面的大括号({})以及包括的字符串都是要经过url编码的。具体编码可以参考之前的文章。
如果返回正确的话,会是以下的数据(有些uin用xxx表示3个数字,自己随便想3个数字上去就是了,…表示很多,省略了):

{“retcode”:0,”result”:{“friends”:[{“flag”:0,”uin”:3413xxx582,”categories”:0},…,{“flag”:0,”uin”:4034xxx110,”categories”:0}],”marknames”:[{“uin”:323xxx1328,”markname”:”WWW”,”type”:0}],”categories”:[{“index”:1,”sort”:1,”name”:”朋友”},{“index”:2,”sort”:3,”name”:”家人”},{“index”:3,”sort”:2,”name”:”同学”}],”vipinfo”:[{“vip_level”:0,”u”:3413xxx582,”is_vip”:0},…,{“vip_level”:0,”u”:4034xxx110,”is_vip”:0}],”info”:[{“face”:0,”flag”:289931776,”nick”:”浙林龙哥”,”uin”:2237080851},…,{“face”:558,”flag”:8388608,”nick”:”QQ群发工具”,”uin”:40340xxx10}]}}

 

WebQQ 各种数字结果所代表的含义浅析

WebQQ发的每一个心跳包返回都是有含义的.从js里查看到如下数据:

this.sendPollSuccess=function(a,d){

c.pollWatcher.pollStop();

(new Date).getTime();

h–;

if(a.retcode===0||a.retcode===102){

g=0;

try{

k.notifyObservers(this,”PollSuccess”,a.result)

}

catch(f){

e.out(“PollSuccess, but [PollSuccess notify] error!!!!!!!!!!!!!!!!!!!!!!!!”,1)

}

try{

k.notifyObservers(this,”PollComplete”)

}

catch(m){

e.out(“PollComplete, but [PollComplete notify] error!!!!!!!!!!!!!!!!!!!!!!!!”,1)

}

}

else if(a.retcode===100)k.notifyObservers(this,”NotReLogin”);

else if(a.retcode===120)k.notifyObservers(EQQ,”ReLinkFailure”,a);

else if(a.retcode===121)k.notifyObservers(EQQ,”ReLinkFailure”,a);

else if(a.retcode===116)alloy.portal.setPtwebqq(a.p),k.notifyObservers(this,”PollComplete”);

else{

try{

k.notifyObservers(o,”PollComplete”)

}

catch(s){

e.out(“PollComplete, but [PollComplete notify] error!!!!!!!!!!!!!!!!!!!!!!!!”,1)

}

a.retcode!=109&&a.retcode!=110&&y()

}

n(a,d)

};

 

其中,如果返回值retcode=0,表示有新消息到了,这个消息可能是所有各种类型.而102是告诉我们,暂时没有什么新消息
retcode如果是100,说明页面过期什么的,需要重新登录了
120和121都是同一种类型,那就是号码出现了异常(比如号码被限制了什么的),或是网页请求出现了严重的错误需要重新登录.
如果是116,你就要注意了,他返回的html里含包含了一个p的参数.这个p的值就是ptwebqq的新值,需要用这个值替换原先那个.
retcode其他就是没处理到的数据,比如109和110,就是表示已经下线了之类的信息.

除了以上js里分析到的数据,还存在别的一些可能性.
比如:如果返回的HTML数据里包含kick_message,代表QQ在别的地方登录了
如果出现:很遗憾,网络连接出现异常,请您稍后再试,这个问题八成是说明cookie没有带进请求.需要重点排查cookie
如果出现:’http://aq.qq.com…这样的链接,对不起,你的号码已经被限制登录了,需要去解除限制.
如果出现:{“retcode”:103,”errmsg”:””} 这中可能性为页面长时间没反应,或是请求失败了需要重新登录
说这些也不是完全没依据的:

this.sendLoginSuccess=function(a,c){

 

switch(a.retcode){

 

case 0:f=1;

k.notifyObservers(EQQ,”LoginSuccess”,a.result);

alloy.portal.speedTest.sRTS(4,”start”,new Date);

alloy.portal.speedTest.sRTS(5,”start”,new Date);

break;

 

case 103:k.notifyObservers(this,”NotLogin”,a.result);

break;

 

case 106:k.notifyObservers(EQQ,”UinNotInWhitelist”,a.result);

break;

 

case 111:k.notifyObservers(EQQ,”UinInBlacklist”,a.result);

break;

 

case 112:k.notifyObservers(EQQ,”Overload”,a.result);

break;

 

case 1E5:case 100001:case 100002:k.notifyObservers(EQQ,”PtwebqqFail”,a.result);

break;

 

default:e.out(“\u672a\u77e5\u767b\u5f55\u5931\u8d25″),k.notifyObservers(EQQ,”LoginFailure”,{

text:”\u8fde\u63a5\u5931\u8d25″

}),e.out(“[sendLogin] error: “+a.retcode),n(a,!c)

 

 

 

this.sendReLinkSuccess=function(a,c){

 

switch(a.retcode){

case 0:f=1;

k.notifyObservers(EQQ,”ReLinkSuccess”,a.result);

break;

 

case 103:k.notifyObservers(this,”NotReLogin”,a.result);

break;

 

case 113:case 115:case 112:k.notifyObservers(EQQ,”ReLinkFailure”,a);

break;

 

default:k.notifyObservers(EQQ,”ReLinkStop”),n(a,!c)

}

n(a,c)

 

 

 

this.onSendMsgError=function(a){

var b=c.View.getChatBox(a.uin);

if(b)switch(a.retcode){

case 103:case “103”:b.appendErrorMsg(“\u60a8\u7684\u767b\u5f55\u5df2\u5931\u6548\uff0c\u8bf7\u767b\u5f55\u540e\u518d\u5c1d\u8bd5\u3002”);

break;

 

case 100:case “100”:b.appendErrorMsg(“\u60a8\u5df2\u7ecf\u5904\u4e8e\u79bb\u7ebf\u72b6\u6001\uff0c\u8bf7\u4e0a\u7ebf\u540e\u518d\u5c1d\u8bd5\u3002”);

break;

 

case 123:case “123”:b.appendErrorMsg(“\u53d1\u9001\u6d88\u606f\u5185\u5bb9\u8d85\u957f\uff0c\u8bf7\u5206\u6761\u53d1\u9001\u3002”);

break;

 

default:b.addSendMsgErr(a)

}

 

以上说明:
103 :页面过期,需要重新登录 ,提示:登录已经失效,请重新登录后再尝试
106:号码不在白名单里,意思就是可能号码被限制了.
111:号码在黑名单里,这么多名堂,意思可能是说号码被限制了,同上.
112,115:负载过重?重定向页面连接失败,
100000,100001,100002:这三个比较少见,可能是都是说的ptwebqq参数错误的问题吧:提示:登录信息超时,是否重试?
100:在发送消息的时候会出现,提示:您的号码已经处于离线状态了,请上线后在尝试…
123:发送消息内容超长,请分条发送
其他结果都意味着登录失败.

关于WebQQ登录问题各种错误的总结

src: http://www.10qf.com/thread-45-1-1.html

WebQQ协议在08年就开始存在了,期间一直变化。你问我变化有多大有多快,就跟小孩子变脸一样!很多这方面的老手都知道,也一直苦于应对他的变化。然而作为新手,如果还卡在登录的某一个环节苦苦挣扎,也很正常。下面我们来总结一下,webqq协议可能会遇到的一些问题。
1.Cookie怎么带进请求包一起发送到服务器
这个问题比较幼稚,如果连这一步都卡着了。没办法,只能把HTTP基础再去深刻了解一下了。Cookie只存在于客户端。如果要从本地客户端存储的cookie取出来并附加在HttpRequest请求对象里,在C#里是很容易的。当然我说的悟出来并不是让你从IE浏览器里的cookie取出来,那不属于我们要谈论的范围了。在c#里,有一个命名空间叫System.Net,底下封装了大量用于网络请求的基础组件。比如:HttpWebRequest,HttpWebResponse,Cookie,CookieContainer等等。反正方便程度全“语言系”最低了!具体我会单独列出来一篇介绍的文章的。

2.每次登录都说验证码错误
出现这个问题,在可以确定没有输错验证码的情况下,很容易想到,是不是我们的cookie没有更新或是别的错误,提交上去之后服务器返回错误。如果您遇上这个问题了,9成把握是你的cookie提交错误了。检查一下吧!

3.登录第二步时出现 105的错误

之所以会出现这个错误,有一种可能,就是缺少一些必要的cookie,就如我之前在 WebQQ协议 模拟登录(精解)  一问最后所提到的cookie问题。少了这几个cookie要怎么才能获取到呢?对了,就是加上在第一步返回结果里提取出来的链接请求,cookie自然就有了。

4.Poll返回108错误
Poll的时候,可以POST数据,也可以作为参数进行传递。但是这两个数据 clientid和psessionid的值要是不对的话,可能就会是108错误了,还有就是您,压根就没有在最后一步登录成功,也会出现这样的提示。另外,clientid这个数据是从登录最初开始就要一直固定不变,不要直接将“clientid”= Getcliengid();这样写上去,这样他的值可不一定相同,很有可能每次请求都不一样。因此这里要特别注意。

5.103,102,121,109错误代码的涵义
103,代表掉线了
102,暂时是没有消息
121,此QQ号被限制登录了
109,网络问题导致掉线,要重新登录

最后,还给大家总结一下各种错误代码:
WebQQ 各种数字结果所代表的含义浅析

QQ 2011 Protocol

QQ2011Protocol

source:https://github.com/cnangel/pidgin-libqq

 

Introduction

I’m not really sure that this should be considered a new version of the protocol, however there are some differences between the current QQ protocol and the described QQ2010 protocol.

Differences

Version string

The 2 byte version string at the beginning of every message has been changed to

New message

A new message has been introduced that is used when AND ONLY WHEN a captcha is being sent by the server. It’s sent after the server has sent image data for the captcha.

The client sends:

The server responds with:

The client then responds with its original message, and the server responds with its original message.

The server message is always the same and has parts of a URL encoded inside of it. An ascii representation of the server string is:

This could quite possibly be the URL to get a new captcha?

pidgin中qq plugin主要流程分析(附登陆协议流程图)

/* libperple/protocol/QQ.C */
1)
// plugin 的注册主结构,这里我们重点关注登陆的函数 qq_login
static PurplePluginProtocolInfo prpl_info =
{
OPT_PROTO_CHAT_TOPIC | OPT_PROTO_USE_POINTSIZE,
NULL,       /* user_splits */
NULL,       /* protocol_options */
{“png”, 96, 96, 96, 96, 0, PURPLE_ICON_SCALE_SEND}, /* icon_spec */
_qq_list_icon,      /* list_icon */
_qq_list_emblem,     /* list_emblems */
_qq_status_text,     /* status_text */
_qq_tooltip_text,     /* tooltip_text */
_qq_away_states,     /* away_states */
_qq_buddy_menu,      /* blist_node_menu */
qq_chat_info,      /* chat_info */
qq_chat_info_defaults,     /* chat_info_defaults */
qq_login,       /* open */
 qq_close,      /* close */
_qq_send_im,      /* send_im */
NULL,       /* set_info */
NULL,       /* send_typing */
_qq_get_info,      /* get_info */
_qq_set_away,      /* set_away */
NULL,       /* set_idle */
NULL,       /* change_passwd */
qq_add_buddy,      /* add_buddy */
NULL,       /* add_buddies */
qq_remove_buddy,     /* remove_buddy */
NULL,       /* remove_buddies */
NULL,       /* add_permit */
NULL,       /* add_deny */
NULL,       /* rem_permit */
NULL,       /* rem_deny */
NULL,       /* set_permit_deny */
qq_group_join,      /* join_chat */
NULL,       /* reject chat invite */
NULL,       /* get_chat_name */
NULL,       /* chat_invite */
NULL,       /* chat_leave */
NULL,       /* chat_whisper */
_qq_chat_send,   /* chat_send */
NULL,       /* keepalive */
NULL,       /* register_user */
_qq_get_chat_buddy_info,    /* get_cb_info */
NULL,       /* get_cb_away */
NULL,       /* alias_buddy */
NULL,       /* group_buddy */
NULL,       /* rename_group */
NULL,       /* buddy_free */
NULL,       /* convo_closed */
NULL,       /* normalize */
qq_set_my_buddy_icon,     /* set_buddy_icon */
NULL,       /* remove_group */
_qq_get_chat_buddy_real_name,    /* get_cb_real_name */
NULL,       /* set_chat_topic */
NULL,       /* find_blist_chat */
qq_roomlist_get_list,     /* roomlist_get_list */
qq_roomlist_cancel,     /* roomlist_cancel */
NULL,       /* roomlist_expand_category */
NULL,       /* can_receive_file */
NULL,       /* qq_send_file send_file */
NULL,       /* new xfer */
NULL,       /* offline_message */
NULL,       /* PurpleWhiteboardPrplOps */
NULL,       /* send_raw */
NULL,       /* roomlist_room_serialize */
NULL,       /* unregister_user */
NULL,       /* send_attention */
NULL,       /* get attention_types */

sizeof(PurplePluginProtocolInfo), /* struct_size */
NULL
};

static void qq_login(PurpleAccount *account){ // *account 为传入的用户信息和网络信息
// 用户名和密码,是否隐身,网络登陆服务器,登陆协议,代理等
……………………..
// 函数最后调用 qq_connect(account);
qq_connect(account);            // 此处开始我们的调用之旅
}
/*以下函数位于 qq_network.c */
2)
 void qq_connect(PurpleAccount *account){
………..
qd->connect_data = purple_proxy_connect(NULL, account,
qd->real_hostname, qd->real_port, qq_connect_cb, gc);  // 通过代理连接 注册了连接成功后的
//callback 函数 qq_connect_cb
…………
}
/* the callback function after socket is built
* we setup the qq protocol related configuration here */
3) static void qq_connect_cb(gpointer data, gint source, const gchar *error_message){
……………
// 连接成功后,注册挂钩函数,也就是数据可读的时候调用的函数,tcp:tcp_pending, udp: udp_pending
if (qd->use_tcp)
gc->inpa = purple_input_add(qd->fd, PURPLE_INPUT_READ, tcp_pending, gc);
else
gc->inpa = purple_input_add(qd->fd, PURPLE_INPUT_READ, udp_pending, gc);
……………….
qq_send_packet_token(gc);  // 发送 LoginToken 包,此处是交互还是的源头
}

4) // 此处我们分析udp数据可读时的callback 函数
static void udp_pending(gpointer data, gint source, PurpleInputCondition cond){
……………
/* here we have UDP proxy suppport */  // 读取udp数据包,然后就可以分析数据了
buf_len = read(qd->fd, buf, MAX_PACKET_SIZE);
………………
packet_process(gc, buf, buf_len); // 对读取到的数据包进行分析,主要的工作也就在这里了。
}

5)static void packet_process(PurpleConnection *gc, guint8 *buf, gint buf_len){
………….
// 处理属于服务器的发送给我们的某些信息,需要我们自己reply,
// 
比如服务器发送的聊天信息和系统消息
(1) qq_proc_cmd_server(gc, cmd, seq, buf + bytes, bytes_not_read);

// 服务器返回的响应消息,一般而言不需要reply,或者需要进一步的交互
(2) qq_proc_cmd_reply(gc, cmd, seq, buf + bytes, bytes_not_read);
…………..
}

附登陆协议分析图: 有助于更好阅读源代码