无意间看到有人用传说中的3G QQ聊:http://pt.3g.qq.com/ 自己体验了一把,发现确实非常原始。都是通过刷新获得新的消息,所以感觉很容易模拟。于是用了两个晚上研究了一下。最后实现了qq登陆/收消息/发消息的功能。 运行下面的代码,会返回消息+笑脸表情。 未命名 基于jdk1.7.05。没有任何依赖。可以直接运行。 代码在下面:

import java.awt.BorderLayout;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Scanner;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class QQClient {

 String username, password;
 long lastActiveTime;
 HttpClient client;
 String sid;
 static Logger log = Logger.getAnonymousLogger();
 HashMap<String, LinkedList<String>> friends = new HashMap<>();

 public QQClient(String username, String password) {
  super();
  this.username = username;
  this.password = password;
  this.client = new HttpClient();
  System.setProperty("proxySet", "true");
  System.setProperty("proxyHost", "116.226.239.208");
  System.setProperty("proxyPort", "10086");
 }

 public static void main(String[] args) {
  QQClient qq = new QQClient("sbnith@qq.com", "shibang416");
  qq.login();
  while (true) {
   LinkedList<Msg> msgs = qq.getMsg();
   for (Msg msg : msgs) {
    System.out.println(msg);
    String m = msg.content + "\\t" + msg.time + "\\t"
      + msg.sendername + "/:)";
    System.out.println("\\n我\\n" + m);
    System.out.println(qq.sendMsg(msg.senderid, m));
   }
   try {
    Thread.sleep(1000);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }

 }

 private void login() {
  try {
   String ret = client.http("http://pt.3g.qq.com/", null, null);
   Pattern p = Pattern.compile("action=\\"(.*?)\\"");
   Matcher m = p.matcher(ret);
   String posturl = null;
   if (m.find())
    posturl = m.group(1).replace("&", "&");

   HashMap<String, String> header = new HashMap<>();
   header.put("Referer", "http://pt.3g.qq.com/");
   ret = client
     .http(posturl,
       header,
       "login_url=http%3A%2F%2Fpt.3g.qq.com%2Fs%3Faid%3DnLogin&sidtype=1&q_from=&loginTitle=%E6%89%8B%E6%9C%BA%E8%85%BE%E8%AE%AF%E7%BD%91&bid=0&qq="
         + this.username
         + "&pwd="
         + this.password
         + "&loginType=3&loginsubmit=%E7%99%BB%E5%BD%95");
   if (ret.contains("验证码")) {
    p = Pattern.compile("src=\\"(.*?)\\"");
    m = p.matcher(ret);
    String imageurl = null;
    if (m.find())
     imageurl = m.group(1);
    StringBuffer body = new StringBuffer("verify="
      + deCaptcha(imageurl, posturl));
    header.put("Referer", posturl);
    String form = ret.substring(
      ret.indexOf("action=\\"/handleLogin")).substring(8);
    posturl = "http://pt.3g.qq.com"
      + form.substring(0, form.indexOf('"')).replace("&",
        "&");
    log.info(posturl);
    p = Pattern.compile("name=\\"(.*?)\\" value=\\"(.*?)\\"/>");
    m = p.matcher(form);

    while (m.find()) {
     body.append("&" + m.group(1) + "="
       + URLEncoder.encode(m.group(2), "UTF-8"));
    }

    ret = client.http(posturl, header, body.toString());
   }
   p = Pattern.compile("sid=(.*?)&");
   m = p.matcher(ret);
   if (m.find())
    sid = m.group(1);
   log.info("sid:" + sid);
   if (sid != null) {
    header.put("Referer",
      "http://pt.3g.qq.com/s?aid=nLogin3gqq&auto=1&s_it=1&g_f=286&sid="
        + sid);
    client.http(
      "http://pt.3g.qq.com/s?aid=nLogin3gqqbysid&r=67656557",
      header, "3gqqsid=" + sid + "&auto=1&loginType=1");

   }

  } catch (Throwable e) {
   e.printStackTrace();
  }
 }

 private boolean sendMsg(String to, String msg) {
  log.fine(to + "\\n" + msg);
  if (sid == null) {
   log.warning("please login and get sid");
   return false;
  }
  try {
   msg = client.http("http://q16.3g.qq.com/g/s?sid=" + sid
     + "&aid=sendmsg&tfor=qq&referer=", null, "msg="
     + URLEncoder.encode(msg, "UTF-8") + "&u=" + to
     + "&saveURL=0&do=send&on=1&saveURL=0");
  } catch (Throwable e) {
   e.printStackTrace();
  }
  return msg.contains("消息发送成功");

 }

 private LinkedList<Msg> getMsg() {
  LinkedList<Msg> ret = new LinkedList<>();
  if (sid == null) {
   log.warning("please login and get sid");
   return ret;
  }

  String str = client.http("http://q32.3g.qq.com/g/s?sid=" + sid
    + "&3G_UIN=&saveURL=0&aid=nqqChat", null, null);
  Pattern content = Pattern.compile(
    "<div class=\\"main-module bm-blue\\">(.*?)</div>",
    Pattern.DOTALL);
  Pattern sendername = Pattern.compile("<title>与(.*?)聊天");
  Pattern senderid = Pattern.compile("name=\\"u\\" value=\\"(.*?)\\"");
  Pattern time = Pattern.compile(" (.*?)</p>", Pattern.DOTALL);
  Pattern msgcontent = Pattern.compile("<p>(.*?)</p>", Pattern.DOTALL);
  Matcher m = null;
  boolean hasMore = true;
  while (hasMore) {
   String name = null, id = null;
   m = sendername.matcher(str);
   if (m.find())
    name = m.group(1);
   m = senderid.matcher(str);
   if (m.find())
    id = m.group(1);
   int index = str.indexOf("<div class=\\"bg-blue line-h\\">");
   if (index == -1)
    return ret;
   str = str.substring(0, index);
   m = content.matcher(str);
   hasMore = false;
   while (m.find()) {
    hasMore = true;
    String temp = m.group(1);
    Matcher m1 = time.matcher(temp);
    String t = null;
    if (m1.find())
     t = m1.group(1);
    m1 = msgcontent.matcher(temp);
    String c = null;
    if (m1.find())
     c = m1.group(1);
    ret.add(new Msg(id, name, t, c));
   }
   if (hasMore)
    str = client.http("http://q32.3g.qq.com/g/s?sid=" + sid
      + "&3G_UIN=&saveURL=0&aid=nqqChat", null, null);
  }
  return ret;
 }

 private String deCaptcha(String url, String referer) {

  try {
   URLConnection con = new URL(url).openConnection();
   con.addRequestProperty("Referer", referer);
   InputStream in = con.getInputStream();
   ByteArrayOutputStream bo = new ByteArrayOutputStream();
   int b = -1;
   while ((b = in.read()) != -1)
    bo.write(b);
   in.close();
   JFrame f = new JFrame("输入验证码");
   f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   f.setAlwaysOnTop(true);
   f.setBounds(200, 200, 100, 100);
   f.add(new JLabel(new ImageIcon(bo.toByteArray())),
     BorderLayout.CENTER);
   bo.close();
   f.setVisible(true);
   System.out.println("输入看到的验证码");
   Scanner scan = new Scanner(System.in);
   String cap = scan.nextLine().trim();
   while (cap.length() != 4) {
    System.out.println("验证码是4位的");
    cap = scan.nextLine().trim();
   }
   scan.close();
   f.dispose();
   return cap;
  } catch (Exception e) {
   e.printStackTrace();
   System.exit(0);// 如果识别验证码出错。则从来
  }
  return null;

 }

 class Msg {
  String senderid;
  String sendername;
  String time;
  String content;

  public Msg(String senderid, String sendername, String time,
    String content) {
   super();
   this.senderid = senderid;
   this.sendername = sendername;
   this.time = time;
   this.content = content;
  }

  @Override
  public String toString() {
   return sendername + "(" + senderid + ")\\t" + time + "\\n" + content;
  }
 }

 class HttpClient {
  HashMap<String, String> cookies = new HashMap<String, String>();

  private String http(String url, HashMap<String, String> header,
    String body) {
   try {
    log.fine(url);
    URLConnection con = new URL(url).openConnection();

    if (cookies.size() > 0) {
     StringBuffer cookieStr = new StringBuffer();
     for (String key : cookies.keySet()) {
      cookieStr.append("; " + key + ":" + cookies.get(key));
     }
     con.addRequestProperty("Cookie", cookieStr.substring(2));
    }
    con.addRequestProperty(
      "User-Agent",
      "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.2.149.29 Safari/525.13");
    if (header != null)
     for (String key : header.keySet()) {
      con.addRequestProperty(key, header.get(key));
     }
    con.setConnectTimeout(10000);
    con.setReadTimeout(30000);
    if (body != null) {
     con.setDoOutput(true);
     byte[] b = body.getBytes("UTF-8");
     OutputStream out = con.getOutputStream();
     out.write(b);
     out.flush();
     out.close();
    }
    InputStream in = con.getInputStream();
    ByteArrayOutputStream ba = new ByteArrayOutputStream();
    int b = -1;
    while ((b = in.read()) != -1) {
     ba.write(b);
    }
    String ret = new String(ba.toByteArray(), "UTF-8");
    ba.close();
    in.close();
    // cookie
    if (con.getHeaderField("Set-Cookie") != null) {
     String[] setCookie = con.getHeaderField("Set-Cookie")
       .split("\\n");
     for (String line : setCookie) {
      String[] kv = line.substring(0, line.indexOf(';'))
        .split("=");
      cookies.put(kv[0], kv.length > 1 ? kv[1] : "");
     }
    }
    return ret;
   } catch (Throwable e) {
    e.printStackTrace();
    log.warning(e.getLocalizedMessage());
   }

   return null;

  }
 }
}