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

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

通过webqq接口,可以实现发送qq消息接收qq消息等,这样,想实现一个qq聊天机器人,就不是什么难事情了了,下面开始一步步做。


1。首先调用http://ptlogin2.qq.com/check?appid=1003903&uin=qq号码,来获取该qq号码验证码之类的信息。看返回结果决定是不是要输入验证码登陆。
如果返回:ptui_checkVC('1','95ab7db15e5ab17f50f25d33598259e83ccc098c4af2f8a4');需要输入验证码,这里需要记住这个长字符串(获取验证码图片用)以及cookie
如果返回:ptui_checkVC('0','!MPG');不需要输入验证码,验证码值用!MPG代替。可能为其他字符串,但是以感叹号开头
如果需要输入验证码:则调用
http://captcha.qq.com/getimage?aid=1003903&&uin=qq号码&vc_type=95ab7db15e5ab17f50f25d33598259e83ccc098c4af2f8a4
获取验证码图片。。。
2。开始登陆,在登陆之前,需要将密码加密,tx的加密方法很复杂,不过还好,弄到了他的js文件,然后通过java的ScriptEnginee来执行这个js来获取加密后的字符串。。。
代码:

            ScriptEngineManager m = new ScriptEngineManager();
            ScriptEngine se = m.getEngineByName("javascript");
            se.eval(new FileReader(new File("1.js")));
            Object t = se.eval("md5(md5_3(\""+p+"\")+\""+code.toUpperCase()+"\");");
            return t.toString();
 

现在调用登陆接口,以获得相关的cookie。注意Referer。这个一定得加上,否则不成功:Referer : http://web2-b.qq.com/proxy.html
http://ptlogin2.qq.com/login?u=qq号码&p=密码和验证码加密后的字符串&verifycode=验证码&remember_uin=1&aid=1003903&u1=http%3A%2F%2Fweb2.qq.com%2Floginproxy.html%3Fstrong%

3Dtrue&h=1&ptredirect=0&ptlang=2052&from_ui=1&pttype=1&dumy=&fp=loginerroralert

登陆成功后,要记住返回的cookie值。
如其中ptwebqq,skey等。

3。这还没有完成,还需要再次登录,这个好像就是qq聊天接口登陆了(刚才的登陆可以理解为登陆qq网站),只有这次登陆,才算真正登陆qq,这个时候,如果你qq已经登陆,会把你的qq踢下线,而且此次登陆才算上线时间。
地址:http://web2-b.qq.com/channel/login,把一个数据结构post给它就够了
r=%7B%22status%22%3A%22%22%2C%22ptwebqq%22%3A%22{1}%22%2C%22passwd_sig%22%3A%22%22%2C%22clientid%22%3A%22{2}%22%7D
其中{1}是ptwebqq,在第2步登陆后cookie返回的,{2}clientId,自己随便定义一个数字吧。

