Repository: lyyyuna/wechat_robot Branch: master Commit: ba0d0901fda3 Files: 12 Total size: 39.9 KB Directory structure: gitextract_yz03lpkv/ ├── .gitignore ├── README.md ├── wechat-asyncio/ │ ├── HttpClient.py │ ├── Monitor.py │ ├── MsgHandler.py │ ├── RobotEngine.py │ ├── RobotPredefinedAnswer.py │ ├── Wechat.py │ ├── config.py │ ├── logger.conf │ └── main.py └── wechat-draft/ └── wechat-robot.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.pyc *.json *.jpg auth.py __pycache__ *.log .idea ================================================ FILE: README.md ================================================ # 微信聊天机器人 - 个人账号版 这是一个基于微信网页版接口的,个人账号聊天机器人。 不同于微信公众号和订阅号,个人账号腾讯并没有提供 API 接口,所以只能模拟微信网页版的协议来做。由于使用未公开通信协议,故不能保证向后兼容性。 * wechat-asyncio 是异步实现的。需要 Python 3.5。 * wechat-draft 是很早的第一个可用的版本。只有两个线程。只需要 > Python 3。 ## 简单说明 ### 依赖 申请一枚图灵机器人 API,新建 auth.py,并写入 apikey = 'xxxxxxxxxxx' * pip3 install aiohttp ### 快速开始 目前使用起来还不友好,且登陆有一定失败率。 (terminal 1) python3 main.py 运行后会在当前文件夹内下载二维码,根据终端提示做即可。 1. 若 10s 内二维码下载失败则重来。 2. 若 wx.log 提示 sync 失败则重来。 ### 参数调整 在 config.py 中可以对各个时间间隔做调整。 ## Changelog ### 2016.2.4 改成了基于 asyncio/aiohttp 的异步框架。现在只有 5 个任务:sync, sendmsg, updategroupinfo, msgloop, monitor。 ### 2016.1.23 wechat-robot.py 改用 Python3。原来的 urllib2/urllib 全部改为了 requests。 ## 参考 前期参考了 [查看被删的微信好友](https://github.com/0x5e/wechat-deleted-friends)。 也参考了 [Nodejs 版的微信聊天机器人](https://github.com/HalfdogStudio/wechat-user-bot)。 ================================================ FILE: wechat-asyncio/HttpClient.py ================================================ # coding=utf-8 import logging import asyncio import aiohttp import json logger = logging.getLogger('wx') class HttpClient: def __init__(self, client): if not isinstance(client, aiohttp.ClientSession): raise TypeError('Please init with a aiohttp.ClientSession instance') self.__client = client self.__cookies = None async def get(self, url, params=None): try: self.__cookies = self.__client.cookies async with await self.__client.get(url, params=params) as r: #assert r.status == 200 return await r.text() except Exception: logger.exception("Network Exception, url: %s, params: %s" % (url, params)) return None async def get_json(self, url, params=None): try: self.__cookies = self.__client.cookies async with await self.__client.get(url, params=params) as r: text = await r.text(encoding='utf-8') return json.loads(text) except Exception: logger.exception("Network Exception, url: %s, params: %s" % (url, params)) return None async def get_json_timeout(self, url, params=None): try: self.__cookies = self.__client.cookies with aiohttp.Timeout(2): async with await self.__client.get(url, params=params) as r: text = await r.text(encoding='utf-8') return json.loads(text) except Exception: logger.exception("Network Exception, url: %s, params: %s" % (url, params)) return None async def post(self, url, data, params=None): try: async with await self.__client.post(url, params=params, data=data) as r: #assert r.status == 200 return await r.text() except Exception: logger.exception("Network Exception, url: %s, params: %s" % (url, params)) return None async def post_json(self, url, data, params=None): try: async with await self.__client.post(url, params=params, data=data) as r: #assert r.status == 200 text = await r.text(encoding='utf-8') return json.loads(text) except Exception: logger.exception("Network Exception, url: %s, params: %s" % (url, params)) return None async def post_json_timeout(self, url, data, params=None): try: with aiohttp.Timeout(2): async with await self.__client.post(url, params=params, data=data) as r: #assert r.status == 200 text = await r.text(encoding='utf-8') return json.loads(text) except Exception: logger.exception("Network Exception, url: %s, params: %s" % (url, params)) return None async def downloadfile(self, url, data, filename): try: async with await self.__client.post(url, data=data) as r: #assert r.status == 200 with open(filename, 'wb') as fd: while True: chunk = await r.content.read(512) if not chunk: break fd.write(chunk) return True except aiohttp.errors.DisconnectedError: return False ================================================ FILE: wechat-asyncio/Monitor.py ================================================ # coding=utf-8 import logging import asyncio from time import ctime import config logger = logging.getLogger('monitor') class Monitor(): def __init__(self, wx): self.wx = wx async def monitor(self): while True: logger.info('Monitoring.......... ' + ctime()) logger.info('retcode %s, selector %s' % (self.wx.retcode, self.wx.selector)) logger.info('recvqueue size: %s' % self.wx.recvqueue.qsize()) logger.info('sendqueue size: %s' % self.wx.sendqueue.qsize()) logger.info('updatequeue size: %s' % self.wx.updatequeue.qsize()) logger.info('Monitor end.......... ') if self.wx.recvqueue.qsize() > 3: while self.wx.recvqueue.qsize() > 1: try: self.wx.recvqueue.get_nowait() except: pass if self.wx.sendqueue.qsize() > 3: while self.wx.sendqueue.qsize() > 1: try: self.wx.sendqueue.get_nowait() except: pass if self.wx.updatequeue.qsize() > 3: while self.wx.updatequeue.qsize() > 1: try: self.wx.updatequeue.get_nowait() except: pass await asyncio.sleep(config.monitor_interval) ================================================ FILE: wechat-asyncio/MsgHandler.py ================================================ # coding=utf-8 import asyncio import re import config import logging logger = logging.getLogger('monitor') class MsgHandler: def __init__(self, wx, robot): self.wx = wx self.robot = robot async def __parsemsg(self): msg = await self.wx.recvqueue.get() # 自己从别的平台发的消息忽略 if msg['FromUserName'] == self.wx.My['UserName']: return None # 排除不是发给自己的消息 if msg['ToUserName'] != self.wx.My['UserName']: return None # 在黑名单里面 if msg['FromUserName'] in self.wx.blacklist: return None msginfo = {} # 文字消息 if msg['MsgType'] == 1: content = msg['Content'] fromsomeone_NickName = '' ## 来自群消息 if msg['FromUserName'].find('@@') != -1: fromsomeone = content[:content.find(':
')] groupname = msg['FromUserName'] if groupname not in self.wx.grouplist: await self.wx.updatequeue.put(groupname) elif fromsomeone in self.wx.grouplist[groupname]: fromsomeone_NickName = self.wx.grouplist[groupname][fromsomeone] fromsomeone_NickName = '@' + fromsomeone_NickName + ' ' else: await self.wx.updatequeue.put(groupname) # 去掉消息头部的来源信息 content = content[content.find('>')+1:] # 普通消息 else: fromsomeone_NickName = '' # print (content) if len(content)>1: regx = re.compile(r'@.+?\u2005') content = regx.sub(' ', content) msginfo['Content'] = content msginfo['fromsomeone'] = fromsomeone_NickName msginfo['FromUserName'] = msg['FromUserName'] return msginfo else: return None async def msgloop(self): while True: msginfo = await self.__parsemsg() if msginfo != None: response = {} answser = await self.robot.answser(msginfo) response['Content'] = msginfo['fromsomeone'] + answser response['user'] = msginfo['FromUserName'] await self.wx.sendqueue.put(response) logger.info(msginfo['fromsomeone'] + ' say: ' + msginfo['Content']) logger.info('Harry Potter say: ' + response['Content']) await asyncio.sleep(config.msgloop_interval) ================================================ FILE: wechat-asyncio/RobotEngine.py ================================================ # coding=utf-8 import asyncio import re from HttpClient import * import RobotPredefinedAnswer import random class RobotEngine(): def __init__(self, client, apikey): self.rbclient = HttpClient(client) self.apikey = apikey self.acc = 0 self.lasttext = '' self.lastuser = '' async def answser(self, msginfo): content = msginfo['Content'] # 去掉英文,因为图灵机器人不支持 content = re.sub(r'[a-zA-Z]', '', content) # 去掉两端空格,不然图灵api那边有问题 content = content.strip() # 做一下字数限制 content = content[:50] # 做完处理发现没有字符了 if content == '': content = self.__randomanswer() tuling_data = dict( key=self.apikey, info=content, userid=msginfo['FromUserName'][2:32], # 把userName的一部分截取,作为userid,这样对话有上下文功能. ) tuling_url = 'http://www.tuling123.com/openapi/api' # 使用post方法访问图灵,以免出现content没有经过urlencoded得不到正确结果. dic = await self.rbclient.post_json_timeout(tuling_url, data=tuling_data) if dic is not None: text = dic['text'] else: text = '网络异常。。。。。。。。。。。。' # 做一下字数回复的限制 if len(text)>100: text = text[:100] text = text + '......' # 对于不能回答的问题直接回复数数 if text.find('不明白你是什么意思,麻烦换一种说法') != -1: text = str(self.acc) self.acc = self.acc + 1 if text.find('不明白你说的什么意思') != -1: text = str(self.acc) self.acc = self.acc + 1 if text == self.lasttext and msginfo['FromUserName'] == self.lastuser: text = self.__randomanswer() self.lasttext = text self.lastuser = msginfo['FromUserName'] return text def __randomanswer(self): diaglen = len(RobotPredefinedAnswer.dialoglist) index = random.randint(0, diaglen-1) return RobotPredefinedAnswer.dialoglist[index] ================================================ FILE: wechat-asyncio/RobotPredefinedAnswer.py ================================================ dialoglist = [ '公司下属出错了,没什么大事,我小心提醒一句,就说下次要注意啊,不然艾姆安格瑞', '我妈给我介绍对象,看了照片,很美,我脱口而出,excited!' , '财务处通知我薪水涨了,我说,哎呀,你们给我搞的这个涨薪,excited!' , '吃到好吃的东西,不说delicious, 说excited', '新同事长的美艳无比,自我介绍时候我忍不住连声赞叹excited' , '看完大圣归来,观后感就是excited' , '遇到任何高兴的事情,都用一棵塞体的表达心情' , '有了点积蓄开始买手表,先后买了三块,不打算再买了,遇到重大场合把三块都戴着以示严肃' , '以前在公交车被人踩了,都是叫哎呀!现在被踩脱口而出 艾姆安格瑞!' , '遇到工科生必问你们学不学engineer drawing,用不用鸭嘴笔' , '身为淮安人说普通话不自觉带扬州口音' , '把格利高里派克演的葛底斯堡演讲视频截下来,对着反复练习,每一次都觉得自己受到了灵魂洗礼,熟练背诵全文没一点问题' , '曾经花了一年的闲余时间听完了莎士比亚的戏剧全集音频,顺着翻完了世图出版的那本全集书,看书的动机就是因为他说中国人要熟悉莎士比亚和贝多芬' , '欢迎客人亲戚时候,都是套路:今天天气预报说有雨,二舅你来了立刻万里无云阳光灿烂' , '跟人提起未来,规划,梦想的时候,必然要提到,人的命运啊,就什么都不知道。个人奋斗当然很重要,但和历史行程也是分不开的' , '被人表扬时候表示谦虚,都是说我很惭愧,就做了这么点微小工作,谢谢大家' , '为了表示志向高洁理想远大,必然要念林则徐的两句诗' , '朋友问我下班去吃日料不,以前回好,现在回吼啊!' , '就会一句粤语,识得唔识得噶' , '形容和某人的深厚友谊一般用谈笑风生这个词' , '对唱歌好有文艺范的妹子充满好感' , '以前很反感说话中夹英,现在能夹就夹,不然体现不出膜法师的风度' , '这些就是比较少见的一些词语运用,类似图样什么的出现的场合太多了,就不一一列举了' , '它,就是化肥,“金坷垃”。' , '你家麦地2米以下藏着丰富的氮磷钾。' , '肥料掺了金坷垃,一袋能顶两袋撒' , '肥料掺了金坷垃,小麦亩产一千八。' , '美国英国法国都在用金坷垃......日本不给用......' ] ================================================ FILE: wechat-asyncio/Wechat.py ================================================ # coding=utf-8 from HttpClient import HttpClient import aiohttp import asyncio import time import re import xml.dom.minidom import json import html import config import logging logger = logging.getLogger('wx') class Wechat(): def __init__(self, client): self.__wxclient = HttpClient(client) self.tip = 0 self.deviceId = 'e000701000000000' self.recvqueue = asyncio.Queue() self.sendqueue = asyncio.Queue() self.blacklist = [] self.updatequeue = asyncio.Queue() # 更新群组信息的请求 self.grouplist = {} # 存储群组的联系人信息 # 给 monitor 用 self.retcode = '0' self.selector = '0' async def __getuuid(self): logger.debug('Entering getuuid.') url = 'https://login.weixin.qq.com/jslogin' payload = { 'appid': 'wx782c26e4c19acffb', 'fun': 'new', 'lang': 'zh_CN', '_': int(time.time()), } text = await self.__wxclient.post(url=url, data=payload) if text == None: return False logger.info(text) regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)"' pm = re.search(regx, text) code = pm.group(1) uuid = pm.group(2) self.uuid = uuid if code == '200': return True else: return False async def __downloadQR(self): logger.debug('Entering downloadQR.') url = 'https://login.weixin.qq.com/qrcode/' + self.uuid payload = { 't': 'webwx', '_': int(time.time()), } su = await self.__wxclient.downloadfile(url, data=payload, filename='qrimage.jpg') logger.info ('请扫描二维码') print ('请扫描二维码') return su async def __waitforlogin(self): logger.debug('Waiting for login.......') url = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s' % (self.tip, self.uuid, int(time.time())) text = await self.__wxclient.get(url) regx = r'window.code=(\d+);' pm = re.search(regx, text) code = pm.group(1) if code == '201': logger.info ('成功扫描,请在手机上点击确认以登录') print ('成功扫描,请在手机上点击确认以登录') self.tip = 0 elif code == '200': logger.info ('正在登录。。。') print ('正在登录。。。') regx = r'window.redirect_uri="(\S+?)";' pm = re.search(regx, text) redirect_uri = pm.group(1) + '&fun=new' self.redirect_uri = redirect_uri base_uri = redirect_uri[:redirect_uri.rfind('/')] self.base_uri = base_uri services = [ ('wx2.qq.com', 'webpush2.weixin.qq.com'), ('qq.com', 'webpush.weixin.qq.com'), ('web1.wechat.com', 'webpush1.wechat.com'), ('web2.wechat.com', 'webpush2.wechat.com'), ('wechat.com', 'webpush.wechat.com'), ('web1.wechatapp.com', 'webpush1.wechatapp.com'), ] push_uri = base_uri for (searchUrl, pushUrl) in services: if base_uri.find(searchUrl) >= 0: push_uri = 'https://%s/cgi-bin/mmwebwx-bin' % pushUrl break self.push_uri = push_uri elif code == '408': pass return code async def __checklogin(self): logger.debug('Entering checklogin.') text = await self.__wxclient.get(self.redirect_uri) doc = xml.dom.minidom.parseString(text) root = doc.documentElement for node in root.childNodes: if node.nodeName == 'skey': skey = node.childNodes[0].data elif node.nodeName == 'wxsid': wxsid = node.childNodes[0].data elif node.nodeName == 'wxuin': wxuin = node.childNodes[0].data elif node.nodeName == 'pass_ticket': pass_ticket = node.childNodes[0].data if not all((skey, wxsid, wxuin, pass_ticket)): return False BaseRequest = { 'Uin': int(wxuin), 'Sid': wxsid, 'Skey': skey, 'DeviceID': self.deviceId, } logger.debug('%s, %s, %s, %s', skey, wxsid, wxuin, pass_ticket) self.skey = skey self.wxsid = wxsid self.wxuin = wxuin self.pass_ticket = pass_ticket self.BaseRequest = BaseRequest return True async def __responseState(self, func, BaseResponse): ErrMsg = BaseResponse['ErrMsg'] Ret = BaseResponse['Ret'] logger.info('func: %s, Ret: %d, ErrMsg: %s' % (func, Ret, ErrMsg)) if Ret != 0: return False return True async def __webwxinit(self): logger.debug('Entering webwxinit.') url = self.base_uri + \ '/webwxinit?pass_ticket=%s&skey=%s&r=%s' % ( self.pass_ticket, self.skey, int(time.time())) payload = { 'BaseRequest' : self.BaseRequest } dic = await self.__wxclient.post_json(url=url, data=json.dumps(payload)) self.My = dic['User'] self.SyncKey = dic['SyncKey'] logger.debug('The new SyncKey is: %s' % self.SyncKey) return await self.__responseState('webwxinit', dic['BaseResponse']) async def __webwxgetcontact(self): url = self.base_uri + \ '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' % ( self.pass_ticket, self.skey, int(time.time())) dic = await self.__wxclient.get_json(url) SpecialUsers = ["newsapp", "fmessage", "filehelper", "weibo", "qqmail", "tmessage", "qmessage", "qqsync", "floatbottle", "lbsapp", "shakeapp", "medianote", "qqfriend", "readerapp", "blogapp", "facebookapp", "masssendapp", "meishiapp", "feedsapp", "voip", "blogappweixin", "weixin", "brandsessionholder", "weixinreminder", "wxid_novlwrv3lqwv11", "gh_22b87fa7cb3c", "officialaccounts", "notification_messages", "wxitil", "userexperience_alarm"] self.blacklist += SpecialUsers MemberList = {} for member in dic['MemberList']: if member['VerifyFlag'] & 8 != 0: # 公众号 continue elif member['UserName'] in SpecialUsers: continue MemberList[member['UserName']] = { 'NickName' : member['NickName'] , 'DisplayName' : member['DisplayName'] } self.memberlist = MemberList logger.info('You have %s friends.' % len(MemberList)) async def __login(self): success = await self.__getuuid() if not success: logger.info ('获取 uuid 失败') print ('获取 uuid 失败') success = await self.__downloadQR() if not success: logger.info ('获取二维码失败') print ('获取二维码失败') while await self.__waitforlogin() != '200': pass success = await self.__checklogin() if not success: logger.info ('登陆失败') print ('登陆失败') logger.info ('登陆成功') print ('登陆成功') success = await self.__webwxinit() if not success: logger.info ('初始化失败') print ('初始化失败') logger.info ('初始化成功') print ('初始化成功') await self.__webwxgetcontact() def __syncKey(self): SyncKey = self.SyncKey SyncKeyItems = ['%s_%s' % (item['Key'], item['Val']) for item in SyncKey['List']] SyncKeyStr = '|'.join(SyncKeyItems) return SyncKeyStr async def __synccheck(self): url = self.push_uri + '/synccheck?' BaseRequest = self.BaseRequest params = { 'skey': BaseRequest['Skey'] , 'sid': BaseRequest['Sid'] , 'uin': BaseRequest['Uin'] , 'deviceId': BaseRequest['DeviceID'] , 'synckey': self.__syncKey() , 'r': int(time.time()*1000) } text = await self.__wxclient.get(url, params = params) if text == None: return ('1111', '1111') regx = r'window.synccheck={retcode:"(\d+)",selector:"(\d+)"}' pm = re.search(regx, text) retcode = pm.group(1) selector = pm.group(2) logger.info('retcode: %s, selector: %s' % (retcode, selector)) return (retcode, selector) async def __webwxsync(self): url = self.base_uri + '/webwxsync?' payload = { 'BaseRequest' : self.BaseRequest , 'SyncKey' : self.SyncKey , 'rr' : ~int(time.time()) } params = { 'skey' : self.BaseRequest['Skey'] , 'pass_ticket' : self.pass_ticket , 'sid' : self.BaseRequest['Sid'] } dic = await self.__wxclient.post_json(url, params=params, data=json.dumps(payload)) if dic == None: return # 更新 synckey self.SyncKey = dic['SyncKey'] await self.__responseState('webwxsync', dic['BaseResponse']) msglist = dic['AddMsgList'] for msg in msglist: await self.recvqueue.put(msg) async def __webwxsendmsg(self, content, user): url = self.base_uri + \ '/webwxsendmsg?pass_ticket=%s' % (self.pass_ticket) msgid = int(time.time()*10000000) msg = { 'ClientMsgId' : msgid , 'Content' : content, 'FromUserName' : self.My['UserName'] , 'LocalID' : msgid , 'ToUserName' : user, 'Type' : 1 } payload = { 'BaseRequest' : self.BaseRequest , 'Msg' : msg } data = json.dumps(payload, ensure_ascii=False) data = data.encode('utf-8') text = await self.__wxclient.post(url, data=data) async def __webwxbatchgetcontact(self, groupname): url = self.base_uri + '/webwxbatchgetcontact?' List = [{ 'ChatRoomId' : '', 'UserName' : groupname }] payload = { 'BaseRequest': self.BaseRequest , 'Count' : 1 , 'List' : List } params = { 'lang' : 'zh_CN' , 'type' : 'ex' , 'pass_ticket' : self.pass_ticket , 'r' : int(time.time()) } dic = await self.__wxclient.post_json(url, params=params, data=json.dumps(payload)) if dic == None: return GroupMapUsers = {} ContactList = dic['ContactList'] for contact in ContactList: memberlist = contact['MemberList'] for member in memberlist: # 默认 @群名片,没有群名片就 @昵称 nickname = member['NickName'] displayname = member['DisplayName'] AT = '' if displayname == '': # 有些人的昵称会有表情 会表示成 <span> # 需要 html.unescape() 转义一下 AT = html.unescape(nickname) else: AT = html.unescape(displayname) GroupMapUsers[member['UserName']] = AT self.grouplist[groupname] = GroupMapUsers async def sync(self): await self.__login() logger.info ('开始心跳噗通噗咚 咚咚咚!!!!') print ('开始心跳噗通噗咚 咚咚咚!!!!') logger.info('Begin to sync with wx server.....') while True: retcode, selector = await self.__synccheck() if retcode != '0': logger.info ('sync 失败') print ('sync 失败') if selector != '0': await self.__webwxsync() await asyncio.sleep(config.sync_interval) self.retcode = retcode self.selector = selector async def sendmsg(self): while True: response = await self.sendqueue.get() # 不要发的太频繁,在拿到 response 之后歇一秒 await asyncio.sleep(config.send_interval) await self.__webwxsendmsg(response['Content'], response['user']) async def updategroupinfo(self): while True: groupname = await self.updatequeue.get() logger.info('更新群信息开始') await self.__webwxbatchgetcontact(groupname) await asyncio.sleep(config.updategroupinfo_interval) logger.info('更新群信息结束') ================================================ FILE: wechat-asyncio/config.py ================================================ send_interval = 0.5 sync_interval = 1 updategroupinfo_interval = 1 monitor_interval = 10 msgloop_interval = 1 ================================================ FILE: wechat-asyncio/logger.conf ================================================ #logger.conf ############################################### [loggers] keys=root,wx,monitor [logger_root] level=DEBUG handlers=hand01 [logger_wx] handlers=hand01 qualname=wx propagate=0 [logger_monitor] handlers=hand02 qualname=monitor propagate=0 ############################################### [handlers] keys=hand01,hand02 [handler_hand01] class=handlers.RotatingFileHandler level=DEBUG formatter=form01 args=('wx.log', 'a', 10*1024*1024, 5) [handler_hand02] class=handlers.RotatingFileHandler level=DEBUG formatter=form01 args=('monitor.log', 'a', 10*1024*1024, 5) ############################################### [formatters] keys=form01 [formatter_form01] format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s datefmt=%a, %d %b %Y %H:%M:%S ================================================ FILE: wechat-asyncio/main.py ================================================ # coding=utf-8 import aiohttp import asyncio from Wechat import Wechat from MsgHandler import MsgHandler from RobotEngine import RobotEngine from Monitor import Monitor import auth import logging import logging.config logging.config.fileConfig("logger.conf") with aiohttp.ClientSession() as client, aiohttp.ClientSession() as rclient: wx = Wechat(client) robot = RobotEngine(rclient, auth.apikey) msg = MsgHandler(wx, robot) god = Monitor(wx) tasks = [ wx.sync() , wx.sendmsg() , wx.updategroupinfo() , msg.msgloop() , god.monitor() ] asyncio.get_event_loop().run_until_complete(asyncio.wait(tasks)) ================================================ FILE: wechat-draft/wechat-robot.py ================================================ # coding=utf-8 import os import requests import simplejson as json import time import re import xml.dom.minidom # from collections import deque import queue import html import threading # lock = threading.Lock() DEBUG = False LOG = True deviceId = 'e000000000000000' g_info = {} g_info['tip'] = 0 g_queue = queue.Queue()# [] import config apikey = config.apikey def getUUID(): global g_info url = 'https://login.weixin.qq.com/jslogin' params = { 'appid': 'wx782c26e4c19acffb', 'fun': 'new', 'lang': 'zh_CN', '_': int(time.time()), } r = requests.post(url, params) if DEBUG: print (r.text) text = r.text regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)"' pm = re.search(regx, text) code = pm.group(1) uuid = pm.group(2) g_info['uuid'] = uuid if code == '200': return True return False def showQRImage(): global g_info uuid = g_info['uuid'] url = 'https://login.weixin.qq.com/qrcode/' + uuid params = { 't': 'webwx', '_': int(time.time()), } r = requests.post(url, params) g_info['tip'] = 1 with open('qrcode.jpg', 'wb') as fd: for chunk in r.iter_content(512): fd.write(chunk) print ('请扫描二维码。。。') def waitForLogin(): global g_info tip = g_info['tip'] uuid = g_info['uuid'] url = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s' % ( tip, uuid, int(time.time())) r = requests.get(url) text = r.text regx = r'window.code=(\d+);' pm = re.search(regx, text) code = pm.group(1) if code == '201': print ('成功扫描,请在手机上点击确认以登录') g_info['tip'] = 0 elif code == '200': print ('正在登录。。。') regx = r'window.redirect_uri="(\S+?)";' pm = re.search(regx, text) redirect_uri = pm.group(1) + '&fun=new' g_info['redirect_uri'] = redirect_uri base_uri = redirect_uri[:redirect_uri.rfind('/')] g_info['base_uri'] = base_uri services = [ ('wx2.qq.com', 'webpush2.weixin.qq.com'), ('qq.com', 'webpush.weixin.qq.com'), ('web1.wechat.com', 'webpush1.wechat.com'), ('web2.wechat.com', 'webpush2.wechat.com'), ('wechat.com', 'webpush.wechat.com'), ('web1.wechatapp.com', 'webpush1.wechatapp.com'), ] push_uri = base_uri for (searchUrl, pushUrl) in services: if base_uri.find(searchUrl) >= 0: push_uri = 'https://%s/cgi-bin/mmwebwx-bin' % pushUrl break g_info['push_uri'] = push_uri elif code == '408': pass return code def login(): global g_info redirect_uri = g_info['redirect_uri'] r = requests.get(redirect_uri) g_info['cookies'] = r.cookies doc = xml.dom.minidom.parseString(r.text) root = doc.documentElement for node in root.childNodes: if node.nodeName == 'skey': skey = node.childNodes[0].data elif node.nodeName == 'wxsid': wxsid = node.childNodes[0].data elif node.nodeName == 'wxuin': wxuin = node.childNodes[0].data elif node.nodeName == 'pass_ticket': pass_ticket = node.childNodes[0].data if not all((skey, wxsid, wxuin, pass_ticket)): return False BaseRequest = { 'Uin': int(wxuin), 'Sid': wxsid, 'Skey': skey, 'DeviceID': deviceId, } g_info['skey'] = skey g_info['wxsid'] = wxsid g_info['wxuin'] = wxuin g_info['pass_ticket'] = pass_ticket g_info['BaseRequest'] = BaseRequest if DEBUG: print (skey, wxsid, wxuin) return True def responseState(func, BaseResponse): ErrMsg = BaseResponse['ErrMsg'] Ret = BaseResponse['Ret'] if DEBUG: print('func: %s, Ret: %d, ErrMsg: %s' % (func, Ret, ErrMsg)) if Ret != 0: return False return True def webwxinit(): global g_info base_uri = g_info['base_uri'] pass_ticket = g_info['pass_ticket'] skey = g_info['skey'] BaseRequest = g_info['BaseRequest'] url = base_uri + \ '/webwxinit?pass_ticket=%s&skey=%s&r=%s' % ( pass_ticket, skey, int(time.time())) params = { 'BaseRequest': BaseRequest } r = requests.post(url, json.dumps(params)) text = r.text if DEBUG: with open('webwxinit.json', 'wb') as fd: for chunk in r.iter_content(512): fd.write(chunk) dic = json.loads(text) ContactList = dic['ContactList'] My = dic['User'] SyncKey = dic['SyncKey'] g_info['ContactList'] = ContactList g_info['My'] = My g_info['SyncKey'] = SyncKey state = responseState('webwxinit', dic['BaseResponse']) return state def webwxgetcontact(): global g_info base_uri = g_info['base_uri'] pass_ticket = g_info['pass_ticket'] skey = g_info['skey'] url = base_uri + \ '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' % ( pass_ticket, skey, int(time.time())) headers = {'Content-Type':'application/json; charset=UTF-8'} r = requests.get(url,headers=headers,cookies=g_info['cookies']) text = r.text if DEBUG: with open('webwxgetcontact.json', 'wb') as fd: for chunk in r.iter_content(512): fd.write(chunk) dic = json.loads(text, 'utf-8') MemberList = dic['MemberList'] SpecialUsers = ["newsapp", "fmessage", "filehelper", "weibo", "qqmail", "tmessage", "qmessage", "qqsync", "floatbottle", "lbsapp", "shakeapp", "medianote", "qqfriend", "readerapp", "blogapp", "facebookapp", "masssendapp", "meishiapp", "feedsapp", "voip", "blogappweixin", "weixin", "brandsessionholder", "weixinreminder", "wxid_novlwrv3lqwv11", "gh_22b87fa7cb3c", "officialaccounts", "notification_messages", "wxitil", "userexperience_alarm"] for i in range(len(MemberList) - 1, -1, -1): Member = MemberList[i] if Member['VerifyFlag'] & 8 != 0: # 公众号/服务号 MemberList.remove(Member) elif Member['UserName'] in SpecialUsers: # 特殊账号 MemberList.remove(Member) _MemberList = {} for user in MemberList: _MemberList[user['UserName']] = user['NickName'] g_info['MemberList'] = _MemberList def syncKey(): global g_info SyncKey = g_info['SyncKey'] SyncKeyItems = ['%s_%s' % (item['Key'], item['Val']) for item in SyncKey['List']] SyncKeyStr = '|'.join(SyncKeyItems) return SyncKeyStr def syncCheck(): global g_info push_uri = g_info['push_uri'] BaseRequest = g_info['BaseRequest'] url = push_uri + '/synccheck?' params = { 'skey': BaseRequest['Skey'], 'sid': BaseRequest['Sid'], 'uin': BaseRequest['Uin'], 'deviceId': BaseRequest['DeviceID'], 'synckey': syncKey(), 'r': int(time.time()), } r = requests.get(url, params=params, cookies=g_info['cookies'], timeout=300) text = r.text regx = r'window.synccheck={retcode:"(\d+)",selector:"(\d+)"}' pm = re.search(regx, text) retcode = pm.group(1) selector = pm.group(2) if DEBUG or LOG: print (text) return (retcode, selector) def webwxsync(): global g_info base_uri = g_info['base_uri'] BaseRequest = g_info['BaseRequest'] SyncKey = g_info['SyncKey'] pass_ticket = g_info['pass_ticket'] # url = base_uri + '/webwxsync?lang=zh_CN&skey=%s&sid=%s&pass_ticket=%s' % ( # BaseRequest['Skey'], BaseRequest['Sid'], quote_plus(pass_ticket)) url = base_uri + '/webwxsync?' params = { 'BaseRequest': BaseRequest, 'SyncKey': SyncKey, 'rr': ~int(time.time()), } url_params = { # 'lang' : 'zh_CN' , 'skey' : BaseRequest['Skey'] , 'pass_ticket' : pass_ticket , 'sid' : BaseRequest['Sid'] } headers = { 'ContentType': 'application/json; charset=UTF-8' } r = requests.post(url, params=url_params, data=json.dumps(params), headers = headers, cookies=g_info['cookies']) # if DEBUG: # global i # i = i + 1 # with open('sync' + str(i) + '.json', 'wb') as fd: # for chunk in r.iter_content(512): # fd.write(chunk) r.encoding = 'utf-8' dic = json.loads(r.text) # print (dic) # 更新 synckey g_info['SyncKey'] = dic['SyncKey'] state = responseState('webwxsync', dic['BaseResponse']) msg_list = dic['AddMsgList'] if DEBUG: print (msg_list) return (state, msg_list) def getMsg(msg_list): global g_info, g_queue My = g_info['My'] for msg in msg_list: if msg['MsgType'] != 1: continue if msg['FromUserName'] == My['UserName']: continue if msg['ToUserName'] == My['UserName']: response = {} content = msg['Content'] # 群消息中 :
之前是 UserName if content.find(':
') != -1: fromsomeone = content[:content.find(':
')] else: fromsomeone = '' fromsomeone_NickName = '' if msg['FromUserName'].find('@@') != -1: # 如果是来自群,那就试着去 g_info[] 对应的群中找群成员列表 groupName = msg['FromUserName'] # 群还没有记录 if groupName not in g_info: g_info['Group_UserName_Req'] = msg['FromUserName'] # 在群列表中有了,因为可能群成员会变化,所以要再次找一遍 elif fromsomeone in g_info[groupName]: fromsomeone_NickName = g_info[groupName][fromsomeone] fromsomeone_NickName = '@' + fromsomeone_NickName + ' ' # 找不到,所以置标志位,会在另一个群中触发寻找行为 else: g_info['Group_UserName_Req'] = msg['FromUserName'] else: fromsomeone_NickName = '' response['fromsomeone'] = fromsomeone_NickName response['Content'] = content[content.find('>')+1:] response['FromUserName'] = msg['FromUserName'] # g_info['Group_UserName_Req'] = response['FromUserName'] # 不停地塞新消息 # lock.acquire() # try: # g_queue.append(response) # finally: # lock.release() # print (response['Content']) # test if g_queue.qsize() > 5: g_queue.get() g_queue.get() g_queue.put(response) if LOG: print ('getmsg queue: %s' % g_queue.qsize()) def webwxsendmsg(content, user): global g_info base_uri = g_info['base_uri'] pass_ticket = g_info['pass_ticket'] BaseRequest = g_info['BaseRequest'] My = g_info['My'] wxuin = BaseRequest['Uin'] wxsid = BaseRequest['Sid'] skey = BaseRequest['Skey'] deviceId = BaseRequest['DeviceID'] url = base_uri + \ '/webwxsendmsg?pass_ticket=%s' % (pass_ticket) msgid = int(time.time()*10000000) msg = { 'ClientMsgId' : msgid , 'Content' : content, 'FromUserName' : My['UserName'] , 'LocalID' : msgid , 'ToUserName' : user, 'Type' : 1 } params = { 'BaseRequest' : BaseRequest, 'Msg' : msg } data = json.dumps(params, ensure_ascii=False) # print (data) data = data.encode('utf-8') r = requests.post(url, data=data, cookies=g_info['cookies']) def sendMsg(): global g_info, g_queue MemberList = g_info['MemberList'] tuling_url = 'http://www.tuling123.com/openapi/api?key=' + apikey + '&info=' time.sleep(1) if LOG: print ('sendmsg queue: %s' % g_queue.qsize()) while g_queue.empty() is False: # lock.acquire() # try: # response = g_queue.popleft() # finally: # lock.release() response = g_queue.get() content = response['Content'] from_user = response['FromUserName'] AT = response['fromsomeone'] tuling_url = tuling_url + content try: data = requests.get(tuling_url) data = json.loads(data.text) text = data['text'] except: text = '网络异常。。。。。' webwxsendmsg(AT + text, from_user) time.sleep(1) if LOG: print print ('机器人收到回复:%s' % content) print ('机器人的回复: %s' % text) print def webwxbatchgetcontact(UserName): global g_info base_uri = g_info['base_uri'] pass_ticket = g_info['pass_ticket'] BaseRequest = g_info['BaseRequest'] url = base_uri + '/webwxbatchgetcontact?' List = [{ 'ChatRoomId' : '', 'UserName' : UserName }] post_params = { 'BaseRequest': BaseRequest , 'Count' : 1 , 'List' : List } url_params = { 'lang' : 'zh_CN' , 'type' : 'ex' , 'pass_ticket' : pass_ticket , 'r' : int(time.time()) } headers = { 'ContentType': 'application/json; charset=UTF-8' } r = requests.post(url, params=url_params, data=json.dumps(post_params), headers = headers, cookies=g_info['cookies']) r.encoding = 'utf-8' dic = json.loads(r.text) GroupMapUsers = {} ContactList = dic['ContactList'] for contact in ContactList: memberlist = contact['MemberList'] for member in memberlist: # 默认 @群名片,没有群名片就 @昵称 nickname = member['NickName'] displayname = member['DisplayName'] AT = '' if displayname == '': # 有些人的昵称会有表情 会表示成 <span> # 需要 html.unescape() 转义一下 AT = html.unescape(nickname) else: AT = html.unescape(displayname) GroupMapUsers[member['UserName']] = AT if DEBUG: print (member['NickName']) # 整群的成员列表消息记录 g_info[UserName] = GroupMapUsers def getgroupinfo(): global g_info if 'Group_UserName_Req' not in g_info: return if g_info['Group_UserName_Req'] == '0': return Group_UserName = g_info['Group_UserName_Req'] webwxbatchgetcontact(Group_UserName) # 这个变量表示一次 获取群成员列表 请求。请求完毕置空 g_info['Group_UserName_Req'] = '0' time.sleep(0.5) def heartBeatLoop(): while True: retcode, selector = syncCheck() if retcode != '0': print ('sync 失败。。。') if selector == '2': state, msg_list = webwxsync() getMsg(msg_list) getgroupinfo() time.sleep(1) def main(): global g_info if not getUUID(): print ('获取 uuid 失败') return print ('获取二维码图片中。。。') showQRImage() time.sleep(1) while waitForLogin() != '200': pass if not login(): print ('登陆失败') return if not webwxinit(): print ('初始化失败') return print ('登陆') print ('获取好友。。。。') webwxgetcontact() print ('开始心跳 噗咚噗通') t1 = threading.Thread(target=heartBeatLoop) t1.start() MemberCount = len(g_info['MemberList']) print ('这位同志啊,你有 %s 个好友' % MemberCount) try: while True: sendMsg() time.sleep(1.5) except KeyboardInterrupt: print ('bye bye ~') if __name__ == '__main__': main()