├── .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()
--------------------------------------------------------------------------------