LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

QQ自动聊天机器人(含协议讲解)

admin
2017年2月8日 11:9 本文热度 6455
前一段时间,在一个群里(好多萝莉哦),我感觉气氛比较冷,人气少,于是准备用AI活跃一下气氛。但是我发现网上的QQ聊天机器人无法满足卖萌的需求,然后和社主商量后决定写一个QQ自动聊天机器人。 

首先如何登陆QQ?普通QQ?WebQQ?3GQQ? 
普通首先被否定了,本来想用Windows API来控制QQ2012的,用Spy++试着取得句柄后,彻底傻眼了,神马按钮、文本框根本没有句柄!谈何控制! 
3GQQ的资料太少、 
WebQQ有一些资料,是WebQQ2.0的资料,我试验了下,不能用了,上网查资料发现,原来WebQQ在今年年中升级3.0版本,密码加密算法和各种都变了。。。怪不得。。。 

但是为了不辜负社长,我决定自己破解WebQQ3.0的登陆。研究了几天,用WireShark把WebQQ各种的接口找到了,但是发现密码加密算法和以前不一样了,这下怎么整?还好我在网上找到一段C#代码,可以计算出最新的加密方式,这下各种东西都齐全了,开始动工! 

语言、选择最喜欢的Visual Basic 2010,.Net很强大。WebQQ登陆需要频繁使用HTTP的Post和Get,于是我找了个C#的代码,可以实现Http\Https的Post和Get,封装成了函数,非常方便使用。 

大家可以下载我的源代码 

随便改、随便复制,只要不是非法用途就行~ 

这个是工程的大体构架,其中带点的是我们需要编写的: 
160411


打开我的源文件(需要VS2010或者VB2010 Express,VB2010Express不能看到WebQQ的加密算法和被封装的Http访问函数的部分,不过没关系,不影响理解算法) 
HTTP:这是包含Http访问的C#代码,来源于互联网,感谢作者,Http访问的部分我稍微修改了一下,修复了丢Cookie的Bug 
QQ_Encypt:QQ最新的加密算法,来自互联网 
Platform_Test:这个是主程序,为控制台界面(我比较懒),为AI模块提供平台,对AI模块提供发送信息的方法和收到信息的事件等功能。 
Rin:这个是AI模块,模拟镜音リン,就是我的头像上那只。这部分主要是识别人话,然后根据数据库内容回复,同时具备学习能力。 
QQ_Encypt_Test:这个是个演示程序,实际不包含在解决方案里,就是演示QQ登陆时需要的秘文的计算。 

由于抓包过程就像抓带有放射性物质的小强一样,这里省略,只分享结果! 
好了,下面介绍WebQQ的各种接口,大家认真看啦! 

注意:Http操作会返回cookie,应妥善收藏 
Public cookies As New Net.CookieCollection 
这些cookie用于在每一次Http操作时验证此次操作的计算机是否和上次是一台、 
以后每次提交Http操作的时候就把这些cookie都附带上就好啦 

第一步、搞定验证码 
首先确定登陆时是否需要验证码,用Get访问下 
http://ptlogin2.qq.com/check?uin=qq号&appid=1003903&r=0.23301555978693034 
其中uin是qq号,appid是应用程序编号,不可以变,据我观察r是个随机数,变不变问题都不大,为了简单干脆不变,用rnd()函数产生也行,这一步注意收集cookie实例代码: 
With Http.HttpWebResponseUtility.CreateGetHttpResponse("http://ptlogin2.qq.com/check?" + "uin=" + QQ_Number + "&appid=1003903&r=0.23301555978693034", -1, "", cookies) 
            Verify_Code = New System.IO.StreamReader(.GetResponseStream()).ReadToEnd 
            cookies.Add(.Cookies) 
End With 

Http.HttpWebResponseUtility.CreateGetHttpResponse就是之前说的C#写的Http访问方法,这个进行Get,用法很简单,参数分别是访问地址、超时、模拟的浏览器(一般写""就好)和要提交的Cookie 

