diff --git a/README.md b/README.md index ff8bf37..70a6410 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,372 @@ -# WeixinBot +# WeixinBot [![star this repo](http://github-svg-buttons.herokuapp.com/star.svg?user=Urinx&repo=WeixinBot&style=flat&background=1081C1)](http://github.com/Urinx/WeixinBot) [![fork this repo](http://github-svg-buttons.herokuapp.com/fork.svg?user=Urinx&repo=WeixinBot&style=flat&background=1081C1)](http://github.com/Urinx/WeixinBot/fork) ![python](https://img.shields.io/badge/python-2.7-ff69b4.svg) + 网页版微信API,包含终端版微信及微信机器人 + +### Demo +为了运行 `weixin.py` 示例脚本,你需要有安装 `qrcode` 包,你可以通过 `pip install qrcode` 来安装。 + +![1](screenshot/1.png) + +按照操作指示在手机微信上扫描二维码然后登录,你可以选择是否开启自动回复模式。 + +![2](screenshot/2.png) + +开启自动回复模式后,如果接收到的是文字消息就会自动回复,包括群消息。 + +![3](screenshot/3.png) + +现在,名片,链接,动画表情和地址位置消息都可以正常接收。 + +![4](screenshot/4.png) + +![5](screenshot/5.png) + +**目前支持的命令**: + +`->[昵称或ID]:[内容]` 给好友发送消息 + +`m->[昵称或ID]:[文件路径]` 给好友发送文件中的内容 + +![6](screenshot/6.png) + +`f->[昵称或ID]:[文件路径]` 给好友发送文件 + +`i->[昵称或ID]:[图片路径]` 给好友发送图片 + +`quit` 退出程序 + +![7](screenshot/7.png) + +注意,以上命令均不包含方括号。 + +### Web Weixin Pipeline + +``` + +--------------+ +---------------+ +---------------+ + | | | | | | + | Get UUID | | Get Contact | | Status Notify | + | | | | | | + +-------+------+ +-------^-------+ +-------^-------+ + | | | + | +-------+ +--------+ + | | | + +-------v------+ +-----+--+------+ +--------------+ + | | | | | | + | Get QRCode | | Weixin Init +------> Sync Check <----+ + | | | | | | | + +-------+------+ +-------^-------+ +-------+------+ | + | | | | + | | +-----------+ + | | | + +-------v------+ +-------+--------+ +-------v-------+ + | | Confirm Login | | | | ++------> Login +---------------> New Login Page | | Weixin Sync | +| | | | | | | +| +------+-------+ +----------------+ +---------------+ +| | +|QRCode Scaned| ++-------------+ +``` + +### Web Weixin API + +#### 登录 + +| API | 获取 UUID | +| --- | --------- | +| url | https://login.weixin.qq.com/jslogin | +| method | POST | +| data | URL Encode | +| params | **appid**: `应用ID`
**fun**: new `应用类型`
**lang**: zh\_CN `语言`
**_**: `时间戳` | + +返回数据(String): +``` +window.QRLogin.code = 200; window.QRLogin.uuid = "xxx" +``` + +| API | 生成二维码 | +| --- | --------- | +| url | https://login.weixin.qq.com/l/ `uuid` | + +| API | 二维码扫描登录 | +| --- | --------- | +| url | https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login | +| method | GET | +| params | **tip**: 1 `未扫描` 0 `已扫描`
**uuid**: xxx
**_**: `时间戳` | + +返回数据(String): +``` +window.code=xxx; + +xxx: + 408 登陆超时 + 201 扫描成功 + 200 确认登录 + +当返回200时,还会有 +window.redirect_uri="https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=xxx&uuid=xxx&lang=xxx&scan=xxx"; +``` + +| API | webwxnewloginpage | +| --- | --------- | +| url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage | +| method | GET | +| params | **ticket**: xxx
**uuid**: xxx
**lang**: zh_CN `语言`
**scan**: xxx
**fun**: new | + +返回数据(XML): +``` + + 0 + OK + xxx + xxx + xxx + xxx + 1 + +``` + +#### 微信初始化 + +| API | webwxinit | +| --- | --------- | +| url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?pass_ticket=xxx&skey=xxx&r=xxx | +| method | POST | +| data | JSON | +| header | ContentType: application/json; charset=UTF-8 | +| params | {
     BaseRequest: {
         Uin: xxx,
         Sid: xxx,
         Skey: xxx,
         DeviceID: xxx,
     }
} | + +返回数据(JSON): +``` +{ + "BaseResponse": { + "Ret": 0, + "ErrMsg": "" + }, + "Count": 11, + "ContactList": [...], + "SyncKey": { + "Count": 4, + "List": [ + { + "Key": 1, + "Val": 635705559 + }, + ... + ] + }, + "User": { + "Uin": xxx, + "UserName": xxx, + "NickName": xxx, + "HeadImgUrl": xxx, + "RemarkName": "", + "PYInitial": "", + "PYQuanPin": "", + "RemarkPYInitial": "", + "RemarkPYQuanPin": "", + "HideInputBarFlag": 0, + "StarFriend": 0, + "Sex": 1, + "Signature": "Apt-get install B", + "AppAccountFlag": 0, + "VerifyFlag": 0, + "ContactFlag": 0, + "WebWxPluginSwitch": 0, + "HeadImgFlag": 1, + "SnsFlag": 17 + }, + "ChatSet": xxx, + "SKey": xxx, + "ClientVersion": 369297683, + "SystemTime": 1453124908, + "GrayScale": 1, + "InviteStartCount": 40, + "MPSubscribeMsgCount": 2, + "MPSubscribeMsgList": [...], + "ClickReportInterval": 600000 +} +``` + +| API | webwxstatusnotify | +| --- | --------- | +| url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxstatusnotify?lang=zh_CN&pass_ticket=xxx | +| method | POST | +| data | JSON | +| header | ContentType: application/json; charset=UTF-8 | +| params | {
     BaseRequest: { Uin: xxx, Sid: xxx, Skey: xxx, DeviceID: xxx },
     Code: 3,
     FromUserName: `自己ID`,
     ToUserName: `自己ID`,
     ClientMsgId: `时间戳`
} | + +返回数据(JSON): +``` +{ + "BaseResponse": { + "Ret": 0, + "ErrMsg": "" + }, + ... +} +``` + +#### 获取联系人信息 + +| API | webwxgetcontact | +| --- | --------- | +| url | https://wx.qq.com/cgi-bin/mmwebwx-bin//webwxgetcontact?pass_ticket=xxx&skey=xxx&r=xxx | +| method | POST | +| data | JSON | +| header | ContentType: application/json; charset=UTF-8 | + +返回数据(JSON): +``` +{ + "BaseResponse": { + "Ret": 0, + "ErrMsg": "" + }, + "MemberCount": 334, + "MemberList": [ + { + "Uin": 0, + "UserName": xxx, + "NickName": "Urinx", + "HeadImgUrl": xxx, + "ContactFlag": 3, + "MemberCount": 0, + "MemberList": [], + "RemarkName": "", + "HideInputBarFlag": 0, + "Sex": 0, + "Signature": "你好,我们是地球三体组织。在这里,你将感受到不一样的思维模式,以及颠覆常规的世界观。而我们的目标,就是以三体人的智慧,引领人类未来科学技术500年。", + "VerifyFlag": 8, + "OwnerUin": 0, + "PYInitial": "URINX", + "PYQuanPin": "Urinx", + "RemarkPYInitial": "", + "RemarkPYQuanPin": "", + "StarFriend": 0, + "AppAccountFlag": 0, + "Statues": 0, + "AttrStatus": 0, + "Province": "", + "City": "", + "Alias": "Urinxs", + "SnsFlag": 0, + "UniFriend": 0, + "DisplayName": "", + "ChatRoomId": 0, + "KeyWord": "gh_", + "EncryChatRoomId": "" + }, + ... + ], + "Seq": 0 +} +``` + +| API | webwxbatchgetcontact | +| --- | --------- | +| url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxbatchgetcontact?type=ex&r=xxx&pass_ticket=xxx | +| method | POST | +| data | JSON | +| header | ContentType: application/json; charset=UTF-8 | +| params | {
     BaseRequest: { Uin: xxx, Sid: xxx, Skey: xxx, DeviceID: xxx },
     Count: `群数量`,
     List: [
         { UserName: `群ID`, EncryChatRoomId: "" },
         ...
     ],
} | + +返回数据(JSON)同上 + +#### 同步刷新 + +| API | synccheck | +| --- | --------- | +| url | https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck | +| method | GET | +| data | URL Encode | +| params | **r**: `时间戳`
**sid**: xxx
**uin**: xxx
**skey**: xxx
**deviceid**: xxx
**synckey**: xxx
**_**: `时间戳` | + +返回数据(String): +``` +window.synccheck={retcode:"xxx",selector:"xxx"} + +retcode: + 0 正常 + 1100 登出微信 +selector: + 0 正常 + 2 新的消息 + 7 进入/离开聊天界面 +``` + +| API | webwxsync | +| --- | --------- | +| url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=xxx&skey=xxx&pass_ticket=xxx | +| method | POST | +| data | JSON | +| header | ContentType: application/json; charset=UTF-8 | +| params | {
     BaseRequest: { Uin: xxx, Sid: xxx, Skey: xxx, DeviceID: xxx },
     SyncKey: xxx,
     rr: `时间戳取反`
} | + +返回数据(JSON): +``` +{ + 'BaseResponse': {'ErrMsg': '', 'Ret': 0}, + 'SyncKey': { + 'Count': 7, + 'List': [ + {'Val': 636214192, 'Key': 1}, + ... + ] + }, + 'ContinueFlag': 0, + 'AddMsgCount': 1, + 'AddMsgList': [ + { + 'FromUserName': '', + 'PlayLength': 0, + 'RecommendInfo': {...}, + 'Content': "", + 'StatusNotifyUserName': '', + 'StatusNotifyCode': 5, + 'Status': 3, + 'VoiceLength': 0, + 'ToUserName': '', + 'ForwardFlag': 0, + 'AppMsgType': 0, + 'AppInfo': {'Type': 0, 'AppID': ''}, + 'Url': '', + 'ImgStatus': 1, + 'MsgType': 51, + 'ImgHeight': 0, + 'MediaId': '', + 'FileName': '', + 'FileSize': '', + ... + }, + ... + ], + 'ModChatRoomMemberCount': 0, + 'ModContactList': [], + 'DelContactList': [], + 'ModChatRoomMemberList': [], + 'DelContactCount': 0, + ... +} +``` + +#### 消息接口 + +| API | webwxsendmsg | +| --- | --------- | +| url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?pass_ticket=xxx | +| method | POST | +| data | JSON | +| header | ContentType: application/json; charset=UTF-8 | +| params | {
     BaseRequest: { Uin: xxx, Sid: xxx, Skey: xxx, DeviceID: xxx },
     Msg: {
         Type: 1 `文字消息`,
         Content: `要发送的消息`,
         FromUserName: `自己ID`,
         ToUserName: `好友ID`,
         LocalID: `与clientMsgId相同`,
         ClientMsgId: `时间戳左移4位随后补上4位随机数`
     }
} | + +返回数据(JSON): +``` +{ + "BaseResponse": { + "Ret": 0, + "ErrMsg": "" + }, + ... +} +``` diff --git a/screenshot/1.png b/screenshot/1.png new file mode 100644 index 0000000..bcde745 Binary files /dev/null and b/screenshot/1.png differ diff --git a/screenshot/2.png b/screenshot/2.png new file mode 100644 index 0000000..a15e0c4 Binary files /dev/null and b/screenshot/2.png differ diff --git a/screenshot/3.png b/screenshot/3.png new file mode 100644 index 0000000..6974e66 Binary files /dev/null and b/screenshot/3.png differ diff --git a/screenshot/4.png b/screenshot/4.png new file mode 100644 index 0000000..835565b Binary files /dev/null and b/screenshot/4.png differ diff --git a/screenshot/5.png b/screenshot/5.png new file mode 100644 index 0000000..2421c9f Binary files /dev/null and b/screenshot/5.png differ diff --git a/screenshot/6.png b/screenshot/6.png new file mode 100644 index 0000000..7703178 Binary files /dev/null and b/screenshot/6.png differ diff --git a/screenshot/7.png b/screenshot/7.png new file mode 100644 index 0000000..d8dce41 Binary files /dev/null and b/screenshot/7.png differ diff --git a/weixin.py b/weixin.py new file mode 100755 index 0000000..6b8d7a9 --- /dev/null +++ b/weixin.py @@ -0,0 +1,460 @@ +#!/usr/bin/env python +# coding: utf-8 +import qrcode +import urllib, urllib2 +import cookielib +import requests +import xml.dom.minidom +import json +import time, re, sys, os, random +import multiprocessing + +def catchKeyboardInterrupt(fn): + def wrapper(*args): + try: + return fn(*args) + except KeyboardInterrupt: + print '\n[*] 强制退出程序' + return wrapper + +class WebWeixin(object): + def __str__(self): + description = \ + "=========================\n" + \ + "[#] Web Weixin\n" + \ + "[#] Debug Mode: " + str(self.DEBUG) + "\n" + \ + "[#] Uuid: " + self.uuid + "\n" + \ + "[#] Uin: " + str(self.uin) + "\n" + \ + "[#] Sid: " + self.sid + "\n" + \ + "[#] Skey: " + self.skey + "\n" + \ + "[#] DeviceId: " + self.deviceId + "\n" + \ + "[#] PassTicket: " + self.pass_ticket + "\n" + \ + "=========================" + return description + + def __init__(self): + self.DEBUG = False + self.uuid = '' + self.base_uri = '' + self.redirect_uri= '' + self.uin = '' + self.sid = '' + self.skey = '' + self.pass_ticket = '' + self.deviceId = 'e' + repr(random.random())[2:17] + self.BaseRequest = {} + self.synckey = '' + self.SyncKey = [] + self.User = [] + self.MemberList = [] + self.ContactList = [] + self.GroupList = [] + self.autoReplyMode = False + + opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookielib.CookieJar())) + urllib2.install_opener(opener) + + def getUUID(self): + url = 'https://login.weixin.qq.com/jslogin' + params = { + 'appid': 'wx782c26e4c19acffb', + 'fun': 'new', + 'lang': 'zh_CN', + '_': int(time.time()), + } + data = self._post(url, params, False) + regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)"' + pm = re.search(regx, data) + if pm: + code = pm.group(1) + self.uuid = pm.group(2) + return code == '200' + return False + + def genQRCode(self): + self._str2qr('https://login.weixin.qq.com/l/' + self.uuid) + + def waitForLogin(self, tip = 1): + time.sleep(tip) + url = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s' % (tip, self.uuid, int(time.time())) + data = self._get(url) + pm = re.search(r'window.code=(\d+);', data) + code = pm.group(1) + + if code == '201': return True + elif code == '200': + pm = re.search(r'window.redirect_uri="(\S+?)";', data) + r_uri = pm.group(1) + '&fun=new' + self.redirect_uri = r_uri + self.base_uri = r_uri[:r_uri.rfind('/')] + return True + elif code == '408': + self._echo('[登陆超时] ') + else: + self._echo('[登陆异常] ') + return False + + def login(self): + data = self._get(self.redirect_uri) + doc = xml.dom.minidom.parseString(data) + root = doc.documentElement + + for node in root.childNodes: + if node.nodeName == 'skey': + self.skey = node.childNodes[0].data + elif node.nodeName == 'wxsid': + self.sid = node.childNodes[0].data + elif node.nodeName == 'wxuin': + self.uin = node.childNodes[0].data + elif node.nodeName == 'pass_ticket': + self.pass_ticket = node.childNodes[0].data + + if '' in (self.skey, self.sid, self.uin, self.pass_ticket): + return False + + self.BaseRequest = { + 'Uin': int(self.uin), + 'Sid': self.sid, + 'Skey': self.skey, + 'DeviceID': self.deviceId, + } + return True + + def webwxinit(self): + url = self.base_uri + '/webwxinit?pass_ticket=%s&skey=%s&r=%s' % (self.pass_ticket, self.skey, int(time.time())) + params = { + 'BaseRequest': self.BaseRequest + } + dic = self._post(url, params) + self.SyncKey = dic['SyncKey'] + self.User = dic['User'] + # synckey for synccheck + self.synckey = '|'.join([ str(keyVal['Key']) + '_' + str(keyVal['Val']) for keyVal in self.SyncKey['List'] ]) + + return dic['BaseResponse']['Ret'] == 0 + + def webwxstatusnotify(self): + url = self.base_uri + '/webwxstatusnotify?lang=zh_CN&pass_ticket=%s' % (self.pass_ticket) + params = { + 'BaseRequest': self.BaseRequest, + "Code": 3, + "FromUserName": self.User['UserName'], + "ToUserName": self.User['UserName'], + "ClientMsgId": int(time.time()) + } + dic = self._post(url, params) + + return dic['BaseResponse']['Ret'] == 0 + + def webwxgetcontact(self): + url = self.base_uri + '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' % (self.pass_ticket, self.skey, int(time.time())) + dic = self._post(url, {}) + self.MemberList = dic['MemberList'] + + ContactList = self.MemberList[:] + SpecialUsers = ['newsapp', 'fmessage', 'filehelper', 'weibo', 'qqmail', 'fmessage', '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', 'wxid_novlwrv3lqwv11', 'gh_22b87fa7cb3c', 'wxitil', 'userexperience_alarm', 'notification_messages'] + for i in xrange(len(ContactList) - 1, -1, -1): + Contact = ContactList[i] + if Contact['VerifyFlag'] & 8 != 0: # 公众号/服务号 + ContactList.remove(Contact) + elif Contact['UserName'] in SpecialUsers: # 特殊账号 + ContactList.remove(Contact) + elif Contact['UserName'].find('@@') != -1: # 群聊 + self.GroupList.append(Contact) + ContactList.remove(Contact) + elif Contact['UserName'] == self.User['UserName']: # 自己 + ContactList.remove(Contact) + self.ContactList = ContactList + + return True + + def webwxbatchgetcontact(self): + url = self.base_uri + '/webwxbatchgetcontact?type=ex&r=%s&pass_ticket=%s' % (int(time.time()), self.pass_ticket) + params = { + 'BaseRequest': self.BaseRequest, + "Count": len(self.GroupList), + "List": [ {"UserName": g['UserName'], "EncryChatRoomId":""} for g in self.GroupList ] + } + dic = self._post(url, params) + # blabla ... + return True + + def synccheck(self): + params = { + 'r': int(time.time()), + 'sid': self.sid, + 'uin': self.uin, + 'skey': self.skey, + 'deviceid': self.deviceId, + 'synckey': self.synckey, + '_': int(time.time()), + } + url = 'https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?' + urllib.urlencode(params) + data = self._get(url) + pm = re.search(r'window.synccheck={retcode:"(\d+)",selector:"(\d+)"}', data) + retcode = pm.group(1) + selector = pm.group(2) + return [retcode, selector] + + def webwxsync(self): + url = self.base_uri + '/webwxsync?sid=%s&skey=%s&pass_ticket=%s' % (self.sid, self.skey, self.pass_ticket) + params = { + 'BaseRequest': self.BaseRequest, + 'SyncKey': self.SyncKey, + 'rr': ~int(time.time()) + } + dic = self._post(url, params) + if self.DEBUG: print dic + + if dic['BaseResponse']['Ret'] == 0: + self.SyncKey = dic['SyncKey'] + self.synckey = '|'.join([ str(keyVal['Key']) + '_' + str(keyVal['Val']) for keyVal in self.SyncKey['List'] ]) + return dic + + def webwxsendmsg(self, word, to = 'filehelper'): + url = self.base_uri + '/webwxsendmsg?pass_ticket=%s' % (self.pass_ticket) + clientMsgId = str(int(time.time()*1000)) + str(random.random())[:5].replace('.','') + params = { + 'BaseRequest': self.BaseRequest, + 'Msg': { + "Type": 1, + "Content": self._transcoding(word), + "FromUserName": self.User['UserName'], + "ToUserName": to, + "LocalID": clientMsgId, + "ClientMsgId": clientMsgId + } + } + headers = {'content-type': 'application/json; charset=UTF-8'} + data = json.dumps(params, ensure_ascii=False).encode('utf8') + r = requests.post(url, data = data, headers = headers) + dic = r.json() + return dic['BaseResponse']['Ret'] == 0 + + def getUserRemarkName(self, id): + name = u'这个人物名字未知' + for member in self.MemberList: + if member['UserName'] == id: + name = member['RemarkName'] if member['RemarkName'] else member['NickName'] + return name.encode('utf-8') + + def getUSerID(self, name): + name = name.decode('utf-8') + for member in self.MemberList: + if name == member['RemarkName'] or name == member['NickName']: + return member['UserName'] + return None + + def handleMsg(self, r): + for msg in r['AddMsgList']: + print '[*] 你有新的消息,请注意查收' + msgType = msg['MsgType'] + name = self.getUserRemarkName(msg['FromUserName']) + content = msg['Content'].encode('utf-8') + if msgType == 51: + print '[*] 成功截获微信初始化消息' + elif msgType == 1: + if msg['ToUserName'] == 'filehelper': + print name+' -> 文件传输助手: '+content.replace('
','\n') + elif msg['FromUserName'] == self.User['UserName']: + pass + elif msg['ToUserName'][:2] == '@@': + [people, content] = content.split(':
') + print '|'+name+'| '+people+':\n'+content.replace('
','\n') + else: + print name+': '+content + if self.autoReplyMode: + ans = self._xiaodoubi(content)+'\n[微信机器人自动回复]' + if self.webwxsendmsg(ans, msg['FromUserName']): + print '自动回复: '+ans + else: + print '自动回复失败' + elif msgType == 3: + print name+' 给你发送了一张图片,请在手机上查看' + elif msgType == 34: + print name+' 给你发了一段语音,请在手机上查看' + elif msgType == 42: + print name+' 给你发送了一张名片:' + print '=========================' + print '= 昵称: '+self._searchContent('nickname', content) + print '= 微信号: '+self._searchContent('alias', content) + print '= 地区: '+self._searchContent('province', content)+' '+self._searchContent('city', content) + print '= 性别: '+( '男' if self._searchContent('sex', content)=='1' else '女' ) + print '=========================' + elif msgType == 47: + url = self._searchContent('cdnurl', content) + print name+' 给你发了一个动画表情,点击下面链接查看:' + print url + os.system('open %s' % url) + elif msgType == 49: + content = content.replace('<','<').replace('>','>') + print name+' 给你分享了一个链接:' + print '=========================' + print '= 标题: '+self._searchContent('title', content, 'xml') + print '= 描述: '+self._searchContent('des', content, 'xml') + print '= 链接: '+self._searchContent('url', content, 'xml') + print '=========================' + elif msgType == 62: + print name+' 给你发了一个小视频,请在手机上查看' + elif msgType == 10002: + print name+' 撤回消息' + else: + print '[*] 该消息类型为: %d,可能是表情,图片或链接' % msg['MsgType'] + print msg + + def listenMsgMode(self): + print '[*] 进入消息监听模式 ... 成功' + playWeChat = 0 + while True: + [retcode, selector] = self.synccheck() + if self.DEBUG: print 'retcode: %s, selector: %s' % (retcode, selector) + if retcode == '1100': + print '[*] 你在手机上登出了微信,债见' + break + elif retcode == '0': + if selector == '2': + r = self.webwxsync() + if r is not None: self.handleMsg(r) + elif selector == '7': + playWeChat += 1 + print '[*] 你在手机上玩微信被我发现了 %d 次' % playWeChat + r = self.webwxsync() + elif selector == '0': + time.sleep(1) + + def sendMsg(self, name, word, isfile = False): + id = self.getUSerID(name) + if id: + if isfile: + with open(word, 'r') as f: + for line in f.readlines(): + line = line.replace('\n','') + self._echo('-> '+name+': '+line) + if self.webwxsendmsg(line, id): + print ' [成功]' + else: + print ' [失败]' + time.sleep(1) + else: + if self.webwxsendmsg(word, id): + print '[*] 消息发送成功' + else: + print '[*] 消息发送失败' + else: + print '[*] 此用户不存在' + + @catchKeyboardInterrupt + def start(self): + print '[*] 微信网页版 ... 开动' + self._run('[*] 正在获取 uuid ... ', self.getUUID) + print '[*] 正在获取二维码 ... 成功'; self.genQRCode() + self._run('[*] 请使用微信扫描二维码以登录 ... ', self.waitForLogin) + self._run('[*] 请在手机上点击确认以登录 ... ', self.waitForLogin, 0) + self._run('[*] 正在登录 ... ', self.login) + self._run('[*] 微信初始化 ... ', self.webwxinit) + self._run('[*] 开启状态通知 ... ', self.webwxstatusnotify) + self._run('[*] 获取联系人 ... ', self.webwxgetcontact) + print '[*] 共有 %d 位联系人' % len(self.ContactList) + if self.DEBUG: print self + + if raw_input('[*] 是否开启自动回复模式(y/n): ') == 'y': + self.autoReplyMode = True + print '[*] 自动回复模式 ... 开启' + else: + print '[*] 自动回复模式 ... 关闭' + + listenProcess = multiprocessing.Process(target=self.listenMsgMode) + listenProcess.start() + + while True: + text = raw_input('') + if text == 'quit': + listenProcess.terminate() + exit('[*] 退出微信') + elif text[:2] == '->': + [name, word] = text[2:].split(':') + self.sendMsg(name, word) + elif text[:3] == 'm->': + [name, file] = text[3:].split(':') + self.sendMsg(name, file, True) + elif text[:3] == 'f->': + print '发送文件' + elif text[:3] == 'i->': + print '发送图片' + + def _run(self, str, func, *args): + self._echo(str) + if func(*args): print '成功' + else: exit('失败\n[*] 退出程序') + + def _echo(self, str): + sys.stdout.write(str) + sys.stdout.flush() + + def _printQR(self, mat): + for i in mat: + BLACK = '\033[40m \033[0m' + WHITE = '\033[47m \033[0m' + print ''.join([BLACK if j else WHITE for j in i]) + + def _str2qr(self, str): + qr = qrcode.QRCode() + qr.border = 1 + qr.add_data(str) + mat = qr.get_matrix() + self._printQR(mat) # qr.print_tty() or qr.print_ascii() + + def _transcoding(self, data): + if not data: return data + result = None + if type(data) == unicode: + result = data + elif type(data) == str: + result = data.decode('utf-8') + return result + + def _get(self, url): + request = urllib2.Request(url = url) + response = urllib2.urlopen(request) + data = response.read() + return data + + def _post(self, url, params, jsonfmt = True): + if jsonfmt: + request = urllib2.Request(url = url, data = json.dumps(params)) + request.add_header('ContentType', 'application/json; charset=UTF-8') + else: + request = urllib2.Request(url = url, data = urllib.urlencode(params)) + response = urllib2.urlopen(request) + data = response.read() + if jsonfmt: return json.loads(data) + return data + + def _xiaodoubi(self, word): + url = 'http://www.xiaodoubi.com/bot/chat.php' + try: + r = requests.post(url, data = {'chat': word}) + return r.content + except: + return "让我一个人静静 T_T..." + + def _simsimi(self, word): + key = '' + url = 'http://sandbox.api.simsimi.com/request.p?key=%s&lc=ch&ft=0.0&text=%s' % (key, word) + r = requests.get(url) + ans = r.json() + if ans['result'] == '100': return ans['response'] + else: return '你在说什么,风太大听不清列' + + def _searchContent(self, key, content, fmat = 'attr'): + if fmat == 'attr': + pm = re.search(key+'\s?=\s?"([^"<]+)"', content) + if pm: return pm.group(1) + elif fmat == 'xml': + pm=re.search('<{0}>([^<]+)'.format(key),content) + if pm: return pm.group(1) + return '未知' + +if __name__ == '__main__': + webwx = WebWeixin() + webwx.start()