class QQBot(object): def __init__(self): self.client = HttpClient() # cache self.friend_list = {} self._group_sig_list = {} self._self_info = {} self.client_id = 53999199 self.ptwebqq = '' self.psessionid = '' self.appid = 0 self.vfwebqq = '' self.qrcode_path = './v.jpg' self.username = '' self.account = 0 def _get_qr_login_status( self, qr_validation_url, appid, star_time, mibao_css, js_ver, sign, init_url ): redirect_url = None login_result = self.client.get( qr_validation_url.format( appid, date_to_millis(datetime.datetime.utcnow()) - star_time, mibao_css, js_ver, sign ), init_url ) ret_code = int(find_first_result(login_result, r"\d+?", None)) redirect_info = re.findall(r"(http.*?)\'", login_result) if redirect_info: logger.debug("redirect_info match is: %s" % redirect_info) redirect_url = redirect_info[0] return ret_code, redirect_url def check_msg(self): # Pooling the message response = self.client.post( 'http://d1.web2.qq.com/channel/poll2', { 'r': json.dumps( { "ptwebqq": self.ptwebqq, "clientid": self.client_id, "psessionid": self.psessionid, "key": "" } ) }, SMART_QQ_REFER ) logger.debug("Pooling returns response:\n %s" % response) if response == "": return try: ret = json.loads(response) except ValueError: logger.warning("RUNTIMELOG decode poll response error.") logger.debug("RESPONSE {}".format(response)) return ret_code = ret['retcode'] if ret_code in (103, ): logger.warning( "Pooling received retcode: " + str(ret_code) + ": Check error. 请前往http://w.qq.com/ 手动登陆SmartQQ一次." ) elif ret_code in (121,): logger.warning("Pooling error with retcode %s" % ret_code) elif ret_code == 0: if 'result' not in ret or len(ret['result']) == 0: logger.info("Pooling ends, no new message received.") else: return ret['result'] elif ret_code == 100006: logger.error("Pooling request error, response is: %s" % ret) elif ret_code == 116: self.ptwebqq = ret['p'] logger.debug("ptwebqq updated in this pooling") else: logger.warning("Pooling returns unknown retcode %s" % ret_code) 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.client.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_online_buddies2(self): """ 获取在线好友列表 get_online_buddies2 :return:list """ try: logger.info("RUNTIMELOG Requesting the online buddies.") online_buddies = json.loads(self.client.get( 'http://d1.web2.qq.com/channel/get_online_buddies2?vfwebqq={0}&clientid={1}&psessionid={2}&t={3}' .format( self.vfwebqq, self.client_id, self.psessionid, self.client.get_timestamp()), )) logger.debug("RESPONSE get_online_buddies2 html: " + str(online_buddies)) if online_buddies['retcode'] != 0: raise TypeError('get_online_buddies2 result error') online_buddies = online_buddies['result'] return online_buddies except: logger.warning("RUNTIMELOG get_online_buddies2 fail") return None def _login_by_cookie(self): logger.info("Try cookie login...") self.client.load_cookie() self.ptwebqq = self.client.get_cookie('ptwebqq') response = self.client.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 ) }, SMART_QQ_REFER ) try: ret = json.loads(response) except ValueError: logger.warning("Cookies login fail, response decode error.") return if ret['retcode'] != 0: raise CookieLoginFailed("Login step 1 failed with response:\n %s " % ret) response2 = self.client.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.client.get_timestamp() )) ret2 = json.loads(response2) if ret2['retcode'] != 0: raise CookieLoginFailed( "Login step 2 failed with response:\n %s " % ret ) self.psessionid = ret['result']['psessionid'] self.account = ret['result']['uin'] self.vfwebqq = ret2['result']['vfwebqq'] logger.info("Login by cookie succeed. account: %s" % self.account) return True def _login_by_qrcode(self, no_gui): logger.info("RUNTIMELOG Trying to login by qrcode.") logger.info("RUNTIMELOG Requesting the qrcode login pages...") qr_validation_url = '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}' init_url = "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.client.get( init_url, ) appid = find_first_result( html, r'<input type="hidden" name="aid" value="(\d+)" />', 'Get AppId Error', True ) sign = find_first_result( html, r'g_login_sig=encodeURIComponent\("(.*?)"\)', 'Get Login Sign Error', ) js_ver = find_first_result( html, r'g_pt_version=encodeURIComponent\("(\d+)"\)', 'Get g_pt_version Error', True, ) mibao_css = find_first_result( html, r'g_mibao_css=encodeURIComponent\("(.+?)"\)', 'Get g_mibao_css Error', True ) star_time = date_to_millis(datetime.datetime.utcnow()) error_times = 0 ret_code = None login_result = None redirect_url = None while True: error_times += 1 logger.info("Downloading QRCode file...") self.client.download( 'https://ssl.ptlogin2.qq.com/ptqrshow?appid={0}&e=0&l=L&s=8&d=72&v=4'.format(appid), self.qrcode_path ) if not no_gui: thread = Thread(target=show_qr, args=(self.qrcode_path, )) thread.setDaemon(True) thread.start() while True: ret_code, redirect_url = self._get_qr_login_status( qr_validation_url, appid, star_time, mibao_css, js_ver, sign, init_url ) if ret_code in ( QR_CODE_STATUS['succeed'], QR_CODE_STATUS["qr_code_expired"] ): break time.sleep(1) if ret_code == QR_CODE_STATUS['succeed'] or error_times > 10: break if os.path.exists(self.qrcode_path): os.remove(self.qrcode_path) login_failed_tips = "QRCode validation response is:\n%s" % login_result if ret_code is not None and (ret_code != 0): raise QRLoginFailed(login_failed_tips) elif redirect_url is None: raise QRLoginFailed(login_failed_tips) else: html = self.client.get(redirect_url) logger.debug("QR Login redirect_url response: %s" % html) return True def login(self, no_gui=False): try: self._login_by_cookie() except CookieLoginFailed: logger.info("Cookie login failed.") while True: if self._login_by_qrcode(no_gui): if self._login_by_cookie(): break time.sleep(4) user_info = self.get_self_info2() self.get_online_buddies2() try: self.username = user_info['nick'] logger.info( "User information got: user name is [%s]" % self.username ) except KeyError: logger.exception( "User info access failed, check your login and response:\n%s" % user_info ) exit(1) logger.info("RUNTIMELOG QQ:{0} login successfully, Username:{1}".format(self.account, self.username)) self._self_info = user_info def getTulin(self, info): logger.info("Try Tulin...") self.client.load_cookie() response = self.client.post( 'http://www.tuling123.com/openapi/api', { 'key': '46dec4507ea59630889dce242767ca9b', 'info':info } ) try: ret = json.loads(response) except ValueError: logger.warning("Tulin connect fail, response decode error.") return logger.info("Tulin connect succeed. account: %s" % ret) code=ret['code'] result='' if code==100000: result= ret['text'] if len(result)>250: result=result[0:100]+'...'+result[-150:len(result)] elif code==200000: result= ret['text']+'\n'+ret['url'] elif code==302000: for item in ret['list']: result_temp=result+item['article']+'\n'+item['detailurl']+'\n' if len(result)>250: break result=result+item['article']+'\n'+item['detailurl']+'\n' elif code==305000: for item in ret['list']: result=result+item['trainnum']+':'+item['start']+'-'+item['terminal']+' '+item['starttime']+'-'+item['endtime'] elif code==308000: for item in ret['list']: result=result+item['name']+':'+item['info']+'\n'+item['detailurl'] break; logger.info("Tulin message: %s" % result) return result # 发送群消息 def send_qun_msg(self, reply_content, guin, msg_id, fail_times=0): fix_content = str(reply_content.replace("\\", "\\\\\\\\").replace("\n", "\\\\n").replace("\t", "\\\\t")) rsp = "" try: logger.info("Starting send group message: %s" % reply_content) 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.client.post(req_url, data, SMART_QQ_REFER) rsp_json = json.loads(rsp) logger.debug("RESPONSE send_qun_msg: Reply response: " + str(rsp)) if 'retcode' in rsp_json: raise ValueError("RUNTIMELOG reply group chat error" + str(rsp_json['retcode'])) logger.info("RUNTIMELOG send_qun_msg: Reply '{}' successfully.".format(reply_content)) return rsp_json except: logger.warning("RUNTIMELOG send_qun_msg fail") if fail_times < 5: logger.warning("RUNTIMELOG send_qun_msg: Response Error.Wait for 2s and Retrying." + str(fail_times)) logger.debug("RESPONSE send_qun_msg rsp:" + str(rsp)) time.sleep(2) self.send_qun_msg(reply_content,guin, msg_id, fail_times + 1) else: logger.warning("RUNTIMELOG send_qun_msg: Response Error over 5 times.Exit.reply content:" + str(reply_content)) return False # 发送私密消息 def send_buddy_msg(self, reply_content, tuin, 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.client.post(req_url, data, SMART_QQ_REFER) rsp_json = json.loads(rsp) if 'errCode' in rsp_json and rsp_json['errCode'] != 0: raise ValueError("reply pmchat error" + str(rsp_json['retcode'])) logger.info("RUNTIMELOG Reply successfully.") logger.debug("RESPONSE Reply response: " + str(rsp)) return rsp_json except: if fail_times < 5: logger.warning("RUNTIMELOG Response Error.Wait for 2s and Retrying." + str(fail_times)) logger.debug("RESPONSE " + str(rsp)) time.sleep(2) self.send_buddy_msg(tuin, reply_content, msg_id, fail_times + 1) else: logger.warning("RUNTIMELOG Response Error over 5 times.Exit.reply content:" + str(reply_content)) return False def reply_msg(self, msg, reply_content=None, return_function=False): """ :type msg: QMessage类, 例如 GroupMsg, PrivateMsg, SessMsg :type reply_content: string, 回复的内容. :return: 服务器的响应内容. 如果 return_function 为 True, 则返回的是一个仅有 reply_content 参数的便捷回复函数. """ import functools assert isinstance(msg, QMessage) if isinstance(msg, GroupMsg): if return_function: return functools.partial(self.send_qun_msg, guin=msg.group_code, msg_id=msg.msg_id+1) return self.send_qun_msg(guin=msg.group_code, reply_content=reply_content, msg_id=msg.msg_id+1) if isinstance(msg, PrivateMsg): if return_function: return functools.partial(self.send_buddy_msg, tuin=msg.from_uin, msg_id=msg.msg_id+1) return self.send_buddy_msg(tuin=msg.from_uin, reply_content=reply_content, msg_id=msg.msg_id+1) if isinstance(msg, SessMsg): pass