如果返回的是类似ptui_checkVC(''0'',''!OSC'',''\x00\x00\x00\x00\x5c\xa4\x73\x8a'');的字符串,就说明不需要输入验证码,根据第一个参数来看,0表示不需要,第二个参数是验证码,会以!开头,需要记住(记作Verify_Code),第三个东西没用,把第三个家伙里面的\x去掉,再用计算器从十六进制转成十进制,你会看到一个熟悉的数字。。。如果第一个参数是1,则说明需要验证码,第二个参数要用来获取验证码,设法下载“http://captcha.qq.com/getimage?aid=1003903&&uin=qq号&vc_type=第二个参数” 这个地址为一个图片(可用用My.Computer.Network.DownloadFile下载),然后让用户手动输入里面的验证码,然后记下来(同样是Verify_Code变量)。如果谁能实现验证码识别就可以实现完全自动了~ 

第二步、连接服务器 
计算密文:Encrypt_Code = QQ.QQMd5.Encrypt(QQ_Number, QQ_Password, Verify_Code) 
使用GET来访问这个地址:http://ptlogin2.qq.com/login?u=qq号码&p=计算出来的密文&verifycode=验证码&webqq_type=10&remember_uin=1&login2qq=1&aid=1003903&u1=http%3A%2F%2Fweb2.qq.com%2Floginproxy.html%3Flogin2qq%3D1%26webqq_type%3D10&h=1&ptredirect=0&ptlang=2052&from_ui=1&pttype=1&dumy=&fp=loginerroralert&action=2-17-7145&mibao_css=m_webqq&t=1&g=1,注意不要搞错,后面那长串不要搞错!附带cookie!一定要!这次的cookie要返回一个ptwebqq的值,要记住,登陆时需要用。 

