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()