class QQ: def __init__(self): self.default_config = DefaultConfigs() self.req = HttpClient() self.friend_list = {} self.client_id = int(random.uniform(111111, 888888)) self.ptwebqq = '' self.psessionid = '' self.appid = 0 self.vfwebqq = '' self.qrcode_path = self.default_config.conf.get( "global", "qrcode_path") # QRCode保存路径 self.username = '' self.account = 0 def login_by_qrcode(self): logging.info("Requesting the login pages...") initurl_html = self.req.Get( self.default_config.conf.get("global", "smartqq_url")) logging.debug("login page html: " + str(initurl_html)) initurl = get_revalue(initurl_html, r'\.src = "(.+?)"', "Get Login Url Error.", 1) html = self.req.Get(initurl + '0') appid = get_revalue( html, r'<input type="hidden" name="aid" value="(\d+)" />', 'Get AppId Error', 1) sign = get_revalue(html, r'g_login_sig=encodeURIComponent\("(.*?)"\)', 'Get Login Sign Error', 0) js_ver = get_revalue(html, r'g_pt_version=encodeURIComponent\("(\d+)"\)', 'Get g_pt_version Error', 1) mibao_css = get_revalue(html, r'g_mibao_css=encodeURIComponent\("(.+?)"\)', 'Get g_mibao_css Error', 1) star_time = date_to_millis(datetime.datetime.utcnow()) error_times = 0 ret = [] while True: error_times += 1 self.req.Download( 'https://ssl.ptlogin2.qq.com/ptqrshow?appid={0}&e=0&l=L&s=8&d=72&v=4' .format(appid), self.qrcode_path) logging.info("Please scan the downloaded QRCode") while True: html = self.req.Get( 'https://ssl.ptlogin2.qq.com/ptqrlogin?webqq_type=10&remember_uin=1&login2qq=1&aid={0}&u1=http%3A%2F%2Fw.qq.com%2Fproxy.html%3Flogin2qq%3D1%26webqq_type%3D10&ptredirect=0&ptlang=2052&daid=164&from_ui=1&pttype=1&dumy=&fp=loginerroralert&action=0-0-{1}&mibao_css={2}&t=undefined&g=1&js_type=0&js_ver={3}&login_sig={4}' .format( appid, date_to_millis(datetime.datetime.utcnow()) - star_time, mibao_css, js_ver, sign), initurl) logging.debug("QRCode check html: " + str(html)) ret = html.split("'") if ret[1] in ( '0', '65'): # 65: QRCode 失效, 0: 验证成功, 66: 未失效, 67: 验证中 break if ret[1] == '0' or error_times > 10: break if ret[1] != '0': return logging.info("QRCode scaned, now logging in.") # 删除QRCode文件 if os.path.exists(self.qrcode_path): os.remove(self.qrcode_path) # 记录登陆账号的昵称 self.username = ret[11] html = self.req.Get(ret[5]) logging.debug("mibao_res html: " + str(html)) url = get_revalue(html, r' src="(.+?)"', 'Get mibao_res Url Error.', 0) if url != '': html = self.req.Get(url.replace('&', '&')) url = get_revalue(html, r'location\.href="(.+?)"', 'Get Redirect Url Error', 1) self.req.Get(url) self.ptwebqq = self.req.getCookie('ptwebqq') login_error = 1 ret = {} while login_error > 0: try: html = self.req.Post( 'http://d.web2.qq.com/channel/login2', { 'r': '{{"ptwebqq":"{0}","clientid":{1},"psessionid":"{2}","status":"online"}}' .format(self.ptwebqq, self.client_id, self.psessionid) }, self.default_config.conf.get("global", "connect_referer")) logging.debug("login html: " + str(html)) ret = json.loads(html) login_error = 0 except: login_error += 1 logging.info("login fail, retrying...") if ret['retcode'] != 0: logging.debug(str(ret)) logging.warning("return code:" + str(ret['retcode'])) return self.vfwebqq = ret['result']['vfwebqq'] self.psessionid = ret['result']['psessionid'] self.account = ret['result']['uin'] logging.info("QQ:{0} login successfully, Username:{1}".format( self.account, self.username)) def relogin(self, error_times=0): if error_times >= 10: return False try: html = self.req.Post( 'http://d.web2.qq.com/channel/login2', { 'r': '{{"ptwebqq":"{0}","clientid":{1},"psessionid":"{2}","key":"","status":"online"}}' .format(self.ptwebqq, self.client_id, self.psessionid) }, self.default_config.conf.get("global", "connect_referer")) logging.debug("relogin html: " + str(html)) ret = json.loads(html) self.vfwebqq = ret['result']['vfwebqq'] self.psessionid = ret['result']['psessionid'] return True except: logging.info("login fail, retryng..." + str(error_times)) return self.relogin(error_times + 1) def check_msg(self, error_times=0): if error_times >= 5: if not self.relogin(): raise IOError("Account offline.") else: error_times = 0 # 调用后进入单次轮询,等待服务器发回状态。 html = self.req.Post( 'http://d.web2.qq.com/channel/poll2', { 'r': '{{"ptwebqq":"{1}","clientid":{2},"psessionid":"{0}","key":""}}' .format(self.psessionid, self.ptwebqq, self.client_id) }, self.default_config.conf.get("global", "connect_referer")) logging.debug("check_msg html: " + str(html)) try: if html == "": return self.check_msg() ret = json.loads(html) ret_code = ret['retcode'] if ret_code in (102, ): logging.info("received retcode: " + str(ret_code) + ": No message.") time.sleep(1) return if ret_code in (103, ): logging.warning("received retcode: " + str(ret_code) + ": Check error.retrying.." + str(error_times)) time.sleep(1) return self.check_msg(error_times + 1) if ret_code in (121, ): logging.warning("received retcode: " + str(ret_code)) return self.check_msg(5) elif ret_code == 0: msg_list = [] pm_list = [] sess_list = [] group_list = [] notify_list = [] for msg in ret['result']: ret_type = msg['poll_type'] if ret_type == 'message': pm_list.append(PmMsg(msg)) elif ret_type == 'group_message': group_list.append(GroupMsg(msg)) elif ret_type == 'sess_message': sess_list.append(SessMsg(msg)) elif ret_type == 'input_notify': notify_list.append(InputNotify(msg)) elif ret_code == 'kick_message': notify_list.append(KickMessage(msg)) else: logging.warning("unknown message type: " + str(ret_type) + "details: " + str(msg)) group_list.sort(key=lambda x: x.seq) msg_list += pm_list + sess_list + group_list + notify_list if not msg_list: return return msg_list elif ret_code == 100006: logging.warning("POST data error") return elif ret_code == 116: self.ptwebqq = ret['p'] logging.info("ptwebqq updated.") return else: logging.warning("unknown retcode " + str(ret_code)) return except ValueError, e: logging.warning("Check error occured: " + str(e)) time.sleep(1) return self.check_msg(error_times + 1) except BaseException, e: logging.warning("Unknown check error occured, retrying. Error: " + str(e)) time.sleep(1) return self.check_msg(error_times + 1)
class QQ: def __init__(self): self.default_config = DefaultConfigs() self.req = HttpClient() # cache self.friend_list = {} self.__groupSig_list = {} self.__self_info = {} self.client_id = 53999199 self.ptwebqq = '' self.psessionid = '' self.appid = 0 self.vfwebqq = '' self.qrcode_path = self.default_config.conf.get( "global", "qrcode_path") # QRCode保存路径 self.username = '' self.account = 0 init_logging() def __hash_digest(self, uin, ptwebqq): """ 计算hash,貌似TX的这个算法会经常变化,暂时不使用 get_user_friends2, get_group_name_list_mask2 会依赖此数据 提取自http://pub.idqqimg.com/smartqq/js/mq.js :param uin: :param ptwebqq: :return: """ N = [0, 0, 0, 0] # print(N[0]) for t in range(len(ptwebqq)): N[t % 4] ^= ord(ptwebqq[t]) U = ["EC", "OK"] V = [0, 0, 0, 0] V[0] = int(uin) >> 24 & 255 ^ ord(U[0][0]) V[1] = int(uin) >> 16 & 255 ^ ord(U[0][1]) V[2] = int(uin) >> 8 & 255 ^ ord(U[1][0]) V[3] = int(uin) & 255 ^ ord(U[1][1]) U = [0, 0, 0, 0, 0, 0, 0, 0] for T in range(8): if T % 2 == 0: U[T] = N[T >> 1] else: U[T] = V[T >> 1] N = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" ] V = "" for T in range(len(U)): V += N[U[T] >> 4 & 15] V += N[U[T] & 15] return V def __getGroupSig(self, guin, tuin, service_type=0): key = '%s --> %s' % (guin, tuin) if key not in self.__groupSig_list: url = "http://d1.web2.qq.com/channel/get_c2cmsg_sig2?id=%s&to_uin=%s&service_type=%s&clientid=%s&psessionid=%s&t=%d" % ( guin, tuin, service_type, self.client_id, self.psessionid, int(time.time() * 100)) response = self.req.Get(url) rsp_json = json.loads(response) if rsp_json["retcode"] != 0: return "" sig = rsp_json["result"]["value"] self.__groupSig_list[key] = sig if key in self.__groupSig_list: return self.__groupSig_list[key] return "" def __login(self, times=10): login_error = 1 while login_error > 0: try: logging.info( "RUNTIMELOG Tring to login in with cookies. {0}".format( login_error)) print('Tring to auto login in.') self.ptwebqq = self.req.getCookie('ptwebqq') html = self.req.Post( 'http://d1.web2.qq.com/channel/login2', { 'r': '{{"ptwebqq":"{0}","clientid":{1},"psessionid":"{2}","status":"online"}}' .format(self.ptwebqq, self.client_id, self.psessionid) }, self.default_config.conf.get("global", "connect_referer")) logging.debug("RESPONSE login html: " + str(html)) ret = json.loads(html) html2 = self.req.Get( "http://s.web2.qq.com/api/getvfwebqq?ptwebqq={0}&clientid={1}&psessionid={2}&t={3}" .format(self.ptwebqq, self.client_id, self.psessionid, self.req.getTimeStamp())) logging.debug("RESPONSE getvfwebqq html: " + str(html2)) ret2 = json.loads(html2) if (ret['retcode'] != 0) or (ret2['retcode'] != 0): logging.debug("RESPONSE " + str(ret)) logging.debug("RESPONSE " + str(ret2)) logging.warning( "RUNTIMELOG login2 retcode: {login2}, getvfwebqq retcode: {getvfwebqq}" .format(login2=str(ret['retcode']), getvfwebqq=str(ret2['retcode']))) raise self.psessionid = ret['result']['psessionid'] self.account = ret['result']['uin'] self.vfwebqq = ret2['result']['vfwebqq'] logging.info("RUNTIMELOG Login successfully.") print('Login successfully.') return True except: login_error += 1 logging.info("RUNTIMELOG Login fail, retrying...") print('auto login fail') if login_error > times: return False def __login_by_qrcode(self): try: logging.info("RUNTIMELOG Trying to login by qrcode.") logging.info("RUNTIMELOG Requesting the qrcode login pages...") # initurl_html = self.req.Get(self.default_config.conf.get("global", "smartqq_url")) # logging.debug("RESPONSE login page html: " + str(initurl_html)) # mqjs_url = get_revalue(initurl_html, r'<script src="(http://pub.idqqimg.com/smartqq/js/mq.js?t=\d+)', "Get mqjs_url Error.", 1) # mqjs = self.req.Get(mqjs_url) # initurl = get_revalue(initurl_html, r'\.src = "(.+?)"', "Get Login Url Error.", 1) # html = self.req.Get(initurl + '0') initurl = "https://ui.ptlogin2.qq.com/cgi-bin/login?daid=164&target=self&style=16&mibao_css=m_webqq&appid=501004106&enable_qlogin=0&no_verifyimg=1&s_url=http%3A%2F%2Fw.qq.com%2Fproxy.html&f_url=loginerroralert&strong_login=1&login_state=10&t=20131024001" html = self.req.Get( "https://ui.ptlogin2.qq.com/cgi-bin/login?daid=164&target=self&style=16&mibao_css=m_webqq&appid=501004106&enable_qlogin=0&no_verifyimg=1&s_url=http%3A%2F%2Fw.qq.com%2Fproxy.html&f_url=loginerroralert&strong_login=1&login_state=10&t=20131024001" ) appid = get_revalue( html, r'<input type="hidden" name="aid" value="(\d+)" />', 'Get AppId Error', 1) sign = get_revalue(html, r'g_login_sig=encodeURIComponent\("(.*?)"\)', 'Get Login Sign Error', 0) js_ver = get_revalue( html, r'g_pt_version=encodeURIComponent\("(\d+)"\)', 'Get g_pt_version Error', 1) mibao_css = get_revalue( html, r'g_mibao_css=encodeURIComponent\("(.+?)"\)', 'Get g_mibao_css Error', 1) star_time = date_to_millis(datetime.datetime.utcnow()) error_times = 0 ret = [] while True: error_times += 1 print('download QRcode image...') self.req.Download( 'https://ssl.ptlogin2.qq.com/ptqrshow?appid={0}&e=0&l=L&s=8&d=72&v=4' .format(appid), self.qrcode_path) print "请扫描二维码" _thread.start_new_thread(display_QRCode, (self.qrcode_path, )) while True: html = self.req.Get( 'https://ssl.ptlogin2.qq.com/ptqrlogin?webqq_type=10&remember_uin=1&login2qq=1&aid={0}&u1=http%3A%2F%2Fw.qq.com%2Fproxy.html%3Flogin2qq%3D1%26webqq_type%3D10&ptredirect=0&ptlang=2052&daid=164&from_ui=1&pttype=1&dumy=&fp=loginerroralert&action=0-0-{1}&mibao_css={2}&t=undefined&g=1&js_type=0&js_ver={3}&login_sig={4}' .format( appid, date_to_millis(datetime.datetime.utcnow()) - star_time, mibao_css, js_ver, sign), initurl) logging.debug("RESPONSE QRCode check html: " + str(html)) ret = html.split("'") if ret[1] in ( '0', '65'): # 65: QRCode 失效, 0: 验证成功, 66: 未失效, 67: 验证中 break time.sleep(1) if ret[1] == '0' or error_times > 10: break time.sleep(1) if ret[1] != '0': return False # 删除QRCode文件 if os.path.exists(self.qrcode_path): os.remove(self.qrcode_path) html = self.req.Get(ret[5]) logging.debug("RESPONSE mibao_res html: " + str(html)) return True except BaseException: logging.info("RUNTIMELOG QRcode login fail") print('二维码登陆失败') return False def login(self): if not self.__login(1): while True: if self.__login_by_qrcode(): if self.__login(): break ret = self.get_self_info2() try: self.username = ret['nick'] except KeyError: print ret exit() logging.info( "RUNTIMELOG QQ:{0} login successfully, Username:{1}".format( self.account, self.username)) print "QQ:{0} login successfully, Username:{1}".format( self.account, self.username) def check_msg(self, error_times=0): if error_times >= 5: if not self.__login(1): raise IOError("Account offline.") else: error_times = 0 # 调用后进入单次轮询,等待服务器发回状态。 html = self.req.Post( 'http://d1.web2.qq.com/channel/poll2', { 'r': '{{"ptwebqq":"{ptwebqq}","clientid":{clientid},"psessionid":"{psessionid}","key":""}}' .format(psessionid=self.psessionid, ptwebqq=self.ptwebqq, clientid=self.client_id) }, self.default_config.conf.get("global", "connect_referer")) logging.debug("RESPONSE check_msg html: " + str(html)) try: if html == "": return self.check_msg() ret = json.loads(html) ret_code = ret['retcode'] # if ret_code in (0,): # logging.info("received retcode: " + str(ret_code) + ": No message.") # time.sleep(1) # return if ret_code in (103, ): logging.warning("RUNTIMELOG received retcode: " + str(ret_code) + ": Check error.retrying.." + str(error_times)) time.sleep(1) return self.check_msg(error_times + 1) elif ret_code in (121, ): logging.warning("RUNTIMELOG received retcode: " + str(ret_code)) return self.check_msg(5) elif ret_code == 0: if 'result' not in ret or len(ret['result']) == 0: logging.info("RUNTIMELOG received retcode: " + str(ret_code) + ": No message.") time.sleep(1) return msg_list = [] pm_list = [] sess_list = [] group_list = [] notify_list = [] for msg in ret['result']: ret_type = msg['poll_type'] if ret_type == 'message': pm_list.append(PmMsg(msg)) elif ret_type == 'group_message': group_list.append(GroupMsg(msg)) elif ret_type == 'sess_message': sess_list.append(SessMsg(msg)) elif ret_type == 'input_notify': notify_list.append(InputNotify(msg)) elif ret_code == 'kick_message': notify_list.append(KickMessage(msg)) else: logging.warning("RUNTIMELOG unknown message type: " + str(ret_type) + "details: " + str(msg)) group_list.sort(key=lambda x: x.msg_id) msg_list += pm_list + sess_list + group_list + notify_list if not msg_list: return return msg_list elif ret_code == 100006: logging.warning("RUNTIMELOG POST data error") return elif ret_code == 116: self.ptwebqq = ret['p'] logging.info("RUNTIMELOG ptwebqq updated.") return else: logging.warning("RUNTIMELOG unknown retcode " + str(ret_code)) return except KeyboardInterrupt: logging.info("User interrupted. Exit.") os._exit(0) except ValueError as e: logging.warning("RUNTIMELOG Check error occured: " + str(e)) time.sleep(1) return self.check_msg(error_times + 1) except BaseException as e: logging.warning( "RUNTIMELOG Unknown check error occured, retrying. Error: " + str(e)) time.sleep(1) return self.check_msg(error_times + 1) def uin_to_account(self, tuin): """ 将uin转换成用户QQ号 :param tuin: :return:str 用户昵称 """ uin_str = str(tuin) try: logging.info("RUNTIMELOG Requesting the account by uin: " + str(tuin)) info = json.loads( self.req.Get( 'http://s.web2.qq.com/api/get_friend_uin2?tuin={0}&type=1&vfwebqq={1}&t={2}' .format(uin_str, self.vfwebqq, self.req.getTimeStamp()), self.default_config.conf.get("global", "connect_referer"))) logging.debug("RESPONSE uin_to_account html: " + str(info)) if info['retcode'] != 0: raise TypeError('uin_to_account retcode error') info = info['result']['account'] return info except: logging.warning("RUNTIMELOG uin_to_account fail") return None # 获取自己的信息 def get_self_info2(self): """ 获取自己的信息 get_self_info2 {"retcode":0,"result":{"birthday":{"month":1,"year":1989,"day":30},"face":555,"phone":"","occupation":"","allow":1,"college":"","uin":2609717081,"blood":0,"constel":1,"lnick":"","vfwebqq":"68b5ff5e862ac589de4fc69ee58f3a5a9709180367cba3122a7d5194cfd43781ada3ac814868b474","homepage":"","vip_info":0,"city":"青岛","country":"中国","personal":"","shengxiao":5,"nick":"要有光","email":"","province":"山东","account":2609717081,"gender":"male","mobile":""}} :return:dict """ if not self.__self_info: url = "http://s.web2.qq.com/api/get_self_info2" response = self.req.Get(url) rsp_json = json.loads(response) if rsp_json["retcode"] != 0: return {} self.__self_info = rsp_json["result"] return self.__self_info # 获取好友详情信息 def get_friend_info2(self, tuin): """ 获取好友详情信息 get_friend_info2 {"retcode":0,"result":{"face":0,"birthday":{"month":1,"year":1989,"day":30},"occupation":"","phone":"","allow":1,"college":"","uin":3964575484,"constel":1,"blood":3,"homepage":"http://blog.lovewinne.com","stat":20,"vip_info":0,"country":"中国","city":"","personal":"","nick":" 信","shengxiao":5,"email":"*****@*****.**","province":"山东","gender":"male","mobile":"158********"}} :return:dict """ uin_str = str(tuin) try: logging.info("RUNTIMELOG Requesting the account info by uin: " + str(tuin)) info = json.loads( self.req.Get( 'http://s.web2.qq.com/api/get_friend_info2?tuin={0}&vfwebqq={1}&clientid={2}&psessionid={3}&t={4}' .format(uin_str, self.vfwebqq, self.client_id, self.psessionid, self.req.getTimeStamp()), )) logging.debug("RESPONSE get_friend_info2 html: " + str(info)) if info['retcode'] != 0: raise TypeError('get_friend_info2 result error') info = info['result'] return info except: logging.warning("RUNTIMELOG get_friend_info2 fail") return None # 获取好友详情信息 def get_friend_info(self, tuin): uin_str = str(tuin) if uin_str not in self.friend_list: info = self.get_friend_info2(tuin) or {'nick': '群用户'} info['account'] = self.uin_to_account(tuin) self.friend_list[uin_str] = info try: return '【{0}({1})】'.format(self.friend_list[uin_str]['nick'], self.friend_list[uin_str]['account']) except: logging.warning("RUNTIMELOG get_friend_info return fail.") logging.debug("RUNTIMELOG now uin list: " + str(self.friend_list[uin_str])) # 获取好友的签名信息 def get_single_long_nick2(self, tuin): """ 获取好友的签名信息 get_single_long_nick2 {"retcode":0,"result":[{"uin":3964575484,"lnick":"幸福,知道自己在哪里,知道下一个目标在哪里,心不累~"}]} :return:dict """ url = "http://s.web2.qq.com/api/get_single_long_nick2?tuin=%s&vfwebqq=%s&t=%s" % ( tuin, self.vfwebqq, int(time.time() * 100)) response = self.req.Get(url) rsp_json = json.loads(response) if rsp_json["retcode"] != 0: return {} return rsp_json["result"] # 获取群信息(对于易变的信息,请在外层做缓存处理) def get_group_info_ext2(self, gcode): """ 获取群信息 get_group_info_ext2 {"retcode":0,"result":{"stats":[],"minfo":[{"nick":" 信","province":"山东","gender":"male","uin":3964575484,"country":"中国","city":""},{"nick":"崔震","province":"","gender":"unknown","uin":2081397472,"country":"","city":""},{"nick":"云端的猫","province":"山东","gender":"male","uin":3123065696,"country":"中国","city":"青岛"},{"nick":"要有光","province":"山东","gender":"male","uin":2609717081,"country":"中国","city":"青岛"},{"nick":"小莎机器人","province":"广东","gender":"female","uin":495456232,"country":"中国","city":"深圳"}],"ginfo":{"face":0,"memo":"http://hujj009.ys168.com\r\n0086+区(没0)+电话\r\n0086+手机\r\nhttp://john123951.xinwen365.net/qq/index.htm","class":395,"fingermemo":"","code":3943922314,"createtime":1079268574,"flag":16778241,"level":0,"name":"ぁQQぁ","gid":3931577475,"owner":3964575484,"members":[{"muin":3964575484,"mflag":192},{"muin":2081397472,"mflag":65},{"muin":3123065696,"mflag":128},{"muin":2609717081,"mflag":0},{"muin":495456232,"mflag":0}],"option":2},"cards":[{"muin":3964575484,"card":"●s.Εx2(22222)□"},{"muin":495456232,"card":"小莎机器人"}],"vipinfo":[{"vip_level":0,"u":3964575484,"is_vip":0},{"vip_level":0,"u":2081397472,"is_vip":0},{"vip_level":0,"u":3123065696,"is_vip":0},{"vip_level":0,"u":2609717081,"is_vip":0},{"vip_level":0,"u":495456232,"is_vip":0}]}} :return:dict """ if gcode == 0: return {} try: url = "http://s.web2.qq.com/api/get_group_info_ext2?gcode=%s&vfwebqq=%s&t=%s" % ( gcode, self.vfwebqq, int(time.time() * 100)) response = self.req.Get(url) rsp_json = json.loads(response) if rsp_json["retcode"] != 0: return {} return rsp_json["result"] except Exception as ex: logging.warning("RUNTIMELOG get_group_info_ext2. Error: " + str(ex)) return {} # 发送群消息 def send_qun_msg(self, guin, reply_content, msg_id, fail_times=0): fix_content = str( reply_content.replace("\\", "\\\\\\\\").replace("\n", "\\\\n").replace( "\t", "\\\\t")) rsp = "" try: req_url = "http://d1.web2.qq.com/channel/send_qun_msg2" data = (( 'r', '{{"group_uin":{0}, "face":564,"content":"[\\"{4}\\",[\\"font\\",{{\\"name\\":\\"Arial\\",\\"size\\":\\"10\\",\\"style\\":[0,0,0],\\"color\\":\\"000000\\"}}]]","clientid":{1},"msg_id":{2},"psessionid":"{3}"}}' .format(guin, self.client_id, msg_id, self.psessionid, fix_content)), ('clientid', self.client_id), ('psessionid', self.psessionid)) rsp = self.req.Post( req_url, data, self.default_config.conf.get("global", "connect_referer")) rsp_json = json.loads(rsp) if 'retcode' in rsp_json and rsp_json['retcode'] != 0: raise ValueError("RUNTIMELOG reply group chat error" + str(rsp_json['retcode'])) logging.info("RUNTIMELOG send_qun_msg: Reply successfully.") logging.debug("RESPONSE send_qun_msg: Reply response: " + str(rsp)) return rsp_json except: logging.warning("RUNTIMELOG send_qun_msg fail") if fail_times < 5: logging.warning( "RUNTIMELOG send_qun_msg: Response Error.Wait for 2s and Retrying." + str(fail_times)) logging.debug("RESPONSE send_qun_msg rsp:" + str(rsp)) time.sleep(2) self.send_qun_msg(guin, reply_content, msg_id, fail_times + 1) else: logging.warning( "RUNTIMELOG send_qun_msg: Response Error over 5 times.Exit.reply content:" + str(reply_content)) return False # 发送私密消息 def send_buddy_msg(self, tuin, reply_content, msg_id, fail_times=0): fix_content = str( reply_content.replace("\\", "\\\\\\\\").replace("\n", "\\\\n").replace( "\t", "\\\\t")) rsp = "" try: req_url = "http://d1.web2.qq.com/channel/send_buddy_msg2" data = (( 'r', '{{"to":{0}, "face":594, "content":"[\\"{4}\\", [\\"font\\", {{\\"name\\":\\"Arial\\", \\"size\\":\\"10\\", \\"style\\":[0, 0, 0], \\"color\\":\\"000000\\"}}]]", "clientid":{1}, "msg_id":{2}, "psessionid":"{3}"}}' .format(tuin, self.client_id, msg_id, self.psessionid, fix_content)), ('clientid', self.client_id), ('psessionid', self.psessionid)) rsp = self.req.Post( req_url, data, self.default_config.conf.get("global", "connect_referer")) rsp_json = json.loads(rsp) if 'errCode' in rsp_json and rsp_json['errCode'] != 0: raise ValueError("reply pmchat error" + str(rsp_json['retcode'])) logging.info("RUNTIMELOG Reply successfully.") logging.debug("RESPONSE Reply response: " + str(rsp)) return rsp_json except: if fail_times < 5: logging.warning( "RUNTIMELOG Response Error.Wait for 2s and Retrying." + str(fail_times)) logging.debug("RESPONSE " + str(rsp)) time.sleep(2) self.send_buddy_msg(tuin, reply_content, msg_id, fail_times + 1) else: logging.warning( "RUNTIMELOG Response Error over 5 times.Exit.reply content:" + str(reply_content)) return False # 发送临时消息 def send_sess_msg2(self, tuin, reply_content, msg_id, group_sig, service_type=0, fail_times=0): fix_content = str( reply_content.replace("\\", "\\\\\\\\").replace("\n", "\\\\n").replace( "\t", "\\\\t")) rsp = "" try: req_url = "http://d1.web2.qq.com/channel/send_sess_msg2" data = (( 'r', '{{"to":{0}, "face":594, "content":"[\\"{4}\\", [\\"font\\", {{\\"name\\":\\"Arial\\", \\"size\\":\\"10\\", \\"style\\":[0, 0, 0], \\"color\\":\\"000000\\"}}]]", "clientid":{1}, "msg_id":{2}, "psessionid":"{3}", "group_sig":"{5}", "service_type":{6}}}' .format(tuin, self.client_id, msg_id, self.psessionid, fix_content, group_sig, service_type)), ('clientid', self.client_id), ('psessionid', self.psessionid), ('group_sig', group_sig), ('service_type', service_type)) rsp = self.req.Post( req_url, data, self.default_config.conf.get("global", "connect_referer")) rsp_json = json.loads(rsp) if 'retcode' in rsp_json and rsp_json['retcode'] != 0: raise ValueError("reply sess chat error" + str(rsp_json['retcode'])) logging.info("RUNTIMELOG Reply successfully.") logging.debug("RESPONSE Reply response: " + str(rsp)) return rsp_json except: if fail_times < 5: logging.warning( "RUNTIMELOG Response Error.Wait for 2s and Retrying." + str(fail_times)) logging.debug("RESPONSE " + str(rsp)) time.sleep(2) self.send_sess_msg2(tuin, reply_content, msg_id, group_sig, service_type, fail_times + 1) else: logging.warning( "RUNTIMELOG Response Error over 5 times.Exit.reply content:" + str(reply_content)) return False # 主动发送临时消息 def send_sess_msg2_fromGroup(self, guin, tuin, reply_content, msg_id, service_type=0, fail_times=0): group_sig = self.__getGroupSig(guin, tuin, service_type) fix_content = str( reply_content.replace("\\", "\\\\\\\\").replace("\n", "\\\\n").replace( "\t", "\\\\t")) rsp = "" try: req_url = "http://d1.web2.qq.com/channel/send_sess_msg2" data = (( 'r', '{{"to":{0}, "face":594, "content":"[\\"{4}\\", [\\"font\\", {{\\"name\\":\\"Arial\\", \\"size\\":\\"10\\", \\"style\\":[0, 0, 0], \\"color\\":\\"000000\\"}}]]", "clientid":{1}, "msg_id":{2}, "psessionid":"{3}", "group_sig":"{5}", "service_type":{6}}}' .format(tuin, self.client_id, msg_id, self.psessionid, fix_content, group_sig, service_type)), ('clientid', self.client_id), ('psessionid', self.psessionid), ('group_sig', group_sig), ('service_type', service_type)) rsp = self.req.Post( req_url, data, self.default_config.conf.get("global", "connect_referer")) rsp_json = json.loads(rsp) if 'retcode' in rsp_json and rsp_json['retcode'] != 0: raise ValueError("RUNTIMELOG reply sess chat error" + str(rsp_json['retcode'])) logging.info( "RUNTIMELOG send_sess_msg2_fromGroup: Reply successfully.") logging.debug( "RESPONSE send_sess_msg2_fromGroup: Reply response: " + str(rsp)) return rsp_json except: if fail_times < 5: logging.warning( "RUNTIMELOG send_sess_msg2_fromGroup: Response Error.Wait for 2s and Retrying." + str(fail_times)) logging.debug("RESPONSE " + str(rsp)) time.sleep(2) self.send_sess_msg2_fromGroup(guin, tuin, reply_content, msg_id, service_type, fail_times + 1) else: logging.warning( "RUNTIMELOG send_sess_msg2_fromGroup: Response Error over 5 times.Exit.reply content:" + str(reply_content)) return False
class QQ: def __init__(self): self.default_config = DefaultConfigs() self.req = HttpClient() # cache self.friend_list = {} self.__groupSig_list = {} self.__self_info = {} self.client_id = 53999199 self.ptwebqq = '' self.psessionid = '' self.appid = 0 self.vfwebqq = '' self.qrcode_path = self.default_config.conf.get( "global", "qrcode_path") # QRCode保存路径 self.username = '' self.account = 0 init_logging() def __hash_digest(self, uin, ptwebqq): """ 计算hash,貌似TX的这个算法会经常变化,暂时不使用 get_user_friends2, get_group_name_list_mask2 会依赖此数据 提取自http://pub.idqqimg.com/smartqq/js/mq.js :param uin: :param ptwebqq: :return: """ N = [0, 0, 0, 0] print(N[0]) for t in range(len(ptwebqq)): N[t % 4] ^= ord(ptwebqq[t]) U = ["EC", "OK"] V = [0, 0, 0, 0] V[0] = int(uin) >> 24 & 255 ^ ord(U[0][0]) V[1] = int(uin) >> 16 & 255 ^ ord(U[0][1]) V[2] = int(uin) >> 8 & 255 ^ ord(U[1][0]) V[3] = int(uin) & 255 ^ ord(U[1][1]) U = [0, 0, 0, 0, 0, 0, 0, 0] for T in range(8): if T % 2 == 0: U[T] = N[T >> 1] else: U[T] = V[T >> 1] N = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" ] V = "" for T in range(len(U)): V += N[U[T] >> 4 & 15] V += N[U[T] & 15] return V def __getGroupSig(self, guin, tuin, service_type=0): key = '%s --> %s' % (guin, tuin) if key not in self.__groupSig_list: url = "http://d1.web2.qq.com/channel/get_c2cmsg_sig2?id=%s&to_uin=%s&service_type=%s&clientid=%s&psessionid=%s&t=%d" % ( guin, tuin, service_type, self.client_id, self.psessionid, int(time.time() * 100)) response = self.req.Get(url) rsp_json = json.loads(response) if rsp_json["retcode"] != 0: return "" sig = rsp_json["result"]["value"] self.__groupSig_list[key] = sig if key in self.__groupSig_list: return self.__groupSig_list[key] return "" def login_by_qrcode(self): logging.info("Requesting the login pages...") initurl_html = self.req.Get( self.default_config.conf.get("global", "smartqq_url")) logging.debug("login page html: " + str(initurl_html)) initurl = get_revalue(initurl_html, r'\.src = "(.+?)"', "Get Login Url Error.", 1) html = self.req.Get(initurl + '0') appid = get_revalue( html, r'<input type="hidden" name="aid" value="(\d+)" />', 'Get AppId Error', 1) sign = get_revalue(html, r'g_login_sig=encodeURIComponent\("(.*?)"\)', 'Get Login Sign Error', 0) js_ver = get_revalue(html, r'g_pt_version=encodeURIComponent\("(\d+)"\)', 'Get g_pt_version Error', 1) mibao_css = get_revalue(html, r'g_mibao_css=encodeURIComponent\("(.+?)"\)', 'Get g_mibao_css Error', 1) star_time = date_to_millis(datetime.datetime.utcnow()) error_times = 0 ret = [] while True: error_times += 1 print 'download QR code image...' self.req.Download( 'https://ssl.ptlogin2.qq.com/ptqrshow?appid={0}&e=0&l=L&s=8&d=72&v=4' .format(appid), self.qrcode_path) logging.info("Please scan the downloaded QRCode") thread.start_new_thread(display_QRCode, (self.qrcode_path, )) while True: html = self.req.Get( 'https://ssl.ptlogin2.qq.com/ptqrlogin?webqq_type=10&remember_uin=1&login2qq=1&aid={0}&u1=http%3A%2F%2Fw.qq.com%2Fproxy.html%3Flogin2qq%3D1%26webqq_type%3D10&ptredirect=0&ptlang=2052&daid=164&from_ui=1&pttype=1&dumy=&fp=loginerroralert&action=0-0-{1}&mibao_css={2}&t=undefined&g=1&js_type=0&js_ver={3}&login_sig={4}' .format( appid, date_to_millis(datetime.datetime.utcnow()) - star_time, mibao_css, js_ver, sign), initurl) logging.debug("QRCode check html: " + str(html)) ret = html.split("'") if ret[1] in ( '0', '65'): # 65: QRCode 失效, 0: 验证成功, 66: 未失效, 67: 验证中 break if ret[1] == '0' or error_times > 10: break if ret[1] != '0': return logging.info("QRCode scaned, now logging in.") # 删除QRCode文件 if os.path.exists(self.qrcode_path): os.remove(self.qrcode_path) # 记录登陆账号的昵称 self.username = ret[11] html = self.req.Get(ret[5]) logging.debug("mibao_res html: " + str(html)) # url = get_revalue(html, r' src="(.+?)"', 'Get mibao_res Url Error.', 0) # if url != '': # html = self.req.Get(url.replace('&', '&')) # url = get_revalue(html, r'location\.href="(.+?)"', 'Get Redirect Url Error', 1) # self.req.Get(url) self.ptwebqq = self.req.getCookie('ptwebqq') # 测试用请求 self.req.Get("http://w.qq.com/proxy.html?login2qq=1&webqq_type=10") self.req.Get("http://web2.qq.com/web2_cookie_proxy.html") self.req.Get( "http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1") self.req.Get( "http://s.web2.qq.com/api/getvfwebqq?ptwebqq={0}&clientid={1}&psessionid={2}&t={3}" .format(self.ptwebqq, self.client_id, self.psessionid, date_to_millis(datetime.datetime.utcnow()) - star_time)) self.req.Get( "http://d1.web2.qq.com/proxy.html?v=20151105001&callback=1&id=2") login_error = 1 ret = {} while login_error > 0: try: html = self.req.Post( 'http://d1.web2.qq.com/channel/login2', { 'r': '{{"ptwebqq":"{0}","clientid":{1},"psessionid":"{2}","status":"online"}}' .format(self.ptwebqq, self.client_id, self.psessionid) }, self.default_config.conf.get("global", "connect_referer")) logging.debug("login html: " + str(html)) ret = json.loads(html) login_error = 0 except: login_error += 1 logging.info("login fail, retrying...") if ret['retcode'] != 0: logging.debug(str(ret)) logging.warning("return code:" + str(ret['retcode'])) return self.vfwebqq = ret['result']['vfwebqq'] self.psessionid = ret['result']['psessionid'] self.account = ret['result']['uin'] logging.info("QQ:{0} login successfully, Username:{1}".format( self.account, self.username)) def relogin(self, error_times=0): if error_times >= 10: return False try: html = self.req.Post( 'http://d1.web2.qq.com/channel/login2', { 'r': '{{"ptwebqq":"{0}","clientid":{1},"psessionid":"{2}","key":"","status":"online"}}' .format(self.ptwebqq, self.client_id, self.psessionid) }, self.default_config.conf.get("global", "connect_referer")) logging.debug("relogin html: " + str(html)) ret = json.loads(html) self.vfwebqq = ret['result']['vfwebqq'] self.psessionid = ret['result']['psessionid'] return True except: logging.info("login fail, retryng..." + str(error_times)) return self.relogin(error_times + 1) def check_msg(self, error_times=0): if error_times >= 5: if not self.relogin(): raise IOError("Account offline.") else: error_times = 0 # 调用后进入单次轮询,等待服务器发回状态。 html = self.req.Post( 'http://d1.web2.qq.com/channel/poll2', { 'r': '{{"ptwebqq":"{1}","clientid":{2},"psessionid":"{0}","key":""}}' .format(self.psessionid, self.ptwebqq, self.client_id) }, self.default_config.conf.get("global", "connect_referer")) logging.debug("check_msg html: " + str(html)) try: if html == "": return self.check_msg() ret = json.loads(html) ret_code = ret['retcode'] # if ret_code in (0,): # logging.info("received retcode: " + str(ret_code) + ": No message.") # time.sleep(1) # return if ret_code in (103, ): logging.warning("received retcode: " + str(ret_code) + ": Check error.retrying.." + str(error_times)) time.sleep(1) return self.check_msg(error_times + 1) if ret_code in (121, ): logging.warning("received retcode: " + str(ret_code)) return self.check_msg(5) elif ret_code == 0: if len(ret['result']) == 0: logging.info("received retcode: " + str(ret_code) + ": No message.") time.sleep(1) return msg_list = [] pm_list = [] sess_list = [] group_list = [] notify_list = [] for msg in ret['result']: ret_type = msg['poll_type'] if ret_type == 'message': pm_list.append(PmMsg(msg)) elif ret_type == 'group_message': group_list.append(GroupMsg(msg)) elif ret_type == 'sess_message': sess_list.append(SessMsg(msg)) elif ret_type == 'input_notify': notify_list.append(InputNotify(msg)) elif ret_code == 'kick_message': notify_list.append(KickMessage(msg)) else: logging.warning("unknown message type: " + str(ret_type) + "details: " + str(msg)) group_list.sort(key=lambda x: x.msg_id) msg_list += pm_list + sess_list + group_list + notify_list if not msg_list: return return msg_list elif ret_code == 100006: logging.warning("POST data error") return elif ret_code == 116: self.ptwebqq = ret['p'] logging.info("ptwebqq updated.") return else: logging.warning("unknown retcode " + str(ret_code)) return except ValueError, e: logging.warning("Check error occured: " + str(e)) time.sleep(1) return self.check_msg(error_times + 1) except BaseException, e: logging.warning("Unknown check error occured, retrying. Error: " + str(e)) time.sleep(1) return self.check_msg(error_times + 1)
class Course: def __init__(self): self.hosturl = 'http://sep.ucas.ac.cn' self.loginurl = 'http://sep.ucas.ac.cn/slogin' self.req = HttpClient() config = configparser.ConfigParser() config.read('./config.ini') self.usrname = config.get('USER', 'usrname') self.passwd = config.get('USER', 'passwd') self.pwd = config.get('USER', 'savedir') if not (self.usrname and self.passwd and self.pwd): print('Please setup your account in \'config.ini\'') pause() sys.exit() def login(self): self.req.Get(self.hosturl) postData = {'userName': self.usrname, 'pwd': self.passwd, 'sb': 'sb'} html = self.req.Post(self.loginurl, postData) logging.info('login success') course_index = 'http://sep.ucas.ac.cn/portal/site/16' html = self.req.Get(course_index) Identity = get_revalue(html, r'Identity=(.+?)"', 'get Identity error', 1) logging.debug("Identity=" + Identity) html = self.req.Get( "http://course.ucas.ac.cn/portal/plogin?Identity={0}".format( Identity)) session = get_revalue(html, r'session=(.+?)&', 'get session error', 1) _mid = get_revalue(html, r'_mid=(.+?)"', 'get mid error', 1) guid = get_revalue(html, r'guid=(.+?)"', 'get guid error', 1) logging.debug("session=" + session) logging.debug("_mid=" + _mid) logging.debug("guid=" + guid) html = self.req.Get( "http://course.ucas.ac.cn/portal?sakai.session={0}&_mid={1}". format(session, _mid)) course_urls = re.findall(r'http://course.ucas.ac.cn/portal/site/\d+', html.decode('utf-8')) #return course_urls for test_url in course_urls: self.get_resource(test_url) def get_resource(self, url): import html.parser import os html_parser = html.parser.HTMLParser() html = self.req.Get(url) res_url = get_revalue(html, r'class="icon-sakai-resources" href="(.+?)"', 'get resource error', 1) logging.debug("res_url= " + res_url) html = self.req.Get(res_url) wtf_url = get_revalue( html, r'http://course.ucas.ac.cn/portal/tool-reset/(.+?)/', 'what the fuxk error', 1) logging.debug("wtf_url= " + wtf_url) html = self.req.Get( "http://course.ucas.ac.cn/portal/tool-reset/{0}/?panel=Main". format(wtf_url)) html = unescape(html.decode('utf-8')) #logging.debug(html) title = get_revalue(html, r'<img src =.*?/>([\s\S]+?)</h3>', 'get title error', 1).strip().replace(' ', '_') logging.debug(title) folders = [ x for x in re.findall(r'onclick="javascript[\s\S]+?submit', html) if 'doNavigate' in x ] folders = [re.findall(r'group/\d+/[\s\S]+?/', x) for x in folders][1:] folders = ['/' + x[0] for x in folders] #print folders res = re.findall( r'http://course.ucas.ac.cn/access/content/group/[^"]+', html) res = [x[:-1] if x.endswith(";") else x for x in res] res = list(set(res)) # logging.debug(html) self.download(title, res) for folder in folders: postData = { 'source': 0, 'collectionId': folder, 'criteria': 'title', 'sakai_action': 'doNavigate' } content = self.req.Post( "http://course.ucas.ac.cn/portal/tool/{0}/?panel=Main".format( wtf_url), postData) tmp = re.findall( r'http://course.ucas.ac.cn/access/content/group/[^"]+', content.decode('utf-8')) tmp = list(set(tmp)) name = get_revalue(folder, r'([^/]+?)/$', 'folder error', 1).replace(' ', '_') #print name self.download(os.path.join(title, name), tmp) logging.debug(str(res)) def download(self, title, res): import os print(('Linking' + ' ' + title)) logging.info('linking... ' + title) if not os.path.exists(self.pwd): os.makedirs(self.pwd) _pwd = os.path.join(self.pwd, title) if not os.path.exists(_pwd): os.makedirs(_pwd) for f in res: name = get_revalue(f, r'([^/]+?)$', 'get name error', 1).replace(' ', '_') if name.__contains__('copyrightAlertWindow'): f = re.findall( "http://course.ucas.ac.cn/access/content/group/[^']+", f)[0] name = get_revalue(f, r'([^/]+?)$', 'get name error', 1).replace(' ', '_') content = self.req.Get(f) #logging.debug(content) f = get_revalue(content, r'a href="(.+?)"', 'get wotongyi error', 1) #print title + ' has contents which is protected by COPYRIGHT, failed to download' __pwd = os.path.join(_pwd, name) if (not force_update_flag) and check_existed(__pwd): print(('skip' + ' ' + name + ' already exists, skip')) logging.info(name + ' already exists, skip') continue #print 'downloading ' + name logging.info('downloading ' + name) self.req.Download(f, __pwd)