如果一切没有错会返回类似: 
ptuiCB(''0'',''0'',''http://web2.qq.com/loginproxy.html?login2qq=1&webqq_type=10'',''0'',''登录成功!'', ''无敌高氯酸''); 
如果计算的密文算法不对,会出现这个: 
ptuiCB(''7'',''0'','''',''0'',''很遗憾,网络连接出现异常,请您稍后再试。(3064988208)'', ''1554281354''); 

对于成功那个,最后会返回用户昵称(这里是无敌高氯酸),前面一个参数是登陆信息,比如成功登陆,再前面一个没用,是给webqq指示要跳转的页面。 

到此为止,我们可以简单测试下,打开IE浏览器,输入第一步的网址,会返回一个check文件,打开就是结果,记下验证码,如果需要就获取图片。打开我的那个工程,把QQ_Encypt_Test设为启动项目,运行了输入相关信息,计算出密文,然后用这些信息结合第二部的地址输到刚才那个IE页面的地址栏里然后导航,会下载一个login文件,打开后就是结果,如果看到登陆成功的字样,那就说明成功了。怎么样?不难吧。有人可能会问,我们在IE里没有啥保存cookie的操作,为啥能成功?因为IE会自动保存cookie然后每一次全部提交。 

本段参考代码: 
    Public Function Connect() As Boolean 
        Encrypt_Code = QQ.QQMd5.Encrypt(QQ_Number, QQ_Password, Verify_Code)    ''Calculate the weird encrypt code 
        With Http.HttpWebResponseUtility.CreateGetHttpResponse("http://ptlogin2.qq.com/login?u=" + QQ_Number + "&p=" + Encrypt_Code + "&verifycode=" + Verify_Code + "&webqq_type=10&remember_uin=1&login2qq=1&aid=1003903&u1=http%3A%2F%2Fweb2.qq.com%2Floginproxy.html%3Flogin2qq%3D1%26webqq_type%3D10&h=1&ptredirect=0&ptlang=2052&from_ui=1&pttype=1&dumy=&fp=loginerroralert&action=2-17-7145&mibao_css=m_webqq&t=1&g=1", -1, "", Cookies) 
            Cookies.Add(.Cookies) 
            UserName = New System.IO.StreamReader(.GetResponseStream()).ReadToEnd() 
            If UserName.StartsWith("ptuiCB(''0") Then 
                UserName = UserName.Split(",")(5).Replace(");", "").Replace("''", "").Trim() 
                ptwebqq = Cookies("ptwebqq").Value 
                Return True 
            Else 
                Return False 
            End If 
        End With 
   End Function 

第三步、登录 
第二布的连接成功还没有完,这次才算是真正的登录,第三部如果成功会把已经在线的WebQQ或者普通QQ踢下线。我们要做的就是给“http://d.web2.qq.com/channel/login2”Post一个JSON结构(什么是JSON?这是一种轻量级的数据交换格式,和xml有相似功能,里面可以内嵌属性、数组、多层JSON结构,不熟悉Java的同学比如我都比较陌生,JSON可以画成树形的,好理解点)的字符串: 
{"status":"", 
"ptwebqq":"上面cookie里ptwebqq的值", 
"passwd_sig":"", 
"clientid","这里随便来个数就可以了最好是1000000级别的"} 
把以上做为r,Post到那个地址里,注意附带之前收集的cookie,注意UTF8的编码,这次要加referer为"http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3"!这三点切记!以后每次Post操作都要记着这三点!后面不再提示了! 
这次Post会返回cookie,注意收集;和一个JSON结构,这时JSON解析就要上场,获取里面的psessionid和vfwebqq,以后获取好友列表、收发消息等操作都需要用到 

示例代码: 
    Public Sub Logon() 
        Dim Login_Info As IDictionary(Of String, String) = New Dictionary(Of String, String)() 
        Dim Json_Login_str As String 
        Login_Info.Add("r", "{" + Chr(34) + "status" + Chr(34) + ":" + Chr(34) + Chr(34) + "," + Chr(34) + "ptwebqq" + Chr(34) + ":" + Chr(34) + ptwebqq + Chr(34) + "," + Chr(34) + "passwd_sig" + Chr(34) + ":" + Chr(34) + Chr(34) + "," + Chr(34) + "clientid" + Chr(34) + ":" + Chr(34) + Clientid + Chr(34) + "}") 
        With Http.HttpWebResponseUtility.CreatePostHttpResponse("http://d.web2.qq.com/channel/login2", Login_Info, -1, "", System.Text.Encoding.UTF8, Cookies, "http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3") 
            Cookies.Add(.Cookies) 
            Json_Login_str = New System.IO.StreamReader(.GetResponseStream()).ReadToEnd() 
        End With 
        Dim Json_Login As Newtonsoft.Json.Linq.JObject = Newtonsoft.Json.JsonConvert.DeserializeObject(Json_Login_str) 
        psessionid = Json_Login("result")("psessionid") 
        vfwebqq = Json_Login("result")("vfwebqq") 
    End Sub 



第四步、获取好友列表 
使用Post方法访问http://s.web2.qq.com/api/get_user_friends2可以获得好友列表,注意附带Cookie、Referer要设置正确、UTF8编码!最后一次提示! 
Post的唯一一个r参数是这样的: 
{"h","hello", 
"vfwebqq":"之前获取的vfwebqq的值"} 
同样这次还返回一个JSON结构,同样解析之: 
{ 
  "retcode": 0, 
  "result": { 
    "friends": [ 
      { 
        "flag": 0, 
        "uin": 1771529444, 
        "categories": 0 
      }, 
      { 
        "flag": 0, 
        "uin": 3898920339, 
        "categories": 0 
      } 
    ], 
    "marknames": [], 
    "categories": [ 
      { 
        "index": 1, 
        "sort": 1, 
        "name": "朋友" 
      }, 
      { 
        "index": 2, 
        "sort": 2, 
        "name": "家人" 
      }, 
      { 
        "index": 3, 
        "sort": 3, 
        "name": "同学" 
      } 
    ], 
    "vipinfo": [ 
      { 
        "vip_level": 5, 
        "u": 1771529444, 
        "is_vip": 1 
      }, 
      { 
        "vip_level": 0, 
        "u": 3898920339, 
        "is_vip": 0 
      } 
    ], 
    "info": [ 
      { 
        "face": 303, 
        "flag": 13107814, 
        "nick": "1080P·人格", 
        "uin": 1771529444 
      }, 
      { 
        "face": 402, 
        "flag": 294126146, 
        "nick": "特斯拉的接班人", 
        "uin": 3898920339 
      } 
    ] 
  } 
} 

这个JSON比较长,结构也比较复杂,retcode表示操作是否成功,0是成功,其他非0的就是失败,如果是失败还有个errmsg,里面是错误信息,但一般是空的。如果成功了,会返回一个result,result里面的东西主要需要friends、categories和info(vipinfo表示用户的vip等级,marknames是备注名称),这几个都是数组(JArray): 
info里面包含所有的好友,nick是好友名称,uin是临时分配的序列号,不是QQ号,这个需要记住,以后对这个好友操作时候有用! 
categories里包含分组信息,index是组的编号,sort是排列顺序,name是分组名称 
最后从friends跟据uin获取该用户所属的组,就这样~ 

代码见WebQQ_Client.vb的Grab_Friend_List()函数


第五步、取得群列表
相信看了上面的讲解,可能已经对WebQQ是如何工作的有一定了解了吧,就是各种Get和Post,表明身份,告诉正确的地址你需要什么就行了。
取得群列表比取得好友列表简单,只需要把r参数等于{"vfwebqq":"之前取得的vfwebqq的值"} Post给"http://s.web2.qq.com/api/get_group_name_list_mask2"就好了。注意附带Cookie、Referer要设置正确、UTF8编码!最后一次提示!

同样返回的是JSON结构,不过简单多,同样,retcode表示操作是否成功,0是成功,其他非0的就是失败,如果是失败还有个errmsg,里面是错误信息,但一般是空的。如果成功了,会返回一个result。
我们需要关注gnamelist即可,需要注意的是gnamelist里每个项目的name(名称)、gid(临时群编号,不是群号)和code(群代码)都需要,gid是群发信息时需要的,code是取得群信息要用的:

{
  "retcode": 0,
  "result": {
    "gmasklist": [],
    "gnamelist": [
      {
        "flag": 16777217,
        "name": "Chemistry Fever",
        "gid": 2422023446,
        "code": 2035113860
      },
      {
        "flag": 17826817,
        "name": "⑨次元の苍歌社",
        "gid": 1301755830,
        "code": 2271299603
      }
    ],
    "gmarklist": []
  }
}

上面JSON包含两个群。代码见WebQQ_Client.vb的Grab_Group_list()函数。

第六步、收取信息

好了,现在要介绍最重要的接收信息的方法了!
http://d.web2.qq.com/channel/poll2这个地址Post下面的JSON,做为参数R,同时还要在要Post的参数里添加clientid和psessionid两项,并且赋予他们相应的值,所以一共Post三个值,一个也不可以少!
{"clientid":"之前自定义的clientid的值",
"psessionid",
"之前取得的psessionid的值",
"key":0,
"ids":[]}

这一步比较特殊,由于是持续接受,因此必须循环对服务器发送请求,并且Post的超时要设为无限长,收取信息最好放到单独线程,防止被阻塞,提高收发效率。

实例代码:
    Function Wait_For_MSG() As String
            Dim Rec_Info As IDictionary(Of String, String) = New Dictionary(Of String, String)()
            Rec_Info.Add("r", "{" + Chr(34) + "clientid" + Chr(34) + ":" + Chr(34) + Clientid + Chr(34) + "," + Chr(34) + "psessionid" + Chr(34) + ":" + Chr(34) + psessionid + Chr(34) + "," + Chr(34) + "key" + Chr(34) + ":0," + Chr(34) + "ids" + Chr(34) + ":[]}")
            Rec_Info.Add("psessionid", psessionid)
            Rec_Info.Add("clientid", Clientid)
            With Http.HttpWebResponseUtility.CreatePostHttpResponse("http://d.web2.qq.com/channel/poll2", Rec_Info, -1, "", System.Text.Encoding.UTF8, Cookies, "http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3")
                Return New System.IO.StreamReader(.GetResponseStream()).ReadToEnd()
            End With
   End Function

最后还是返回JSON结构的字符串,其中retcode和可能出现的errmsg的含义和之前说的一样,我们还是重点分析result。返回的JSON示例如下:
{
  "retcode": 0,
  "result": [
    {
      "poll_type": "message",
      "value": {
        "msg_id": 36497,
        "from_uin": 3898920339,
        "to_uin": 1554281354,
        "msg_id2": 245833,
        "msg_type": 9,
        "reply_ip": 176722328,
        "time": 1354283752,
        "content": [
          [
            "font",
            {
              "size": 9,
              "color": "000000",
              "style": [
                0,
                0,
                0
              ],
              "name": "宋体"
            }
          ],
          "测了个试 "
        ]
      }
    }
  ]
}

可以看出result是个数组,因为里面包含很多poll(为什么叫poll?这里是推送的意思,不光是好友消息或者群消息,系统消息或者好友状态改变等信息也可能通过这个传送),疼讯服务器不会一次一条给我们发送poll,因此需要循环处理每个poll。
poll_type表示各种推送的类型,value表示该poll的信息,下面分别讲解(value中的内容):
1、message表示好友消息
   from_uin是发送信息的用户,这是临时的用户代号(uid或者uin)而不是QQ号!
   content是发送信息的内容,是个数组,用JArray可以表示
   其他的不用太关心


2、group_message表示群消息
   from_uin是该消息出现的群,同样是群代码(gid)而不是群号
   send_uin是发送信息的用户,是用户代号
   content是发送消息的内容,是个数组,用JArray可以表示
   其他的不用太关心


3、system_message表示系统消息
   有什么好友申请、加群申请、被T提示等,大家可以自己研究下,设置断点然后查看JSON的字符串即可,这部分不难的~

4、input_notify表示好友正在输入信息

5、buddy_status_change表示好友在线状态改变
   client表示好友的客户端类型,1是电脑,24是IPhoneQQ


要介绍下content了,这是每个好友或者群消息必然包含的部分,是个数组,里面的元素按顺序组合起来就是该条qq消息!
里面可能包含:
1、一个第一个元素是"font"的数组,这个是每个content必须包含的,描述的是该条qq消息的字体(别告诉我你不知道每条qq消息只能有一种字体),第二个元素是个json结构,"name"是字体名称,"size"是尺寸,"color"是颜色,是16进制的数字,"style"貌似是vip字体的样式,我没有搞明白,大家自行研究。。。

2、若干第一个元素是"face"的数组,这个表示一个表情,后面的元素表示表情的编号,如101而不是"101",加引号就不对了,会发不出去

3、若干第一个元素是"offpic"的数组,这个是一个图片,我没研究清楚

4、若干字符串,表示信息的字符部分


第七步、发送消息

发好友消息的接口:http://d.web2.qq.com/channel/send_buddy_msg2
发送群消息的接口:http://d.web2.qq.com/channel/send_qun_msg2

不管发什么消息,需要Post三个东西:clientid,psessionid和r,前两者就是之前我们取得的值,Post的时候注意referer(http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3)和附带cookie!
下面说下r的内容:
r需要包含clientid和psessionid,就是之前的值
msgid是消息的序号,随机一个6位整数就成
content是发送信息的内容,同介绍接受信息时的content相同
最后说明发送目标,如果是好友消息,则设置to为好友的用户代号(不是QQ号!),如果是群消息,需要设定group_uin为群代号(不是群号)

搞定如上的东西Post一下就好了,服务器会返回一个JSON,如果里面的retcode是0就表明成功了。

代码见WebQQ_Interface.vb里的Post_Complex_MSG函数


经过如上介绍,把WebQQ的基本功能接口都介绍了一遍,这些信息足够作为AI的平台了。实际上,经过第三步后去得了clientid,psessionid和vfwebqq这三个值,其他的一切都简单了,只需要调用相应的接口(Get或者Post)即可。
其他的接口比如获取用户签名、用户信息、用户头像、群成员等,都比较简单,大家可以去探究,我这个因为AI不需要这些功能,所以就没有写了。


如何提高信息收发效率?
答案很简单,使用多线程!如果只使用单线程,发送信息时会阻塞接受消息,严重影响效率。实际我的程序工作时接收在单独进程,接收到后分割成单条的Poll,然后去处理,发送给AI程序,AI程序在新的线程中发送信息,这样接收和发送互不干扰,只要是接收到信息后很短时间内就可以准备迎接下一次的信息!

AI是如何实现的?
上面说的那么多实际上都是WebQQ是如何收发消息的,把上面的封装成平台,即提供On_QQ_Message_Arrive事件和Send_Complex_Message方法,这样AI部分就是处理事件然后发送信息。
我这个AI比较弱智,实际只是比较送来的消息然后比较数据库里的内容然后发送相关结果。
AI部分就不分析了,只涉及些基本的数据库(SQLite)操作和字符串操作。
希望比较懂AI的人再帮忙完善一下


如何使用这个现成的WebQQ登录Dll库(为了方便大家我已经把它封装成库了)
首先实例化一个WebQQ_Interface:
Dim Client as New WebQQ_Interface.WebQQ_Client(QQ_Number, QQ_Password, clientId, CMD_Password)
Client.CMD_Head = "cmd" ''让命令的开始为cmd.    ""表示禁用命令系统
WebQQ_Interface需要被实例化,因此可以实现多个QQ同时登录,一个实例代表一个QQ号,互不冲突~
再使用Client.Check_Verify_Code()检测是否成功获取或者输入验证码
使用Client.Connect()发起连接
使用Client.Logon()开始登陆
使用Client.Grab_Friend_List()拉取朋友列表和Client.Grab_Group_list()拉取群列表
以上的函数会返回是否成功,Friend_List和Groups分别是好友列表和组列表的属性
再调用Client.Listening_Thread.Start()开始监听收到的消息
然后AddHandler Client.On_Command_Arrive, AddressOf Client.Def_On_Command_Arrive,添加默认cmd命令处理的处理
最后使用Send_Complex_Message和Send_Text_Message发送消息,也可以在处理消息的时候实例化一个QQ_Message_Sender,实例化时提供之前的WebQQ_Client对象、好友代号或者群代号、是否是群这三个东西即可,用该实例的Send方法可以快速发送文字信息
QQ_Message对象有个方法,GetString,是取得消息里的字符串信息的,不包含表情和图片等;from_uin是发送消息的qq或者群,当是群消息时send_uin是发送消息的用户,From_Group指示是否为群消息

如何使用AI平台
AI平台是Platform_Test这个工程,AI的工程需要设为类库,需要饮用WebQQ_Interface.dll,里面需要有个类,公开声明的,名称为AI,这个类在平台加载时会被实例化。类里面需要有Public Function Init(_Interface As WebQQ_Client) As String这个函数,_Interface是平台送过来的用于发送接收QQ消息的对象,函数的返回值显示在平台窗口里的内容(服务器启动时的控制台里)
然后把WebQQ_Client存下来,再注册收到信息和命令的事件:
        Client = _Interface
        AddHandler _Interface.On_QQ_Message_Arrive, AddressOf On_QQ_Message_Arrive
        AddHandler _Interface.On_Command_Arrive, AddressOf On_Command_Arrive
然后编写相关事件处理程序就完工
QQ.zip834k21次
QQ.zip834k21次

该文章在 2017/2/8 11:09:14 编辑过

全部评论1

admin
2017年2月8日 11:10
附件:WebQQ_Interface.zip

该评论在 2017/2/8 11:11:21 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2024 ClickSun All Rights Reserved