qq聊天機器人 群發工具 (java版) (一)

來源:互聯網
上載者:User

標籤:webqq   qq機器人   qq群發工具   java   

這是最近因為感興趣才寫的小東西,網上大多是易語言版,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.7602819891180843Request 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
// 接收訊息和cookiepublic 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_iJoNLBiZH3WypEcSVAUNo1Request 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=0Request Method:GET
還是GET方法,url地址即為第一步登陸返回的地址,不需要記錄任何參數,只要別忘了更新一遍cookie即可。

4.擷取vfwebqq,姑且寫在這,免得之後再介紹

Request URL:http://s.web2.qq.com/api/getvfwebqq?ptwebqq=9d37ec0c729300bb5dbd927c917f0e851794662e9164c2e6fadab64cea4f6208&clientid=53999199&psessionid=&t=1432729913114Request Method:GETReferer: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/login2Request Method:POSTContent-Type:application/x-www-form-urlencodedReferer:http://d.web2.qq.com/proxy.html?v=20130916001&callback=1&id=2form-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,附上My Code:

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版) (一)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.