如果成功:
会返回一个json数据结构:
{"retcode":0,"result":

{"uin":qq号码,"mode":"master","index":1055,"port":38138,"status":"online","vfwebqq":"f72a8722c988252aef4e0268f1d26a3d647f06f6ff353a5c6cdaaa49abb2fcdf0cee2d8d64373ac2","psessionid":"

8368046764001D636F6E6E7365727665725F77656271714031302E3133332E332E3234300000235100000B79026E040043F60C166D0000000A404746365677767041316D00000028F72A8722C988252AEF4E0268F1D26A3D647F06F6

FF353A5C6CDAAA49ABB2FCDF0CEE2D8D64373AC2"}}
记住其中的psessionid。后面在发送消息和获取qq消息都需要这个参数。

4。发送一个qq消息给好友
地址:http://web2-b.qq.com/channel/send_msg
同样是post:r={"to":qq号码,"face":0,"content":"[\"23\",[\"font\",{\"name\":\"宋体\",\"size\":\"10\",\"style\":

[0,0,0],\"color\":\"000000

\"}]]","msg_id":7780001,"clientid":"15778909","psessionid":"8368046764001D636F6E6E7365727665725F77656271714031302E3133332E332E3234300000326F00000B71026E040043F60C166D0000000A4042725946

34574676716D00000028E7D8E44718236B0C17365E824FD3817ED2EF6C879FEE88D07EA92D030CEA72EE8E59309863128A3E"}
{"retcode":0,"result":"ok"}
需要把这个json UrlEncode一下再发送,否则会返回错误。

发送成功返回:{"retcode":0,"result":"ok"}

5。循环获取消息接口:
通过这个接口你可以实时的不间断的获取最新的消息。
http://web2-b.qq.com/channel/poll?

clientid=15778909&psessionid=8368046764001D636F6E6E7365727665725F77656271714031302E3133332E332E3234300000326F00000B71026E040043F60C166D0000000A404272594634574676716D00000028E7D8E447182

36B0C17365E824FD3817ED2EF6C879FEE88D07EA92D030CEA72EE8E59309863128A3E&t=1288591644319

返回格式:
{"retcode":0,"result":[{"poll_type":"message","value":{"msg_id":9712,"from_uin":qq号码,"to_uin":qq号码,"msg_id2":217523,"msg_type":9,"reply_ip":2887452740,"time":1288591740,"content":[["font",{"size":9,"color":"000000","style":

[0,0,0],"name":"\u5B8B\u4F53"}],"hello world"],"raw_content":"hello world"}}]}
其中的poll_type表示消息格式,message就是普通的qq消息,可以看到发送人,发送时间,以及消息的内容等。
此接口很特殊,在实现时,需要循环不间断调用,如果没有消息返回,该接口会一直等待到,有消息,读取完后要立即再调用该接口。



6.其他接口
获取头像
http://face7.qun.qq.com/cgi/svr/face/getface?cache=0&type=1&fid=0&uin=号码

获取个人信息
http://web2-b.qq.com/api/get_single_info?tuin=qq号码
获取签名
http://web2-b.qq.com/api/get_single_long_nick?tuin=qq号码&t=1288751545148
获取好友列表
http://web2-b.qq.com/api/get_user_friends
r    {"vfwebqq":"8f1383ba2239bb7295b100af215274aff1ee4be177b467cbc386fc53ff6606a8e5941aca61d0eb51"}
获取在线的qq好友
http://web2-b.qq.com/channel/get_online_buddies?clientid=9547083&psessionid=8368046764001D636F6E6E7365727665725F77656271714031302E3133332E332E323430000062F000000B86026E040043F60C166D0000000A404F526B7558357668476D000000288F1383BA2239BB7295B100AF215274AFF1EE4BE177B467CBC386FC53FF6606A8E5941ACA61D0EB51&t=1288751548600
获取最近联系人
http://web2-b.qq.com/api/get_recent_contact
r    {"vfwebqq":"8f1383ba2239bb7295b100af215274aff1ee4be177b467cbc386fc53ff6606a8e5941aca61d0eb51"}


等等。。。

7.附件这是本人通过java写的一个实例客户端,启动后,处于接受qq消息状态,当收到好友发来的消息时,回返回"然后呢?"。。。。
类似qq聊天机器人吧。。。
1.js是qq密码的加密js文件。

。。

 

完整代码:

import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Date;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.imageio.ImageIO;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

import atg.taglib.json.util.JSONArray;
import atg.taglib.json.util.JSONException;
import atg.taglib.json.util.JSONObject;


public class QQClient {

	private int qq = -1;
	private String pwd = null;
	
	private int clientid = 66933334;//这个可以随便写
	private String psessionid = "";
	
	private String ptwebqq;
	private String vfwebqq;
	
	private String skey;
	
	private String refer = "http://web2-b.qq.com/proxy.html";
	
	private String cookie = "";
	//读取消息线程
	private boolean isrun = false;
	private Thread poolThread =new PollThread();
	public Thread getPoolThread() {
		return poolThread;
	}
	/**
	 * 记录日志
	 */
	private void log(String msg){
		System.out.println(new Date().toLocaleString()+":"+msg);
	}
	
	
	public QQClient(int qq, String pwd) { 
		this.qq = qq;
		this.pwd = pwd;
		try {
			boolean rs = checkAndLogin();
			if(rs){ 
				isrun = true;
				poolThread.start();//开始循环接收
				log("启动成功");
			}
		} catch (Exception e) { 
			e.printStackTrace();
		}
	}
	/*****************华丽的分界线*****************/
	//测试
	public static void main(String[] args) throws Exception{
		QQClient q = new QQClient(qq号码, "123"); 
		q.getPoolThread().join();
	}
	
	/*****************华丽的分界线*****************/
	/**
	 * 给toQQ发送一个msg消息,前提是toQQ是你的好友,要不然他收不到
	 */
	public boolean sendMsg(int toQQ, String message){
		try {
			JSONObject json = new JSONObject();
			json.put("to", toQQ);//要发送的人
			json.put("face", 0);
			
			JSONArray msg = new JSONArray();
			msg.add(message);
			JSONArray font = new JSONArray();
			font.add("font");
			
			JSONObject font1 = new JSONObject().put("name", "宋体").put("size", "10");
			
			JSONArray style = new JSONArray();
			style.add(0);
			style.add(0);
			style.add(0);		
			font1.put("style", style);
			font1.put("color", "000000");
			
			font.add(font1);	 
			msg.add(font);
			
			json.put("content", msg.toString());
			json.put("msg_id", new Random().nextInt(10000000));
			json.put("clientid", clientid);
			json.put("psessionid", psessionid);//需要这个才能发送
			String sendMsgUrl = "http://web2-b.qq.com/channel/send_msg";
			String content = json.toString();
			 
			content = URLEncoder.encode(content);//他要需要编码
			content ="r="+content;
			//发送
			String res = postUrl(sendMsgUrl, content);
			//不出意外,这是返回结果:{"retcode":0,"result":"ok"}
			JSONObject rh = new JSONObject(res);
			if("ok".equals(rh.getString("result"))){
				return true;
			} 
		} catch (JSONException e) { 
			e.printStackTrace();
		}
		
		return false;
	}
	
	/**
	 * 检查并且登陆
	 */
	private boolean checkAndLogin() throws Exception{
		if(qq == -1 || pwd == null)
			throw new IllegalArgumentException("qq和密码不能为空");
		String checkIdUrl = "http://ptlogin2.qq.com/check?appid=1003903&uin="+qq;
		String res = getUrl(checkIdUrl);
		//ptui_checkVC('0','!ZLE');返回这个就不需要获取验证码了。验证码就是!ZLE 
		//ptui_checkVC('1','95ab7db15e5ab17f50f25d33598259e83ccc098c4af2f8a4');这个长字符串就需要使用了
		Pattern p = Pattern. compile("\\,\\'([!\\w]+)\\'");
		Matcher m = p. matcher(res);
		String checkType = "";
		if(m.find()){
			checkType = m.group(1); 
		}
		String check = ""; 
		if(!checkType.startsWith("!")){
			//需要输入验证码
			String getCheckImageUrl = "http://captcha.qq.com/getimage?aid=1003903&uin="+qq+"&vc_type="+checkType;
			String file = readCheckImage(getCheckImageUrl);
			log("请打开"+file+",并且在这里输入其中的字符串,然后回车:");
			InputStreamReader ins = new InputStreamReader(System.in);
			BufferedReader br = new BufferedReader(ins);
			check = br.readLine();	
		}else{
			//不需要输入验证码
			check = checkType;
		}
		
		//开始登陆
		String loginUrl = "http://ptlogin2.qq.com/login?u="+qq+"&" +
				"p=" +mdP(pwd, check)+
				"&verifycode="+check+"&remember_uin=1&aid=1003903" +
				"&u1=http%3A%2F%2Fweb2.qq.com%2Floginproxy.html%3Fstrong%3Dtrue" +
				"&h=1&ptredirect=0&ptlang=2052&from_ui=1&pttype=1&dumy=&fp=loginerroralert";
		res = getUrl(loginUrl);
//		ptuiCB('0','0','http://web2.qq.com/loginproxy.html?strong=true','0','登录成功!');
//		ptuiCB('4','0','','0','您输入的验证码有误,请重试。');
	 
		p = Pattern.compile("登录成功!");//提取最后一个字符串,看看是不是 登录成功!
		m = p. matcher(res);
		if(m.find()){
			log("登陆成功"); 
		}else{
			//登陆失败
			log(checkType);
			return false;
		}
		//从cookie中提取ptwebqq,skey
		p = Pattern.compile("ptwebqq=(\\w+);");
		m = p.matcher(cookie);
		if(m.find()){
			ptwebqq = m.group(1);
		}
		p = Pattern.compile("skey=(@\\w+);");
		m = p.matcher(cookie);
		if(m.find()){
			skey = m.group(1);
		}
		log("ptwebqq="+ptwebqq+",skey="+skey);
		
		//再次登陆,只有这次登陆,才算真正登陆qq,这个时候,如果你qq已经登陆,会把你的qq踢下线,而且此次登陆才算上线。
		String channelLoginUrl = "http://web2-b.qq.com/channel/login";
		String content = "{\"status\":\"\",\"ptwebqq\":\""+ptwebqq+"\",\"passwd_sig\":\"\",\"clientid\":\""+clientid+"\"}";
		content = URLEncoder.encode(content);//urlencode 
		content = "r="+content;//post的数据
		res = postUrl(channelLoginUrl, content);//post
		//这次登陆基本上不会发生什么问题
		//下面提取很重要的2个数据psessionid ,vwebqq,通用采用正则表达式,虽然结果是个json
		p = Pattern.compile("\"vfwebqq\":\"(\\w+)\"");
		m = p.matcher(res);
		if(m.find()){
			vfwebqq = m.group(1);
		}
		p = Pattern.compile("\"psessionid\":\"(\\w+)\"");
		m = p.matcher(res);
		if(m.find()){
			psessionid = m.group(1);
		}
		log("vwebqq="+vfwebqq+","+"psessionid="+psessionid);
		//到此,登陆就算完成了,后面可以调用发送qq信息等接口了		
		 return true;
	}
	

	
	
	/**
	 * 调用tx的js来生成密钥
	 */
	public  String mdP(String p, String code){
		try {
			ScriptEngineManager m = new ScriptEngineManager();
			ScriptEngine se = m.getEngineByName("javascript");
			se.eval(new FileReader(new File("1.js")));
			Object t = se.eval("md5(md5_3(\""+p+"\")+\""+code.toUpperCase()+"\");");
			return t.toString();
		}catch (Exception e) {
			e.printStackTrace();
		} 
		return null;
	}
	/**
	 * POST一个url,contents是输入的内容
	 */
	private  String postUrl(String url, String contents){
		try{ 
			System.out.println("post>>>"+url);
			 
			URL serverUrl = new URL(url);
			HttpURLConnection conn = (HttpURLConnection) serverUrl.openConnection(); 
	        conn.setRequestMethod("POST");//"POST" ,"GET" 
	       
	        if(refer != null){
	        	conn.addRequestProperty("Referer", refer);
	        }
	        conn.addRequestProperty("Cookie", cookie);
	        conn.addRequestProperty("Accept-Charset", "UTF-8;");//GB2312,
	        conn.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.8) Firefox/3.6.8");
	        conn.setDoOutput(true); 
	        conn.connect();
	        
	        conn.getOutputStream().write(contents.getBytes());
	        
	        if(conn.getHeaderFields().get("Set-Cookie") != null){
		        for(String s:conn.getHeaderFields().get("Set-Cookie")){
		        	cookie += s;
		        }
	        }
	        
	        InputStream ins =  conn.getInputStream();
	        
	        String charset = "UTF-8"; 
	        InputStreamReader inr = new InputStreamReader(ins, charset);
	        BufferedReader bfr = new BufferedReader(inr);
	       
	        String line = "";
	        StringBuffer res = new StringBuffer(); 
	        do{
	    	    res.append(line);
	    	    line = bfr.readLine();
	    	   //System.out.println(line);
	        }while(line != null);
	      
	        System.out.println(">>>==="+res);
	        
	        return res.toString();
		}catch(Exception e){
			e.printStackTrace();
			return null;
		}
	}
	
	
	/**
	 * GET 一个url
	 */
	private  String getUrl(String url){
		try{ 
			System.out.println("get>>>"+url);
			 
			URL serverUrl = new URL(url);
			HttpURLConnection conn = (HttpURLConnection) serverUrl.openConnection(); 
	        conn.setRequestMethod("GET");//"POST" ,"GET"
	       // conn.setDoOutput(true); 
	        if(refer != null){
	        	conn.addRequestProperty("Referer", refer);
	        }
	        conn.addRequestProperty("Cookie", cookie);
	        conn.addRequestProperty("Accept-Charset", "UTF-8;");//GB2312,
	        conn.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.8) Firefox/3.6.8");
	        conn.connect();
	       
	        if(conn.getHeaderFields().get("Set-Cookie") != null){
		        for(String s:conn.getHeaderFields().get("Set-Cookie")){
		        	cookie += s;
		        }
	        }
	        InputStream ins =  conn.getInputStream();
	        
	        String charset = "UTF-8"; 
	        InputStreamReader inr = new InputStreamReader(ins, charset);
	        BufferedReader bfr = new BufferedReader(inr);
	       
	        String line = "";
	        StringBuffer res = new StringBuffer(); 
	        do{
	    	    res.append(line);
	    	    line = bfr.readLine();
	    	   //System.out.println(line);
	        }while(line != null);
	      
	        System.out.println(">>>==="+res);
	        
	        return res.toString();
		}catch(Exception e){
			e.printStackTrace();
			return null;
		}
	}
	
	/**
	 * 读取验证码。返回验证码文件保存的路径
	 */
	private String readCheckImage(String url){
		try{ 
			System.out.println("get>>>"+url);
		 
			URL serverUrl = new URL(url);
			HttpURLConnection conn = (HttpURLConnection) serverUrl.openConnection(); 
	        conn.setRequestMethod("GET");//"POST" ,"GET" 
	        
	        conn.addRequestProperty("Accept-Charset", "UTF-8;");//GB2312,
	        conn.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.8) Firefox/3.6.8");
	        conn.connect();
	        //返回的cookie
	        if(conn.getHeaderFields().get("Set-Cookie") != null)
		        for(String s:conn.getHeaderFields().get("Set-Cookie")){
		        	cookie += s;
		        }
	        
	        InputStream ins =  conn.getInputStream();
	        
	        BufferedImage bi = ImageIO.read(ins);
	        File f =new File("qqimg.jpg");
	        ImageIO.write(bi, "jpg", f);
	        
	        return f.getAbsolutePath();
		}catch(Exception e){
			e.printStackTrace(); 
		} 
		return null;
	}
	
	/**
	 * 当有qq消息是回调此函数
	 */
	public void receiveMsg(String message, int fromQQ){
		log("qq:"+fromQQ+"说:"+message);
		//test
		sendMsg(fromQQ, "然后呢?");
	}
	
	/**
	 * 通过poll一直等待服务器回应。比如收到消息啊,好友上线啊,申请好友啊之类的各类消息都会通过此接口返回,在获得数据后,应该继续poll获取下个数据
	 * http://web2-b.qq.com/channel/poll
	 */
	class PollThread extends Thread{
		
		private String pollUrl = "http://web2-b.qq.com/channel/poll";
		@Override
		public void run() { 
			String url = pollUrl+ "?clientid="+clientid+"&psessionid="+psessionid; 
			
			try {
				while(isrun){
					//线程一直等待知道服务器有返回数据
					String res = getUrl(url);
					
					JSONObject retJ = new JSONObject(res);
					if(retJ.getInt("retcode") == 0){
						JSONArray result = retJ.getJSONArray("result");
						String poll_type = result.getJSONObject(0).getString("poll_type");
						if("message".equals(poll_type)){
							//说明有人发qq消息给我,							
							String raw_content = result.getJSONObject(0).getJSONObject("value").get("raw_content").toString(); 
							int from_uin = result.getJSONObject(0).getJSONObject("value").getInt("from_uin");
							log("收到来自:"+from_uin+":"+raw_content);
							//通知客户端收到了
							receiveMsg(raw_content, from_uin);
						}
						//system_message 是系统消息
					}
				}
			} catch (JSONException e) { 
				e.printStackTrace();
			}
		}
		
	}

	
}


 

 

猜你喜欢

转载自hfutfei.iteye.com/blog/800866
今日推荐