# -*- coding:utf-8 -*- import sys, time, urllib2, cookielib, urllib import socket import threading import re import hashlib import os import json import random import traceback threading.stack_size(128*1024) re_checkVC = re.compile(r'.+\(\'(.*)\',\'(.*)\'\)') re_login = re.compile(r'.*\(\'(\d)\',.*\'(.*)\'\);') class QQClient(): def __init__(self, putFunction, item, uin, pwd): self.uin = uin self.password = pwd self.item = item self.cookie = cookielib.CookieJar() self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookie)) self.needVC = False self.verifyCode = "" self.clientid = "" self.psessionid = "" self.put = putFunction self.status = "offline" self.ip = "125.%d.216.%d" % (random.randrange(1, 255), random.randrange(1, 255)) def passwordHash(self, vCode): ''' Caculate MD5_3 ''' hash1 = hashlib.md5(hashlib.md5(hashlib.md5(self.password).digest()).digest()).hexdigest().upper() hash2 = hashlib.md5(hash1 + vCode.upper()).hexdigest().upper() return hash2 def httpGet(self, url, params = None, headers = {}): ''' Send HTTP GET Request ''' query = urllib.urlencode(params) path = "".join((url, "?", query)) print path headers["X-Forwarded-For"] = self.ip req = urllib2.Request(path, headers = headers) r = self.opener.open(req) t = r.read() r.close() return t def httpPostJSON(self, url, data = None): ''' Post HTTP Request and Parse JSON ''' print url if data is not None: data = urllib.urlencode(data) req = urllib2.Request(url, data, headers = {"Referer":"http://d.web2.qq.com/proxy.html?v=20110331002&callback=2", "X-Forwarded-For": self.ip}) r = self.opener.open(req) t = r.read() r.close() return json.loads(t) def checkVC(self): ''' Verfication Code ''' data = self.httpGet("http://ptlogin2.qq.com/check", {"uin": self.uin, "appid": "1003903"}) result = re_checkVC.match(data) # (result, VCCode) return (result.group(1) == "1", result.group(2)) def inputVerifyCode(self, vCode): data = self.httpGet("http://captcha.qq.com/getimage", {"aid": "1003903", "uin": self.uin, "vc_type": vCode}) if not os.path.exists("verify"): os.mkdir("verify") jpgfile = os.path.join("verify", str(self.uin)+".jpg") file(jpgfile, "wb").write(data) self.needVC = True if __name__ == "__main__": return raw_input("Input the verify code: ") else: while self.needVC: time.sleep(1.0) return self.verifyCode def login(self, loginStatus = "online"): self.updateStatus("logging") ''' WebQQ Login Process ''' needVerify, vCode = self.checkVC() if needVerify: vCode = self.inputVerifyCode(vCode).upper() if self.status != "logging": return # Login 1 data = self.httpGet("http://ptlogin2.qq.com/login", {"u": self.uin, "p": self.passwordHash(vCode), "verifycode": vCode, "webqq_type": 10, "remember_uin": 1, "login2qq": 1, "aid": "1003903", "u1": "http://web.qq.com/loginproxy.html?login2qq=1&webqq_type=10", "h": 1, "ptredirect": 0, "ptlang": 2052, "from_ui": 1, "pttype": 1, "dumy": "", "fp": "loginerroralert"}) result = re_login.match(data) if result.group(1) != "0": if result.group(1) == "4": self.errormsg = "验证码错误" elif result.group(1) == "3": self.errormsg = "密码错误" else: self.errormsg = "未知错误号" + result.group(1) self.updateStatus("error") print "login result: " + result.group(2) return # Login 2 self.ptwebqq = self.cookie._cookies[".qq.com"]["/"]["ptwebqq"].value print "ptwebqq", self.ptwebqq self.clientid = str(random.randrange(0, 99)) + str(int(time.time()%1000000)) self.psessionid = "null" data = json.dumps({ "status" : loginStatus, "ptwebqq" : self.ptwebqq, "passwd_sig" : "", "clientid" : self.clientid, "psessionid" : self.psessionid }) p = self.httpPostJSON("http://d.web2.qq.com/channel/login2", {"clientid" : self.clientid, "psessionid" : "null", "r" : data}) if p["retcode"] != 0: raise Exception("Login2 retcode: " + str(p["retcode"])) # Save Login Information q = p["result"] self.updateStatus(q["status"]) self.index = q["index"] self.psessionid = q["psessionid"] self.user_state = q["user_state"] self.cip = q["cip"] self.vfwebqq = q["vfwebqq"] self.port = q["port"] # Get Self Information q = self.getFriendInfo(self.uin) print "Hello", q["nick"] self.getLevel() # Keep active self.startPolling() def logout(self): if self.status == "offline" or self.status == "logging": self.updateStatus("offline") self.needVC = False return self.updateStatus("offline") self.needVC = False ''' Logout ''' url = "http://d.web2.qq.com/channel/logout2?ids=&clientid=%s&psessionid=%s&t=1316942952743" % ( str(self.clientid), self.psessionid) p = self.httpPostJSON(url) if p["retcode"] != 0: print "failed to logout: " + str(p["retcode"]) def getFriendInfo(self, uin): ''' Get Personal Information By UIN ''' url = "http://s.web2.qq.com/api/get_friend_info2?tuin=%s&verifysession=&code=&vfwebqq=%s&t=1316942923198" % ( uin, self.vfwebqq) p = self.httpPostJSON(url) if p["retcode"] != 0: raise Exception("failed to get friend info: " + str(p["retcode"])) q = p["result"] return q def updateStatus(self, newStatus): self.status = newStatus self.put({"item": self.item, "type":"status", "value": self.status}) def changeStatus(self, newStatus): url = "http://d.web2.qq.com/channel/change_status2?newstatus=%s&clientid=%s&psessionid=%s&t=1316942950439" % ( newStatus, str(self.clientid), self.psessionid) p = self.httpPostJSON(url) if p["retcode"] != 0: print "failed to changeStatus: " + str(p["retcode"]) else: self.updateStatus(newStatus) def getLevel(self): url = "http://s.web2.qq.com/api/get_qq_level2?tuin=%s&vfwebqq=%s&t=131694292988" % ( self.uin, self.vfwebqq) p = self.httpPostJSON(url) if p["retcode"] != 0: print "failed to changeStatus: " + str(p["retcode"]) else: self.put({"item": self.item, "type":"level", "value": p["result"]}) def startPolling(self): self.pollThread = threading.Thread(target = self.polling) self.pollThread.setDaemon(True) self.pollThread.start() def polling(self): ''' Keep Alive ''' while self.status != "offline": try: print "polling..." data = json.dumps({ "key" : 0, "ids" : [], "clientid" : self.clientid, "psessionid" : self.psessionid }) p = self.httpPostJSON("http://d.web2.qq.com/channel/poll2", {"clientid" : self.clientid, "psessionid" : self.psessionid, "r" : data}) retcode = p["retcode"] if retcode == 102: continue elif retcode == 109: break elif retcode == 0: for msg in p["result"]: self.processMessage(msg) else: raise Exception("Poll retcode: " + str(retcode)) except KeyboardInterrupt: break except: print traceback.format_exc() time.sleep(1.0) self.updateStatus("offline") def processMessage(self, msg): if not "poll_type" in msg: print "No poll_type: ", msg return pollType = msg["poll_type"] value = msg["value"] if pollType == "kick_message": print "I'm kicked." self.logout() elif pollType == "message": print "Message:", value else: print "Unhandled:", value def emptyFunction(msg): print "Message", msg if __name__ == "__main__": qq = QQClient(emptyFunction, "", sys.argv[1], sys.argv[2]) qq.login() while qq.status != "offline": time.sleep(1.0)