├── .gitignore ├── wechat-asyncio ├── config.py ├── main.py ├── logger.conf ├── RobotPredefinedAnswer.py ├── Monitor.py ├── RobotEngine.py ├── MsgHandler.py ├── HttpClient.py └── Wechat.py ├── README.md └── wechat-draft └── wechat-robot.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.json 3 | *.jpg 4 | auth.py 5 | __pycache__ 6 | *.log 7 | .idea -------------------------------------------------------------------------------- /wechat-asyncio/config.py: -------------------------------------------------------------------------------- 1 | send_interval = 0.5 2 | sync_interval = 1 3 | updategroupinfo_interval = 1 4 | monitor_interval = 10 5 | msgloop_interval = 1 6 | -------------------------------------------------------------------------------- /wechat-asyncio/main.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import aiohttp 4 | import asyncio 5 | 6 | from Wechat import Wechat 7 | from MsgHandler import MsgHandler 8 | from RobotEngine import RobotEngine 9 | from Monitor import Monitor 10 | import auth 11 | 12 | import logging 13 | import logging.config 14 | 15 | logging.config.fileConfig("logger.conf") 16 | 17 | with aiohttp.ClientSession() as client, aiohttp.ClientSession() as rclient: 18 | wx = Wechat(client) 19 | robot = RobotEngine(rclient, auth.apikey) 20 | msg = MsgHandler(wx, robot) 21 | god = Monitor(wx) 22 | tasks = [ 23 | wx.sync() , 24 | wx.sendmsg() , 25 | wx.updategroupinfo() , 26 | msg.msgloop() , 27 | god.monitor() 28 | ] 29 | asyncio.get_event_loop().run_until_complete(asyncio.wait(tasks)) 30 | -------------------------------------------------------------------------------- /wechat-asyncio/logger.conf: -------------------------------------------------------------------------------- 1 | #logger.conf 2 | 3 | 4 | ############################################### 5 | 6 | [loggers] 7 | keys=root,wx,monitor 8 | 9 | [logger_root] 10 | level=DEBUG 11 | handlers=hand01 12 | 13 | [logger_wx] 14 | handlers=hand01 15 | qualname=wx 16 | propagate=0 17 | 18 | [logger_monitor] 19 | handlers=hand02 20 | qualname=monitor 21 | propagate=0 22 | 23 | ############################################### 24 | 25 | [handlers] 26 | keys=hand01,hand02 27 | 28 | [handler_hand01] 29 | class=handlers.RotatingFileHandler 30 | level=DEBUG 31 | formatter=form01 32 | args=('wx.log', 'a', 10*1024*1024, 5) 33 | 34 | [handler_hand02] 35 | class=handlers.RotatingFileHandler 36 | level=DEBUG 37 | formatter=form01 38 | args=('monitor.log', 'a', 10*1024*1024, 5) 39 | 40 | ############################################### 41 | 42 | [formatters] 43 | keys=form01 44 | 45 | [formatter_form01] 46 | format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s 47 | datefmt=%a, %d %b %Y %H:%M:%S 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 微信聊天机器人 - 个人账号版 2 | 3 | 这是一个基于微信网页版接口的,个人账号聊天机器人。 4 | 5 | 不同于微信公众号和订阅号,个人账号腾讯并没有提供 API 接口,所以只能模拟微信网页版的协议来做。由于使用未公开通信协议,故不能保证向后兼容性。 6 | 7 | * wechat-asyncio 是异步实现的。需要 Python 3.5。 8 | * wechat-draft 是很早的第一个可用的版本。只有两个线程。只需要 > Python 3。 9 | 10 | ## 简单说明 11 | 12 | ### 依赖 13 | 14 | 申请一枚图灵机器人 API,新建 auth.py,并写入 15 | 16 | apikey = 'xxxxxxxxxxx' 17 | 18 | * pip3 install aiohttp 19 | 20 | ### 快速开始 21 | 22 | 目前使用起来还不友好,且登陆有一定失败率。 23 | 24 | (terminal 1) python3 main.py 25 | 26 | 运行后会在当前文件夹内下载二维码,根据终端提示做即可。 27 | 28 | 1. 若 10s 内二维码下载失败则重来。 29 | 2. 若 wx.log 提示 sync 失败则重来。 30 | 31 | ### 参数调整 32 | 33 | 在 config.py 中可以对各个时间间隔做调整。 34 | 35 | ## Changelog 36 | 37 | ### 2016.2.4 38 | 39 | 改成了基于 asyncio/aiohttp 的异步框架。现在只有 5 个任务:sync, sendmsg, updategroupinfo, msgloop, monitor。 40 | 41 | ### 2016.1.23 42 | 43 | wechat-robot.py 改用 Python3。原来的 urllib2/urllib 全部改为了 requests。 44 | 45 | 46 | ## 参考 47 | 48 | 前期参考了 [查看被删的微信好友](https://github.com/0x5e/wechat-deleted-friends)。 49 | 50 | 也参考了 [Nodejs 版的微信聊天机器人](https://github.com/HalfdogStudio/wechat-user-bot)。 51 | -------------------------------------------------------------------------------- /wechat-asyncio/RobotPredefinedAnswer.py: -------------------------------------------------------------------------------- 1 | dialoglist = [ 2 | '公司下属出错了,没什么大事,我小心提醒一句,就说下次要注意啊,不然艾姆安格瑞', 3 | '我妈给我介绍对象,看了照片,很美,我脱口而出,excited!' , 4 | '财务处通知我薪水涨了,我说,哎呀,你们给我搞的这个涨薪,excited!' , 5 | '吃到好吃的东西,不说delicious, 说excited', 6 | '新同事长的美艳无比,自我介绍时候我忍不住连声赞叹excited' , 7 | '看完大圣归来,观后感就是excited' , 8 | '遇到任何高兴的事情,都用一棵塞体的表达心情' , 9 | '有了点积蓄开始买手表,先后买了三块,不打算再买了,遇到重大场合把三块都戴着以示严肃' , 10 | '以前在公交车被人踩了,都是叫哎呀!现在被踩脱口而出 艾姆安格瑞!' , 11 | '遇到工科生必问你们学不学engineer drawing,用不用鸭嘴笔' , 12 | '身为淮安人说普通话不自觉带扬州口音' , 13 | '把格利高里派克演的葛底斯堡演讲视频截下来,对着反复练习,每一次都觉得自己受到了灵魂洗礼,熟练背诵全文没一点问题' , 14 | '曾经花了一年的闲余时间听完了莎士比亚的戏剧全集音频,顺着翻完了世图出版的那本全集书,看书的动机就是因为他说中国人要熟悉莎士比亚和贝多芬' , 15 | '欢迎客人亲戚时候,都是套路:今天天气预报说有雨,二舅你来了立刻万里无云阳光灿烂' , 16 | '跟人提起未来,规划,梦想的时候,必然要提到,人的命运啊,就什么都不知道。个人奋斗当然很重要,但和历史行程也是分不开的' , 17 | '被人表扬时候表示谦虚,都是说我很惭愧,就做了这么点微小工作,谢谢大家' , 18 | '为了表示志向高洁理想远大,必然要念林则徐的两句诗' , 19 | '朋友问我下班去吃日料不,以前回好,现在回吼啊!' , 20 | '就会一句粤语,识得唔识得噶' , 21 | '形容和某人的深厚友谊一般用谈笑风生这个词' , 22 | '对唱歌好有文艺范的妹子充满好感' , 23 | '以前很反感说话中夹英,现在能夹就夹,不然体现不出膜法师的风度' , 24 | '这些就是比较少见的一些词语运用,类似图样什么的出现的场合太多了,就不一一列举了' , 25 | '它,就是化肥,“金坷垃”。' , 26 | '你家麦地2米以下藏着丰富的氮磷钾。' , 27 | '肥料掺了金坷垃,一袋能顶两袋撒' , 28 | '肥料掺了金坷垃,小麦亩产一千八。' , 29 | '美国英国法国都在用金坷垃......日本不给用......' 30 | ] 31 | -------------------------------------------------------------------------------- /wechat-asyncio/Monitor.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import logging 4 | import asyncio 5 | from time import ctime 6 | import config 7 | 8 | logger = logging.getLogger('monitor') 9 | 10 | class Monitor(): 11 | def __init__(self, wx): 12 | self.wx = wx 13 | 14 | async def monitor(self): 15 | while True: 16 | logger.info('Monitoring.......... ' + ctime()) 17 | logger.info('retcode %s, selector %s' % (self.wx.retcode, self.wx.selector)) 18 | logger.info('recvqueue size: %s' % self.wx.recvqueue.qsize()) 19 | logger.info('sendqueue size: %s' % self.wx.sendqueue.qsize()) 20 | logger.info('updatequeue size: %s' % self.wx.updatequeue.qsize()) 21 | logger.info('Monitor end.......... ') 22 | 23 | if self.wx.recvqueue.qsize() > 3: 24 | while self.wx.recvqueue.qsize() > 1: 25 | try: 26 | self.wx.recvqueue.get_nowait() 27 | except: 28 | pass 29 | 30 | if self.wx.sendqueue.qsize() > 3: 31 | while self.wx.sendqueue.qsize() > 1: 32 | try: 33 | self.wx.sendqueue.get_nowait() 34 | except: 35 | pass 36 | 37 | if self.wx.updatequeue.qsize() > 3: 38 | while self.wx.updatequeue.qsize() > 1: 39 | try: 40 | self.wx.updatequeue.get_nowait() 41 | except: 42 | pass 43 | 44 | await asyncio.sleep(config.monitor_interval) 45 | -------------------------------------------------------------------------------- /wechat-asyncio/RobotEngine.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import asyncio 4 | import re 5 | from HttpClient import * 6 | import RobotPredefinedAnswer 7 | import random 8 | 9 | class RobotEngine(): 10 | def __init__(self, client, apikey): 11 | self.rbclient = HttpClient(client) 12 | self.apikey = apikey 13 | self.acc = 0 14 | self.lasttext = '' 15 | self.lastuser = '' 16 | 17 | async def answser(self, msginfo): 18 | content = msginfo['Content'] 19 | # 去掉英文,因为图灵机器人不支持 20 | content = re.sub(r'[a-zA-Z]', '', content) 21 | # 去掉两端空格,不然图灵api那边有问题 22 | content = content.strip() 23 | # 做一下字数限制 24 | content = content[:50] 25 | # 做完处理发现没有字符了 26 | if content == '': 27 | content = self.__randomanswer() 28 | 29 | tuling_data = dict( 30 | key=self.apikey, 31 | info=content, 32 | userid=msginfo['FromUserName'][2:32], # 把userName的一部分截取,作为userid,这样对话有上下文功能. 33 | ) 34 | tuling_url = 'http://www.tuling123.com/openapi/api' 35 | # 使用post方法访问图灵,以免出现content没有经过urlencoded得不到正确结果. 36 | dic = await self.rbclient.post_json_timeout(tuling_url, data=tuling_data) 37 | if dic is not None: 38 | text = dic['text'] 39 | else: 40 | text = '网络异常。。。。。。。。。。。。' 41 | 42 | # 做一下字数回复的限制 43 | if len(text)>100: 44 | text = text[:100] 45 | text = text + '......' 46 | 47 | # 对于不能回答的问题直接回复数数 48 | if text.find('不明白你是什么意思,麻烦换一种说法') != -1: 49 | text = str(self.acc) 50 | self.acc = self.acc + 1 51 | if text.find('不明白你说的什么意思') != -1: 52 | text = str(self.acc) 53 | self.acc = self.acc + 1 54 | if text == self.lasttext and msginfo['FromUserName'] == self.lastuser: 55 | text = self.__randomanswer() 56 | 57 | self.lasttext = text 58 | self.lastuser = msginfo['FromUserName'] 59 | return text 60 | 61 | def __randomanswer(self): 62 | diaglen = len(RobotPredefinedAnswer.dialoglist) 63 | index = random.randint(0, diaglen-1) 64 | return RobotPredefinedAnswer.dialoglist[index] 65 | -------------------------------------------------------------------------------- /wechat-asyncio/MsgHandler.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import asyncio 4 | import re 5 | 6 | import config 7 | import logging 8 | logger = logging.getLogger('monitor') 9 | 10 | class MsgHandler: 11 | def __init__(self, wx, robot): 12 | self.wx = wx 13 | self.robot = robot 14 | 15 | async def __parsemsg(self): 16 | msg = await self.wx.recvqueue.get() 17 | # 自己从别的平台发的消息忽略 18 | if msg['FromUserName'] == self.wx.My['UserName']: 19 | return None 20 | # 排除不是发给自己的消息 21 | if msg['ToUserName'] != self.wx.My['UserName']: 22 | return None 23 | # 在黑名单里面 24 | if msg['FromUserName'] in self.wx.blacklist: 25 | return None 26 | 27 | msginfo = {} 28 | # 文字消息 29 | if msg['MsgType'] == 1: 30 | content = msg['Content'] 31 | fromsomeone_NickName = '' 32 | ## 来自群消息 33 | if msg['FromUserName'].find('@@') != -1: 34 | fromsomeone = content[:content.find(':
')] 35 | groupname = msg['FromUserName'] 36 | if groupname not in self.wx.grouplist: 37 | await self.wx.updatequeue.put(groupname) 38 | elif fromsomeone in self.wx.grouplist[groupname]: 39 | fromsomeone_NickName = self.wx.grouplist[groupname][fromsomeone] 40 | fromsomeone_NickName = '@' + fromsomeone_NickName + ' ' 41 | else: 42 | await self.wx.updatequeue.put(groupname) 43 | # 去掉消息头部的来源信息 44 | content = content[content.find('>')+1:] 45 | # 普通消息 46 | else: 47 | fromsomeone_NickName = '' 48 | 49 | # print (content) 50 | if len(content)>1: 51 | regx = re.compile(r'@.+?\u2005') 52 | content = regx.sub(' ', content) 53 | 54 | msginfo['Content'] = content 55 | msginfo['fromsomeone'] = fromsomeone_NickName 56 | msginfo['FromUserName'] = msg['FromUserName'] 57 | 58 | return msginfo 59 | else: 60 | return None 61 | 62 | async def msgloop(self): 63 | while True: 64 | msginfo = await self.__parsemsg() 65 | if msginfo != None: 66 | response = {} 67 | answser = await self.robot.answser(msginfo) 68 | response['Content'] = msginfo['fromsomeone'] + answser 69 | response['user'] = msginfo['FromUserName'] 70 | await self.wx.sendqueue.put(response) 71 | 72 | logger.info(msginfo['fromsomeone'] + ' say: ' + msginfo['Content']) 73 | logger.info('Harry Potter say: ' + response['Content']) 74 | 75 | await asyncio.sleep(config.msgloop_interval) 76 | -------------------------------------------------------------------------------- /wechat-asyncio/HttpClient.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import logging 4 | import asyncio 5 | import aiohttp 6 | import json 7 | 8 | logger = logging.getLogger('wx') 9 | 10 | 11 | class HttpClient: 12 | def __init__(self, client): 13 | if not isinstance(client, aiohttp.ClientSession): 14 | raise TypeError('Please init with a aiohttp.ClientSession instance') 15 | self.__client = client 16 | self.__cookies = None 17 | 18 | async def get(self, url, params=None): 19 | try: 20 | self.__cookies = self.__client.cookies 21 | async with await self.__client.get(url, params=params) as r: 22 | #assert r.status == 200 23 | return await r.text() 24 | 25 | except Exception: 26 | logger.exception("Network Exception, url: %s, params: %s" % (url, params)) 27 | return None 28 | 29 | async def get_json(self, url, params=None): 30 | try: 31 | self.__cookies = self.__client.cookies 32 | async with await self.__client.get(url, params=params) as r: 33 | text = await r.text(encoding='utf-8') 34 | return json.loads(text) 35 | 36 | except Exception: 37 | logger.exception("Network Exception, url: %s, params: %s" % (url, params)) 38 | return None 39 | 40 | async def get_json_timeout(self, url, params=None): 41 | try: 42 | self.__cookies = self.__client.cookies 43 | with aiohttp.Timeout(2): 44 | async with await self.__client.get(url, params=params) as r: 45 | text = await r.text(encoding='utf-8') 46 | return json.loads(text) 47 | 48 | except Exception: 49 | logger.exception("Network Exception, url: %s, params: %s" % (url, params)) 50 | return None 51 | 52 | async def post(self, url, data, params=None): 53 | try: 54 | async with await self.__client.post(url, params=params, data=data) as r: 55 | #assert r.status == 200 56 | return await r.text() 57 | 58 | except Exception: 59 | logger.exception("Network Exception, url: %s, params: %s" % (url, params)) 60 | return None 61 | 62 | async def post_json(self, url, data, params=None): 63 | try: 64 | 65 | async with await self.__client.post(url, params=params, data=data) as r: 66 | #assert r.status == 200 67 | text = await r.text(encoding='utf-8') 68 | return json.loads(text) 69 | 70 | except Exception: 71 | logger.exception("Network Exception, url: %s, params: %s" % (url, params)) 72 | return None 73 | 74 | async def post_json_timeout(self, url, data, params=None): 75 | try: 76 | 77 | with aiohttp.Timeout(2): 78 | async with await self.__client.post(url, params=params, data=data) as r: 79 | #assert r.status == 200 80 | text = await r.text(encoding='utf-8') 81 | return json.loads(text) 82 | 83 | except Exception: 84 | logger.exception("Network Exception, url: %s, params: %s" % (url, params)) 85 | return None 86 | 87 | async def downloadfile(self, url, data, filename): 88 | try: 89 | async with await self.__client.post(url, data=data) as r: 90 | #assert r.status == 200 91 | with open(filename, 'wb') as fd: 92 | while True: 93 | chunk = await r.content.read(512) 94 | if not chunk: 95 | break 96 | fd.write(chunk) 97 | return True 98 | except aiohttp.errors.DisconnectedError: 99 | return False 100 | -------------------------------------------------------------------------------- /wechat-asyncio/Wechat.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | from HttpClient import HttpClient 5 | import aiohttp 6 | import asyncio 7 | import time 8 | import re 9 | import xml.dom.minidom 10 | import json 11 | import html 12 | 13 | import config 14 | import logging 15 | 16 | logger = logging.getLogger('wx') 17 | 18 | class Wechat(): 19 | def __init__(self, client): 20 | self.__wxclient = HttpClient(client) 21 | self.tip = 0 22 | self.deviceId = 'e000701000000000' 23 | 24 | self.recvqueue = asyncio.Queue() 25 | self.sendqueue = asyncio.Queue() 26 | self.blacklist = [] 27 | self.updatequeue = asyncio.Queue() # 更新群组信息的请求 28 | self.grouplist = {} # 存储群组的联系人信息 29 | # 给 monitor 用 30 | self.retcode = '0' 31 | self.selector = '0' 32 | 33 | async def __getuuid(self): 34 | logger.debug('Entering getuuid.') 35 | url = 'https://login.weixin.qq.com/jslogin' 36 | payload = { 37 | 'appid': 'wx782c26e4c19acffb', 38 | 'fun': 'new', 39 | 'lang': 'zh_CN', 40 | '_': int(time.time()), 41 | } 42 | 43 | text = await self.__wxclient.post(url=url, data=payload) 44 | if text == None: 45 | return False 46 | logger.info(text) 47 | 48 | regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)"' 49 | pm = re.search(regx, text) 50 | 51 | code = pm.group(1) 52 | uuid = pm.group(2) 53 | 54 | self.uuid = uuid 55 | if code == '200': 56 | return True 57 | else: 58 | return False 59 | 60 | async def __downloadQR(self): 61 | logger.debug('Entering downloadQR.') 62 | url = 'https://login.weixin.qq.com/qrcode/' + self.uuid 63 | payload = { 64 | 't': 'webwx', 65 | '_': int(time.time()), 66 | } 67 | 68 | su = await self.__wxclient.downloadfile(url, data=payload, filename='qrimage.jpg') 69 | logger.info ('请扫描二维码') 70 | print ('请扫描二维码') 71 | return su 72 | 73 | async def __waitforlogin(self): 74 | logger.debug('Waiting for login.......') 75 | url = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s' % (self.tip, self.uuid, int(time.time())) 76 | text = await self.__wxclient.get(url) 77 | 78 | regx = r'window.code=(\d+);' 79 | pm = re.search(regx, text) 80 | code = pm.group(1) 81 | 82 | if code == '201': 83 | logger.info ('成功扫描,请在手机上点击确认以登录') 84 | print ('成功扫描,请在手机上点击确认以登录') 85 | self.tip = 0 86 | elif code == '200': 87 | logger.info ('正在登录。。。') 88 | print ('正在登录。。。') 89 | regx = r'window.redirect_uri="(\S+?)";' 90 | pm = re.search(regx, text) 91 | redirect_uri = pm.group(1) + '&fun=new' 92 | self.redirect_uri = redirect_uri 93 | base_uri = redirect_uri[:redirect_uri.rfind('/')] 94 | self.base_uri = base_uri 95 | 96 | services = [ 97 | ('wx2.qq.com', 'webpush2.weixin.qq.com'), 98 | ('qq.com', 'webpush.weixin.qq.com'), 99 | ('web1.wechat.com', 'webpush1.wechat.com'), 100 | ('web2.wechat.com', 'webpush2.wechat.com'), 101 | ('wechat.com', 'webpush.wechat.com'), 102 | ('web1.wechatapp.com', 'webpush1.wechatapp.com'), 103 | ] 104 | 105 | push_uri = base_uri 106 | for (searchUrl, pushUrl) in services: 107 | if base_uri.find(searchUrl) >= 0: 108 | push_uri = 'https://%s/cgi-bin/mmwebwx-bin' % pushUrl 109 | break 110 | self.push_uri = push_uri 111 | elif code == '408': 112 | pass 113 | 114 | return code 115 | 116 | 117 | async def __checklogin(self): 118 | logger.debug('Entering checklogin.') 119 | text = await self.__wxclient.get(self.redirect_uri) 120 | 121 | doc = xml.dom.minidom.parseString(text) 122 | root = doc.documentElement 123 | 124 | for node in root.childNodes: 125 | if node.nodeName == 'skey': 126 | skey = node.childNodes[0].data 127 | elif node.nodeName == 'wxsid': 128 | wxsid = node.childNodes[0].data 129 | elif node.nodeName == 'wxuin': 130 | wxuin = node.childNodes[0].data 131 | elif node.nodeName == 'pass_ticket': 132 | pass_ticket = node.childNodes[0].data 133 | 134 | if not all((skey, wxsid, wxuin, pass_ticket)): 135 | return False 136 | 137 | BaseRequest = { 138 | 'Uin': int(wxuin), 139 | 'Sid': wxsid, 140 | 'Skey': skey, 141 | 'DeviceID': self.deviceId, 142 | } 143 | logger.debug('%s, %s, %s, %s', skey, wxsid, wxuin, pass_ticket) 144 | self.skey = skey 145 | self.wxsid = wxsid 146 | self.wxuin = wxuin 147 | self.pass_ticket = pass_ticket 148 | self.BaseRequest = BaseRequest 149 | 150 | return True 151 | 152 | 153 | async def __responseState(self, func, BaseResponse): 154 | ErrMsg = BaseResponse['ErrMsg'] 155 | Ret = BaseResponse['Ret'] 156 | logger.info('func: %s, Ret: %d, ErrMsg: %s' % (func, Ret, ErrMsg)) 157 | if Ret != 0: 158 | return False 159 | return True 160 | 161 | 162 | async def __webwxinit(self): 163 | logger.debug('Entering webwxinit.') 164 | url = self.base_uri + \ 165 | '/webwxinit?pass_ticket=%s&skey=%s&r=%s' % ( 166 | self.pass_ticket, self.skey, int(time.time())) 167 | payload = { 168 | 'BaseRequest' : self.BaseRequest 169 | } 170 | 171 | dic = await self.__wxclient.post_json(url=url, data=json.dumps(payload)) 172 | 173 | self.My = dic['User'] 174 | self.SyncKey = dic['SyncKey'] 175 | logger.debug('The new SyncKey is: %s' % self.SyncKey) 176 | 177 | return await self.__responseState('webwxinit', dic['BaseResponse']) 178 | 179 | async def __webwxgetcontact(self): 180 | url = self.base_uri + \ 181 | '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' % ( 182 | self.pass_ticket, self.skey, int(time.time())) 183 | 184 | dic = await self.__wxclient.get_json(url) 185 | 186 | SpecialUsers = ["newsapp", "fmessage", "filehelper", "weibo", "qqmail", "tmessage", "qmessage", "qqsync", "floatbottle", "lbsapp", "shakeapp", "medianote", "qqfriend", "readerapp", "blogapp", "facebookapp", "masssendapp", 187 | "meishiapp", "feedsapp", "voip", "blogappweixin", "weixin", "brandsessionholder", "weixinreminder", "wxid_novlwrv3lqwv11", "gh_22b87fa7cb3c", "officialaccounts", "notification_messages", "wxitil", "userexperience_alarm"] 188 | self.blacklist += SpecialUsers 189 | 190 | MemberList = {} 191 | for member in dic['MemberList']: 192 | if member['VerifyFlag'] & 8 != 0: # 公众号 193 | continue 194 | elif member['UserName'] in SpecialUsers: 195 | continue 196 | MemberList[member['UserName']] = { 197 | 'NickName' : member['NickName'] , 198 | 'DisplayName' : member['DisplayName'] 199 | } 200 | 201 | self.memberlist = MemberList 202 | logger.info('You have %s friends.' % len(MemberList)) 203 | 204 | 205 | 206 | async def __login(self): 207 | success = await self.__getuuid() 208 | if not success: 209 | logger.info ('获取 uuid 失败') 210 | print ('获取 uuid 失败') 211 | success = await self.__downloadQR() 212 | if not success: 213 | logger.info ('获取二维码失败') 214 | print ('获取二维码失败') 215 | 216 | while await self.__waitforlogin() != '200': 217 | pass 218 | 219 | success = await self.__checklogin() 220 | if not success: 221 | logger.info ('登陆失败') 222 | print ('登陆失败') 223 | logger.info ('登陆成功') 224 | print ('登陆成功') 225 | success = await self.__webwxinit() 226 | if not success: 227 | logger.info ('初始化失败') 228 | print ('初始化失败') 229 | logger.info ('初始化成功') 230 | print ('初始化成功') 231 | 232 | await self.__webwxgetcontact() 233 | 234 | def __syncKey(self): 235 | SyncKey = self.SyncKey 236 | SyncKeyItems = ['%s_%s' % (item['Key'], item['Val']) 237 | for item in SyncKey['List']] 238 | SyncKeyStr = '|'.join(SyncKeyItems) 239 | return SyncKeyStr 240 | 241 | async def __synccheck(self): 242 | url = self.push_uri + '/synccheck?' 243 | BaseRequest = self.BaseRequest 244 | params = { 245 | 'skey': BaseRequest['Skey'] , 246 | 'sid': BaseRequest['Sid'] , 247 | 'uin': BaseRequest['Uin'] , 248 | 'deviceId': BaseRequest['DeviceID'] , 249 | 'synckey': self.__syncKey() , 250 | 'r': int(time.time()*1000) 251 | } 252 | text = await self.__wxclient.get(url, params = params) 253 | if text == None: 254 | return ('1111', '1111') 255 | 256 | regx = r'window.synccheck={retcode:"(\d+)",selector:"(\d+)"}' 257 | pm = re.search(regx, text) 258 | 259 | retcode = pm.group(1) 260 | selector = pm.group(2) 261 | logger.info('retcode: %s, selector: %s' % (retcode, selector)) 262 | return (retcode, selector) 263 | 264 | async def __webwxsync(self): 265 | url = self.base_uri + '/webwxsync?' 266 | payload = { 267 | 'BaseRequest' : self.BaseRequest , 268 | 'SyncKey' : self.SyncKey , 269 | 'rr' : ~int(time.time()) 270 | } 271 | params = { 272 | 'skey' : self.BaseRequest['Skey'] , 273 | 'pass_ticket' : self.pass_ticket , 274 | 'sid' : self.BaseRequest['Sid'] 275 | } 276 | 277 | dic = await self.__wxclient.post_json(url, params=params, data=json.dumps(payload)) 278 | if dic == None: 279 | return 280 | # 更新 synckey 281 | self.SyncKey = dic['SyncKey'] 282 | 283 | await self.__responseState('webwxsync', dic['BaseResponse']) 284 | 285 | msglist = dic['AddMsgList'] 286 | for msg in msglist: 287 | await self.recvqueue.put(msg) 288 | 289 | 290 | async def __webwxsendmsg(self, content, user): 291 | url = self.base_uri + \ 292 | '/webwxsendmsg?pass_ticket=%s' % (self.pass_ticket) 293 | 294 | msgid = int(time.time()*10000000) 295 | msg = { 296 | 'ClientMsgId' : msgid , 297 | 'Content' : content, 298 | 'FromUserName' : self.My['UserName'] , 299 | 'LocalID' : msgid , 300 | 'ToUserName' : user, 301 | 'Type' : 1 302 | } 303 | payload = { 304 | 'BaseRequest' : self.BaseRequest , 305 | 'Msg' : msg 306 | } 307 | data = json.dumps(payload, ensure_ascii=False) 308 | data = data.encode('utf-8') 309 | 310 | text = await self.__wxclient.post(url, data=data) 311 | 312 | 313 | async def __webwxbatchgetcontact(self, groupname): 314 | url = self.base_uri + '/webwxbatchgetcontact?' 315 | List = [{ 316 | 'ChatRoomId' : '', 317 | 'UserName' : groupname 318 | }] 319 | payload = { 320 | 'BaseRequest': self.BaseRequest , 321 | 'Count' : 1 , 322 | 'List' : List 323 | } 324 | params = { 325 | 'lang' : 'zh_CN' , 326 | 'type' : 'ex' , 327 | 'pass_ticket' : self.pass_ticket , 328 | 'r' : int(time.time()) 329 | } 330 | 331 | dic = await self.__wxclient.post_json(url, params=params, data=json.dumps(payload)) 332 | if dic == None: 333 | return 334 | GroupMapUsers = {} 335 | ContactList = dic['ContactList'] 336 | for contact in ContactList: 337 | memberlist = contact['MemberList'] 338 | for member in memberlist: 339 | # 默认 @群名片,没有群名片就 @昵称 340 | nickname = member['NickName'] 341 | displayname = member['DisplayName'] 342 | AT = '' 343 | if displayname == '': 344 | # 有些人的昵称会有表情 会表示成 <span> 345 | # 需要 html.unescape() 转义一下 346 | AT = html.unescape(nickname) 347 | else: 348 | AT = html.unescape(displayname) 349 | GroupMapUsers[member['UserName']] = AT 350 | 351 | self.grouplist[groupname] = GroupMapUsers 352 | 353 | async def sync(self): 354 | await self.__login() 355 | logger.info ('开始心跳噗通噗咚 咚咚咚!!!!') 356 | print ('开始心跳噗通噗咚 咚咚咚!!!!') 357 | logger.info('Begin to sync with wx server.....') 358 | while True: 359 | retcode, selector = await self.__synccheck() 360 | if retcode != '0': 361 | logger.info ('sync 失败') 362 | print ('sync 失败') 363 | if selector != '0': 364 | await self.__webwxsync() 365 | 366 | await asyncio.sleep(config.sync_interval) 367 | self.retcode = retcode 368 | self.selector = selector 369 | 370 | 371 | async def sendmsg(self): 372 | while True: 373 | response = await self.sendqueue.get() 374 | # 不要发的太频繁,在拿到 response 之后歇一秒 375 | await asyncio.sleep(config.send_interval) 376 | await self.__webwxsendmsg(response['Content'], response['user']) 377 | 378 | async def updategroupinfo(self): 379 | while True: 380 | groupname = await self.updatequeue.get() 381 | 382 | logger.info('更新群信息开始') 383 | await self.__webwxbatchgetcontact(groupname) 384 | await asyncio.sleep(config.updategroupinfo_interval) 385 | logger.info('更新群信息结束') 386 | -------------------------------------------------------------------------------- /wechat-draft/wechat-robot.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import os 4 | import requests 5 | import simplejson as json 6 | import time 7 | import re 8 | import xml.dom.minidom 9 | # from collections import deque 10 | import queue 11 | import html 12 | 13 | import threading 14 | # lock = threading.Lock() 15 | 16 | DEBUG = False 17 | LOG = True 18 | 19 | deviceId = 'e000000000000000' 20 | g_info = {} 21 | g_info['tip'] = 0 22 | g_queue = queue.Queue()# [] 23 | 24 | import config 25 | apikey = config.apikey 26 | 27 | def getUUID(): 28 | global g_info 29 | 30 | url = 'https://login.weixin.qq.com/jslogin' 31 | params = { 32 | 'appid': 'wx782c26e4c19acffb', 33 | 'fun': 'new', 34 | 'lang': 'zh_CN', 35 | '_': int(time.time()), 36 | } 37 | 38 | r = requests.post(url, params) 39 | if DEBUG: 40 | print (r.text) 41 | text = r.text 42 | regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)"' 43 | pm = re.search(regx, text) 44 | 45 | code = pm.group(1) 46 | uuid = pm.group(2) 47 | 48 | g_info['uuid'] = uuid 49 | if code == '200': 50 | return True 51 | 52 | return False 53 | 54 | def showQRImage(): 55 | global g_info 56 | 57 | uuid = g_info['uuid'] 58 | url = 'https://login.weixin.qq.com/qrcode/' + uuid 59 | params = { 60 | 't': 'webwx', 61 | '_': int(time.time()), 62 | } 63 | 64 | r = requests.post(url, params) 65 | g_info['tip'] = 1 66 | 67 | with open('qrcode.jpg', 'wb') as fd: 68 | for chunk in r.iter_content(512): 69 | fd.write(chunk) 70 | 71 | print ('请扫描二维码。。。') 72 | 73 | def waitForLogin(): 74 | global g_info 75 | 76 | tip = g_info['tip'] 77 | uuid = g_info['uuid'] 78 | url = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s' % ( 79 | tip, uuid, int(time.time())) 80 | 81 | r = requests.get(url) 82 | text = r.text 83 | regx = r'window.code=(\d+);' 84 | pm = re.search(regx, text) 85 | 86 | code = pm.group(1) 87 | if code == '201': 88 | print ('成功扫描,请在手机上点击确认以登录') 89 | g_info['tip'] = 0 90 | elif code == '200': 91 | print ('正在登录。。。') 92 | regx = r'window.redirect_uri="(\S+?)";' 93 | pm = re.search(regx, text) 94 | redirect_uri = pm.group(1) + '&fun=new' 95 | g_info['redirect_uri'] = redirect_uri 96 | base_uri = redirect_uri[:redirect_uri.rfind('/')] 97 | g_info['base_uri'] = base_uri 98 | 99 | services = [ 100 | ('wx2.qq.com', 'webpush2.weixin.qq.com'), 101 | ('qq.com', 'webpush.weixin.qq.com'), 102 | ('web1.wechat.com', 'webpush1.wechat.com'), 103 | ('web2.wechat.com', 'webpush2.wechat.com'), 104 | ('wechat.com', 'webpush.wechat.com'), 105 | ('web1.wechatapp.com', 'webpush1.wechatapp.com'), 106 | ] 107 | 108 | push_uri = base_uri 109 | for (searchUrl, pushUrl) in services: 110 | if base_uri.find(searchUrl) >= 0: 111 | push_uri = 'https://%s/cgi-bin/mmwebwx-bin' % pushUrl 112 | break 113 | g_info['push_uri'] = push_uri 114 | 115 | elif code == '408': 116 | pass 117 | 118 | return code 119 | 120 | def login(): 121 | global g_info 122 | 123 | redirect_uri = g_info['redirect_uri'] 124 | r = requests.get(redirect_uri) 125 | 126 | 127 | g_info['cookies'] = r.cookies 128 | 129 | doc = xml.dom.minidom.parseString(r.text) 130 | root = doc.documentElement 131 | 132 | for node in root.childNodes: 133 | if node.nodeName == 'skey': 134 | skey = node.childNodes[0].data 135 | elif node.nodeName == 'wxsid': 136 | wxsid = node.childNodes[0].data 137 | elif node.nodeName == 'wxuin': 138 | wxuin = node.childNodes[0].data 139 | elif node.nodeName == 'pass_ticket': 140 | pass_ticket = node.childNodes[0].data 141 | 142 | if not all((skey, wxsid, wxuin, pass_ticket)): 143 | return False 144 | 145 | BaseRequest = { 146 | 'Uin': int(wxuin), 147 | 'Sid': wxsid, 148 | 'Skey': skey, 149 | 'DeviceID': deviceId, 150 | } 151 | 152 | g_info['skey'] = skey 153 | g_info['wxsid'] = wxsid 154 | g_info['wxuin'] = wxuin 155 | g_info['pass_ticket'] = pass_ticket 156 | g_info['BaseRequest'] = BaseRequest 157 | 158 | if DEBUG: 159 | print (skey, wxsid, wxuin) 160 | 161 | return True 162 | 163 | 164 | 165 | def responseState(func, BaseResponse): 166 | ErrMsg = BaseResponse['ErrMsg'] 167 | Ret = BaseResponse['Ret'] 168 | if DEBUG: 169 | print('func: %s, Ret: %d, ErrMsg: %s' % (func, Ret, ErrMsg)) 170 | 171 | if Ret != 0: 172 | return False 173 | 174 | return True 175 | 176 | def webwxinit(): 177 | global g_info 178 | 179 | base_uri = g_info['base_uri'] 180 | pass_ticket = g_info['pass_ticket'] 181 | skey = g_info['skey'] 182 | BaseRequest = g_info['BaseRequest'] 183 | 184 | url = base_uri + \ 185 | '/webwxinit?pass_ticket=%s&skey=%s&r=%s' % ( 186 | pass_ticket, skey, int(time.time())) 187 | params = { 188 | 'BaseRequest': BaseRequest 189 | } 190 | 191 | r = requests.post(url, json.dumps(params)) 192 | text = r.text 193 | 194 | if DEBUG: 195 | with open('webwxinit.json', 'wb') as fd: 196 | for chunk in r.iter_content(512): 197 | fd.write(chunk) 198 | 199 | dic = json.loads(text) 200 | ContactList = dic['ContactList'] 201 | My = dic['User'] 202 | SyncKey = dic['SyncKey'] 203 | 204 | g_info['ContactList'] = ContactList 205 | g_info['My'] = My 206 | g_info['SyncKey'] = SyncKey 207 | 208 | state = responseState('webwxinit', dic['BaseResponse']) 209 | return state 210 | 211 | def webwxgetcontact(): 212 | global g_info 213 | 214 | base_uri = g_info['base_uri'] 215 | pass_ticket = g_info['pass_ticket'] 216 | skey = g_info['skey'] 217 | 218 | url = base_uri + \ 219 | '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' % ( 220 | pass_ticket, skey, int(time.time())) 221 | headers = {'Content-Type':'application/json; charset=UTF-8'} 222 | r = requests.get(url,headers=headers,cookies=g_info['cookies']) 223 | text = r.text 224 | 225 | if DEBUG: 226 | with open('webwxgetcontact.json', 'wb') as fd: 227 | for chunk in r.iter_content(512): 228 | fd.write(chunk) 229 | 230 | 231 | dic = json.loads(text, 'utf-8') 232 | MemberList = dic['MemberList'] 233 | SpecialUsers = ["newsapp", "fmessage", "filehelper", "weibo", "qqmail", "tmessage", "qmessage", "qqsync", "floatbottle", "lbsapp", "shakeapp", "medianote", "qqfriend", "readerapp", "blogapp", "facebookapp", "masssendapp", 234 | "meishiapp", "feedsapp", "voip", "blogappweixin", "weixin", "brandsessionholder", "weixinreminder", "wxid_novlwrv3lqwv11", "gh_22b87fa7cb3c", "officialaccounts", "notification_messages", "wxitil", "userexperience_alarm"] 235 | for i in range(len(MemberList) - 1, -1, -1): 236 | Member = MemberList[i] 237 | if Member['VerifyFlag'] & 8 != 0: # 公众号/服务号 238 | MemberList.remove(Member) 239 | elif Member['UserName'] in SpecialUsers: # 特殊账号 240 | MemberList.remove(Member) 241 | 242 | _MemberList = {} 243 | for user in MemberList: 244 | _MemberList[user['UserName']] = user['NickName'] 245 | 246 | g_info['MemberList'] = _MemberList 247 | 248 | 249 | def syncKey(): 250 | global g_info 251 | 252 | SyncKey = g_info['SyncKey'] 253 | SyncKeyItems = ['%s_%s' % (item['Key'], item['Val']) 254 | for item in SyncKey['List']] 255 | SyncKeyStr = '|'.join(SyncKeyItems) 256 | return SyncKeyStr 257 | 258 | def syncCheck(): 259 | global g_info 260 | 261 | push_uri = g_info['push_uri'] 262 | BaseRequest = g_info['BaseRequest'] 263 | url = push_uri + '/synccheck?' 264 | params = { 265 | 'skey': BaseRequest['Skey'], 266 | 'sid': BaseRequest['Sid'], 267 | 'uin': BaseRequest['Uin'], 268 | 'deviceId': BaseRequest['DeviceID'], 269 | 'synckey': syncKey(), 270 | 'r': int(time.time()), 271 | } 272 | 273 | r = requests.get(url, params=params, cookies=g_info['cookies'], timeout=300) 274 | text = r.text 275 | regx = r'window.synccheck={retcode:"(\d+)",selector:"(\d+)"}' 276 | pm = re.search(regx, text) 277 | 278 | retcode = pm.group(1) 279 | selector = pm.group(2) 280 | 281 | if DEBUG or LOG: 282 | print (text) 283 | 284 | return (retcode, selector) 285 | 286 | 287 | def webwxsync(): 288 | global g_info 289 | 290 | base_uri = g_info['base_uri'] 291 | BaseRequest = g_info['BaseRequest'] 292 | SyncKey = g_info['SyncKey'] 293 | pass_ticket = g_info['pass_ticket'] 294 | # url = base_uri + '/webwxsync?lang=zh_CN&skey=%s&sid=%s&pass_ticket=%s' % ( 295 | # BaseRequest['Skey'], BaseRequest['Sid'], quote_plus(pass_ticket)) 296 | url = base_uri + '/webwxsync?' 297 | params = { 298 | 'BaseRequest': BaseRequest, 299 | 'SyncKey': SyncKey, 300 | 'rr': ~int(time.time()), 301 | } 302 | url_params = { 303 | # 'lang' : 'zh_CN' , 304 | 'skey' : BaseRequest['Skey'] , 305 | 'pass_ticket' : pass_ticket , 306 | 'sid' : BaseRequest['Sid'] 307 | } 308 | headers = { 309 | 'ContentType': 'application/json; charset=UTF-8' 310 | } 311 | r = requests.post(url, params=url_params, data=json.dumps(params), headers = headers, cookies=g_info['cookies']) 312 | 313 | # if DEBUG: 314 | # global i 315 | # i = i + 1 316 | # with open('sync' + str(i) + '.json', 'wb') as fd: 317 | # for chunk in r.iter_content(512): 318 | # fd.write(chunk) 319 | r.encoding = 'utf-8' 320 | dic = json.loads(r.text) 321 | # print (dic) 322 | # 更新 synckey 323 | g_info['SyncKey'] = dic['SyncKey'] 324 | state = responseState('webwxsync', dic['BaseResponse']) 325 | 326 | msg_list = dic['AddMsgList'] 327 | if DEBUG: 328 | print (msg_list) 329 | 330 | return (state, msg_list) 331 | 332 | 333 | def getMsg(msg_list): 334 | global g_info, g_queue 335 | 336 | My = g_info['My'] 337 | 338 | for msg in msg_list: 339 | if msg['MsgType'] != 1: 340 | continue 341 | if msg['FromUserName'] == My['UserName']: 342 | continue 343 | if msg['ToUserName'] == My['UserName']: 344 | response = {} 345 | content = msg['Content'] 346 | 347 | # 群消息中 :
之前是 UserName 348 | if content.find(':
') != -1: 349 | fromsomeone = content[:content.find(':
')] 350 | else: 351 | fromsomeone = '' 352 | 353 | fromsomeone_NickName = '' 354 | if msg['FromUserName'].find('@@') != -1: 355 | # 如果是来自群,那就试着去 g_info[] 对应的群中找群成员列表 356 | groupName = msg['FromUserName'] 357 | # 群还没有记录 358 | if groupName not in g_info: 359 | g_info['Group_UserName_Req'] = msg['FromUserName'] 360 | # 在群列表中有了,因为可能群成员会变化,所以要再次找一遍 361 | elif fromsomeone in g_info[groupName]: 362 | fromsomeone_NickName = g_info[groupName][fromsomeone] 363 | fromsomeone_NickName = '@' + fromsomeone_NickName + ' ' 364 | # 找不到,所以置标志位,会在另一个群中触发寻找行为 365 | else: 366 | g_info['Group_UserName_Req'] = msg['FromUserName'] 367 | else: 368 | fromsomeone_NickName = '' 369 | 370 | response['fromsomeone'] = fromsomeone_NickName 371 | response['Content'] = content[content.find('>')+1:] 372 | response['FromUserName'] = msg['FromUserName'] 373 | 374 | # g_info['Group_UserName_Req'] = response['FromUserName'] 375 | 376 | # 不停地塞新消息 377 | # lock.acquire() 378 | # try: 379 | # g_queue.append(response) 380 | # finally: 381 | # lock.release() 382 | # print (response['Content']) 383 | 384 | # test 385 | if g_queue.qsize() > 5: 386 | g_queue.get() 387 | g_queue.get() 388 | g_queue.put(response) 389 | 390 | if LOG: 391 | print ('getmsg queue: %s' % g_queue.qsize()) 392 | 393 | 394 | def webwxsendmsg(content, user): 395 | global g_info 396 | 397 | base_uri = g_info['base_uri'] 398 | pass_ticket = g_info['pass_ticket'] 399 | BaseRequest = g_info['BaseRequest'] 400 | My = g_info['My'] 401 | 402 | wxuin = BaseRequest['Uin'] 403 | wxsid = BaseRequest['Sid'] 404 | skey = BaseRequest['Skey'] 405 | deviceId = BaseRequest['DeviceID'] 406 | 407 | url = base_uri + \ 408 | '/webwxsendmsg?pass_ticket=%s' % (pass_ticket) 409 | 410 | msgid = int(time.time()*10000000) 411 | msg = { 412 | 'ClientMsgId' : msgid , 413 | 'Content' : content, 414 | 'FromUserName' : My['UserName'] , 415 | 'LocalID' : msgid , 416 | 'ToUserName' : user, 417 | 'Type' : 1 418 | } 419 | params = { 420 | 'BaseRequest' : BaseRequest, 421 | 'Msg' : msg 422 | } 423 | data = json.dumps(params, ensure_ascii=False) 424 | # print (data) 425 | data = data.encode('utf-8') 426 | r = requests.post(url, data=data, cookies=g_info['cookies']) 427 | 428 | 429 | def sendMsg(): 430 | global g_info, g_queue 431 | 432 | MemberList = g_info['MemberList'] 433 | tuling_url = 'http://www.tuling123.com/openapi/api?key=' + apikey + '&info=' 434 | 435 | time.sleep(1) 436 | if LOG: 437 | print ('sendmsg queue: %s' % g_queue.qsize()) 438 | while g_queue.empty() is False: 439 | # lock.acquire() 440 | # try: 441 | # response = g_queue.popleft() 442 | # finally: 443 | # lock.release() 444 | response = g_queue.get() 445 | 446 | content = response['Content'] 447 | from_user = response['FromUserName'] 448 | AT = response['fromsomeone'] 449 | tuling_url = tuling_url + content 450 | try: 451 | data = requests.get(tuling_url) 452 | data = json.loads(data.text) 453 | text = data['text'] 454 | except: 455 | text = '网络异常。。。。。' 456 | webwxsendmsg(AT + text, from_user) 457 | time.sleep(1) 458 | 459 | if LOG: 460 | print 461 | print ('机器人收到回复:%s' % content) 462 | print ('机器人的回复: %s' % text) 463 | print 464 | 465 | 466 | 467 | 468 | def webwxbatchgetcontact(UserName): 469 | global g_info 470 | 471 | base_uri = g_info['base_uri'] 472 | pass_ticket = g_info['pass_ticket'] 473 | BaseRequest = g_info['BaseRequest'] 474 | 475 | url = base_uri + '/webwxbatchgetcontact?' 476 | 477 | 478 | List = [{ 479 | 'ChatRoomId' : '', 480 | 'UserName' : UserName 481 | }] 482 | 483 | post_params = { 484 | 'BaseRequest': BaseRequest , 485 | 'Count' : 1 , 486 | 'List' : List 487 | } 488 | url_params = { 489 | 'lang' : 'zh_CN' , 490 | 'type' : 'ex' , 491 | 'pass_ticket' : pass_ticket , 492 | 'r' : int(time.time()) 493 | } 494 | 495 | headers = { 496 | 'ContentType': 'application/json; charset=UTF-8' 497 | } 498 | r = requests.post(url, params=url_params, data=json.dumps(post_params), headers = headers, cookies=g_info['cookies']) 499 | 500 | r.encoding = 'utf-8' 501 | dic = json.loads(r.text) 502 | 503 | 504 | GroupMapUsers = {} 505 | ContactList = dic['ContactList'] 506 | for contact in ContactList: 507 | memberlist = contact['MemberList'] 508 | for member in memberlist: 509 | # 默认 @群名片,没有群名片就 @昵称 510 | nickname = member['NickName'] 511 | displayname = member['DisplayName'] 512 | AT = '' 513 | if displayname == '': 514 | # 有些人的昵称会有表情 会表示成 <span> 515 | # 需要 html.unescape() 转义一下 516 | AT = html.unescape(nickname) 517 | else: 518 | AT = html.unescape(displayname) 519 | GroupMapUsers[member['UserName']] = AT 520 | 521 | if DEBUG: 522 | print (member['NickName']) 523 | 524 | # 整群的成员列表消息记录 525 | g_info[UserName] = GroupMapUsers 526 | 527 | 528 | 529 | 530 | def getgroupinfo(): 531 | global g_info 532 | 533 | if 'Group_UserName_Req' not in g_info: 534 | return 535 | if g_info['Group_UserName_Req'] == '0': 536 | return 537 | 538 | Group_UserName = g_info['Group_UserName_Req'] 539 | webwxbatchgetcontact(Group_UserName) 540 | 541 | # 这个变量表示一次 获取群成员列表 请求。请求完毕置空 542 | g_info['Group_UserName_Req'] = '0' 543 | 544 | time.sleep(0.5) 545 | 546 | 547 | 548 | def heartBeatLoop(): 549 | while True: 550 | retcode, selector = syncCheck() 551 | if retcode != '0': 552 | print ('sync 失败。。。') 553 | if selector == '2': 554 | state, msg_list = webwxsync() 555 | getMsg(msg_list) 556 | getgroupinfo() 557 | 558 | time.sleep(1) 559 | 560 | 561 | 562 | def main(): 563 | global g_info 564 | 565 | if not getUUID(): 566 | print ('获取 uuid 失败') 567 | return 568 | print ('获取二维码图片中。。。') 569 | showQRImage() 570 | time.sleep(1) 571 | 572 | while waitForLogin() != '200': 573 | pass 574 | 575 | if not login(): 576 | print ('登陆失败') 577 | return 578 | 579 | if not webwxinit(): 580 | print ('初始化失败') 581 | return 582 | 583 | print ('登陆') 584 | 585 | print ('获取好友。。。。') 586 | webwxgetcontact() 587 | 588 | print ('开始心跳 噗咚噗通') 589 | t1 = threading.Thread(target=heartBeatLoop) 590 | t1.start() 591 | 592 | MemberCount = len(g_info['MemberList']) 593 | print ('这位同志啊,你有 %s 个好友' % MemberCount) 594 | 595 | try: 596 | while True: 597 | sendMsg() 598 | time.sleep(1.5) 599 | except KeyboardInterrupt: 600 | print ('bye bye ~') 601 | 602 | if __name__ == '__main__': 603 | main() --------------------------------------------------------------------------------