|
@@ -3,29 +3,33 @@
|
|
|
|
|
|
import os
|
|
|
import sys
|
|
|
+import traceback
|
|
|
import webbrowser
|
|
|
import pyqrcode
|
|
|
import requests
|
|
|
+import mimetypes
|
|
|
import json
|
|
|
import xml.dom.minidom
|
|
|
import urllib
|
|
|
import time
|
|
|
import re
|
|
|
import random
|
|
|
-import mimetypes
|
|
|
+from traceback import format_exc
|
|
|
from requests.exceptions import ConnectionError, ReadTimeout
|
|
|
+from Queue import Queue
|
|
|
import HTMLParser
|
|
|
+import threading
|
|
|
|
|
|
UNKONWN = 'unkonwn'
|
|
|
SUCCESS = '200'
|
|
|
-SCANED = '201'
|
|
|
+SCANED = '201'
|
|
|
TIMEOUT = '408'
|
|
|
|
|
|
|
|
|
-def show_image(file):
|
|
|
+def show_image(file_path):
|
|
|
"""
|
|
|
跨平台显示图片文件
|
|
|
- :param file: 图片文件路径
|
|
|
+ :param file_path: 图片文件路径
|
|
|
"""
|
|
|
if sys.version_info >= (3, 3):
|
|
|
from shlex import quote
|
|
@@ -33,10 +37,24 @@ def show_image(file):
|
|
|
from pipes import quote
|
|
|
|
|
|
if sys.platform == "darwin":
|
|
|
- command = "open -a /Applications/Preview.app %s&" % quote(file)
|
|
|
+ command = "open -a /Applications/Preview.app %s&" % quote(file_path)
|
|
|
os.system(command)
|
|
|
else:
|
|
|
- webbrowser.open(file)
|
|
|
+ webbrowser.open(os.path.join(os.getcwd(),'temp',file_path))
|
|
|
+
|
|
|
+
|
|
|
+class SafeSession(requests.Session):
|
|
|
+ def request(self, method, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None,
|
|
|
+ timeout=None, allow_redirects=True, proxies=None, hooks=None, stream=None, verify=None, cert=None,
|
|
|
+ json=None):
|
|
|
+ for i in range(3):
|
|
|
+ try:
|
|
|
+ return super(SafeSession, self).request(method, url, params, data, headers, cookies, files, auth,
|
|
|
+ timeout,
|
|
|
+ allow_redirects, proxies, hooks, stream, verify, cert, json)
|
|
|
+ except Exception as e:
|
|
|
+ print e.message, traceback.format_exc()
|
|
|
+ continue
|
|
|
|
|
|
|
|
|
class WXBot:
|
|
@@ -44,6 +62,7 @@ class WXBot:
|
|
|
|
|
|
def __init__(self):
|
|
|
self.DEBUG = False
|
|
|
+ self.SCHEDULE_INTV = 5
|
|
|
self.uuid = ''
|
|
|
self.base_uri = ''
|
|
|
self.redirect_uri = ''
|
|
@@ -57,7 +76,12 @@ class WXBot:
|
|
|
self.sync_key = []
|
|
|
self.sync_host = ''
|
|
|
|
|
|
- self.session = requests.Session()
|
|
|
+ #文件缓存目录
|
|
|
+ self.temp_pwd = os.path.join(os.getcwd(),'temp')
|
|
|
+ if os.path.exists(self.temp_pwd) == False:
|
|
|
+ os.makedirs(self.temp_pwd)
|
|
|
+
|
|
|
+ self.session = SafeSession()
|
|
|
self.session.headers.update({'User-Agent': 'Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5'})
|
|
|
self.conf = {'qr': 'png'}
|
|
|
|
|
@@ -78,7 +102,9 @@ class WXBot:
|
|
|
self.special_list = [] # 特殊账号列表
|
|
|
self.encry_chat_room_id_list = [] # 存储群聊的EncryChatRoomId,获取群内成员头像时需要用到
|
|
|
|
|
|
- self.file_index = 0 # 发送文件消息时用到的的文件序号
|
|
|
+ self.file_index = 0
|
|
|
+
|
|
|
+ self.msg_queue = Queue()
|
|
|
|
|
|
@staticmethod
|
|
|
def to_unicode(string, encoding='utf-8'):
|
|
@@ -102,7 +128,7 @@ class WXBot:
|
|
|
r = self.session.post(url, data='{}')
|
|
|
r.encoding = 'utf-8'
|
|
|
if self.DEBUG:
|
|
|
- with open('contacts.json', 'w') as f:
|
|
|
+ with open(os.path.join(self.temp_pwd,'contacts.json'), 'w') as f:
|
|
|
f.write(r.text.encode('utf-8'))
|
|
|
dic = json.loads(r.text)
|
|
|
self.member_list = dic['MemberList']
|
|
@@ -133,7 +159,6 @@ class WXBot:
|
|
|
self.account_info['normal_member'][contact['UserName']] = {'type': 'group', 'info': contact}
|
|
|
elif contact['UserName'] == self.my_account['UserName']: # 自己
|
|
|
self.account_info['normal_member'][contact['UserName']] = {'type': 'self', 'info': contact}
|
|
|
- pass
|
|
|
else:
|
|
|
self.contact_list.append(contact)
|
|
|
self.account_info['normal_member'][contact['UserName']] = {'type': 'contact', 'info': contact}
|
|
@@ -147,19 +172,19 @@ class WXBot:
|
|
|
{'type': 'group_member', 'info': member, 'group': group}
|
|
|
|
|
|
if self.DEBUG:
|
|
|
- with open('contact_list.json', 'w') as f:
|
|
|
+ with open(os.path.join(self.temp_pwd,'contact_list.json'), 'w') as f:
|
|
|
f.write(json.dumps(self.contact_list))
|
|
|
- with open('special_list.json', 'w') as f:
|
|
|
+ with open(os.path.join(self.temp_pwd,'special_list.json'), 'w') as f:
|
|
|
f.write(json.dumps(self.special_list))
|
|
|
- with open('group_list.json', 'w') as f:
|
|
|
+ with open(os.path.join(self.temp_pwd,'group_list.json'), 'w') as f:
|
|
|
f.write(json.dumps(self.group_list))
|
|
|
- with open('public_list.json', 'w') as f:
|
|
|
+ with open(os.path.join(self.temp_pwd,'public_list.json'), 'w') as f:
|
|
|
f.write(json.dumps(self.public_list))
|
|
|
- with open('member_list.json', 'w') as f:
|
|
|
+ with open(os.path.join(self.temp_pwd,'member_list.json'), 'w') as f:
|
|
|
f.write(json.dumps(self.member_list))
|
|
|
- with open('group_users.json', 'w') as f:
|
|
|
+ with open(os.path.join(self.temp_pwd,'group_users.json'), 'w') as f:
|
|
|
f.write(json.dumps(self.group_members))
|
|
|
- with open('account_info.json', 'w') as f:
|
|
|
+ with open(os.path.join(self.temp_pwd,'account_info.json'), 'w') as f:
|
|
|
f.write(json.dumps(self.account_info))
|
|
|
return True
|
|
|
|
|
@@ -207,24 +232,10 @@ class WXBot:
|
|
|
return None
|
|
|
|
|
|
def get_contact_info(self, uid):
|
|
|
- if uid in self.account_info['normal_member']:
|
|
|
- return self.account_info['normal_member'][uid]
|
|
|
- else:
|
|
|
- return None
|
|
|
+ return self.account_info['normal_member'].get(uid)
|
|
|
|
|
|
def get_group_member_info(self, uid):
|
|
|
- if uid in self.account_info['group_member']:
|
|
|
- return self.account_info['group_member'][uid]
|
|
|
- else:
|
|
|
- return None
|
|
|
-
|
|
|
- def get_group_member_info(self, uid, gid):
|
|
|
- if gid not in self.group_members:
|
|
|
- return None
|
|
|
- for member in self.group_members[gid]:
|
|
|
- if member['UserName'] == uid:
|
|
|
- return {'type': 'group_member', 'info': member}
|
|
|
- return None
|
|
|
+ return self.account_info['group_member'].get(uid)
|
|
|
|
|
|
def get_contact_name(self, uid):
|
|
|
info = self.get_contact_info(uid)
|
|
@@ -243,40 +254,6 @@ class WXBot:
|
|
|
else:
|
|
|
return name
|
|
|
|
|
|
- def get_group_member_name(self, uid):
|
|
|
- info = self.get_group_member_info(uid)
|
|
|
- if info is None:
|
|
|
- return None
|
|
|
- info = info['info']
|
|
|
- name = {}
|
|
|
- if 'RemarkName' in info and info['RemarkName']:
|
|
|
- name['remark_name'] = info['RemarkName']
|
|
|
- if 'NickName' in info and info['NickName']:
|
|
|
- name['nickname'] = info['NickName']
|
|
|
- if 'DisplayName' in info and info['DisplayName']:
|
|
|
- name['display_name'] = info['DisplayName']
|
|
|
- if len(name) == 0:
|
|
|
- return None
|
|
|
- else:
|
|
|
- return name
|
|
|
-
|
|
|
- def get_group_member_name(self, uid, gid):
|
|
|
- info = self.get_group_member_info(uid, gid)
|
|
|
- if info is None:
|
|
|
- return None
|
|
|
- info = info['info']
|
|
|
- name = {}
|
|
|
- if 'RemarkName' in info and info['RemarkName']:
|
|
|
- name['remark_name'] = info['RemarkName']
|
|
|
- if 'NickName' in info and info['NickName']:
|
|
|
- name['nickname'] = info['NickName']
|
|
|
- if 'DisplayName' in info and info['DisplayName']:
|
|
|
- name['display_name'] = info['DisplayName']
|
|
|
- if len(name) == 0:
|
|
|
- return None
|
|
|
- else:
|
|
|
- return name
|
|
|
-
|
|
|
@staticmethod
|
|
|
def get_contact_prefer_name(name):
|
|
|
if name is None:
|
|
@@ -364,7 +341,7 @@ class WXBot:
|
|
|
str_msg = ''
|
|
|
infos = []
|
|
|
if len(segs) > 1:
|
|
|
- for i in range(0, len(segs)-1):
|
|
|
+ for i in range(0, len(segs) - 1):
|
|
|
segs[i] += u'\u2005'
|
|
|
pm = re.search(u'@.*\u2005', segs[i]).group()
|
|
|
if pm:
|
|
@@ -424,7 +401,7 @@ class WXBot:
|
|
|
uid = uid[:-1]
|
|
|
name = self.get_contact_prefer_name(self.get_contact_name(uid))
|
|
|
if not name:
|
|
|
- name = self.get_group_member_prefer_name(self.get_group_member_name(uid, msg['FromUserName']))
|
|
|
+ name = self.get_group_member_prefer_name(self.get_group_member_name(msg['FromUserName'], uid))
|
|
|
if not name:
|
|
|
name = 'unknown'
|
|
|
msg_content['user'] = {'id': uid, 'name': name}
|
|
@@ -464,15 +441,22 @@ class WXBot:
|
|
|
elif mtype == 3:
|
|
|
msg_content['type'] = 3
|
|
|
msg_content['data'] = self.get_msg_img_url(msg_id)
|
|
|
+ msg_content['img'] = self.session.get(msg_content['data']).content.encode('hex')
|
|
|
if self.DEBUG:
|
|
|
image = self.get_msg_img(msg_id)
|
|
|
print ' %s[Image] %s' % (msg_prefix, image)
|
|
|
elif mtype == 34:
|
|
|
msg_content['type'] = 4
|
|
|
msg_content['data'] = self.get_voice_url(msg_id)
|
|
|
+ msg_content['voice'] = self.session.get(msg_content['data']).content.encode('hex')
|
|
|
if self.DEBUG:
|
|
|
voice = self.get_voice(msg_id)
|
|
|
print ' %s[Voice] %s' % (msg_prefix, voice)
|
|
|
+ elif mtype == 37:
|
|
|
+ msg_content['type'] = 37
|
|
|
+ msg_content['data'] = msg['RecommendInfo']
|
|
|
+ if self.DEBUG:
|
|
|
+ print ' %s[useradd] %s' % (msg_prefix,msg['RecommendInfo']['NickName'])
|
|
|
elif mtype == 42:
|
|
|
msg_content['type'] = 5
|
|
|
info = msg['RecommendInfo']
|
|
@@ -496,7 +480,6 @@ class WXBot:
|
|
|
print ' %s[Animation] %s' % (msg_prefix, msg_content['data'])
|
|
|
elif mtype == 49:
|
|
|
msg_content['type'] = 7
|
|
|
- app_msg_type = ''
|
|
|
if msg['AppMsgType'] == 3:
|
|
|
app_msg_type = 'music'
|
|
|
elif msg['AppMsgType'] == 5:
|
|
@@ -509,7 +492,9 @@ class WXBot:
|
|
|
'title': msg['FileName'],
|
|
|
'desc': self.search_content('des', content, 'xml'),
|
|
|
'url': msg['Url'],
|
|
|
- 'from': self.search_content('appname', content, 'xml')}
|
|
|
+ 'from': self.search_content('appname', content, 'xml'),
|
|
|
+ 'content': msg.get('Content') # 有的公众号会发一次性3 4条链接一个大图,如果只url那只能获取第一条,content里面有所有的链接
|
|
|
+ }
|
|
|
if self.DEBUG:
|
|
|
print ' %s[Share] %s' % (msg_prefix, app_msg_type)
|
|
|
print ' --------------------------'
|
|
@@ -517,6 +502,7 @@ class WXBot:
|
|
|
print ' | desc: %s' % self.search_content('des', content, 'xml')
|
|
|
print ' | link: %s' % msg['Url']
|
|
|
print ' | from: %s' % self.search_content('appname', content, 'xml')
|
|
|
+ print ' | content: %s' % (msg.get('content')[:20] if msg.get('content') else "unknown")
|
|
|
print ' --------------------------'
|
|
|
|
|
|
elif mtype == 62:
|
|
@@ -561,11 +547,21 @@ class WXBot:
|
|
|
:param r: 原始微信消息
|
|
|
"""
|
|
|
for msg in r['AddMsgList']:
|
|
|
- msg_type_id = 99
|
|
|
user = {'id': msg['FromUserName'], 'name': 'unknown'}
|
|
|
if msg['MsgType'] == 51: # init message
|
|
|
msg_type_id = 0
|
|
|
user['name'] = 'system'
|
|
|
+ elif msg['MsgType'] == 37: # friend request
|
|
|
+ msg_type_id = 37
|
|
|
+ pass
|
|
|
+ # content = msg['Content']
|
|
|
+ # username = content[content.index('fromusername='): content.index('encryptusername')]
|
|
|
+ # username = username[username.index('"') + 1: username.rindex('"')]
|
|
|
+ # print u'[Friend Request]'
|
|
|
+ # print u' Nickname:' + msg['RecommendInfo']['NickName']
|
|
|
+ # print u' 附加消息:'+msg['RecommendInfo']['Content']
|
|
|
+ # # print u'Ticket:'+msg['RecommendInfo']['Ticket'] # Ticket添加好友时要用
|
|
|
+ # print u' 微信号:'+username #未设置微信号的 腾讯会自动生成一段微信ID 但是无法通过搜索 搜索到此人
|
|
|
elif msg['FromUserName'] == self.my_account['UserName']: # Self
|
|
|
msg_type_id = 1
|
|
|
user['name'] = 'self'
|
|
@@ -592,14 +588,14 @@ class WXBot:
|
|
|
user['name'] = HTMLParser.HTMLParser().unescape(user['name'])
|
|
|
|
|
|
if self.DEBUG and msg_type_id != 0:
|
|
|
- print '[MSG] %s:' % user['name']
|
|
|
+ print u'[MSG] %s:' % user['name']
|
|
|
content = self.extract_msg_content(msg_type_id, msg)
|
|
|
message = {'msg_type_id': msg_type_id,
|
|
|
'msg_id': msg['MsgId'],
|
|
|
'content': content,
|
|
|
'to_user_id': msg['ToUserName'],
|
|
|
'user': user}
|
|
|
- self.handle_msg_all(message)
|
|
|
+ self.msg_queue.put(message)
|
|
|
|
|
|
def schedule(self):
|
|
|
"""
|
|
@@ -615,9 +611,7 @@ class WXBot:
|
|
|
try:
|
|
|
[retcode, selector] = self.sync_check()
|
|
|
# print '[DEBUG] sync_check:', retcode, selector
|
|
|
- if retcode == '-1' and selector == '-1':
|
|
|
- pass
|
|
|
- elif retcode == '1100': # 从微信客户端上登出
|
|
|
+ if retcode == '1100': # 从微信客户端上登出
|
|
|
break
|
|
|
elif retcode == '1101': # 从其它设备上登了网页微信
|
|
|
break
|
|
@@ -630,6 +624,10 @@ class WXBot:
|
|
|
r = self.sync()
|
|
|
if r is not None:
|
|
|
self.handle_msg(r)
|
|
|
+ elif selector == '4': # 通讯录更新
|
|
|
+ r = self.sync()
|
|
|
+ if r is not None:
|
|
|
+ self.get_contact()
|
|
|
elif selector == '6': # 可能是红包
|
|
|
r = self.sync()
|
|
|
if r is not None:
|
|
@@ -647,13 +645,185 @@ class WXBot:
|
|
|
self.handle_msg(r)
|
|
|
else:
|
|
|
print '[DEBUG] sync_check:', retcode, selector
|
|
|
- self.schedule()
|
|
|
- except Exception,e:
|
|
|
+ except:
|
|
|
print '[ERROR] Except in proc_msg'
|
|
|
+ print format_exc()
|
|
|
check_time = time.time() - check_time
|
|
|
if check_time < 0.8:
|
|
|
time.sleep(1 - check_time)
|
|
|
|
|
|
+ def msg_thread_proc(self):
|
|
|
+ print '[INFO] Msg thread start'
|
|
|
+ while True:
|
|
|
+ if not self.msg_queue.empty():
|
|
|
+ msg = self.msg_queue.get()
|
|
|
+ self.handle_msg_all(msg)
|
|
|
+ else:
|
|
|
+ time.sleep(0.1)
|
|
|
+
|
|
|
+ def schedule_thread_proc(self):
|
|
|
+ print '[INFO] Schedule thread start'
|
|
|
+ while True:
|
|
|
+ check_time = time.time()
|
|
|
+ self.schedule()
|
|
|
+ check_time = time.time() - check_time
|
|
|
+ if check_time < self.SCHEDULE_INTV:
|
|
|
+ time.sleep(self.SCHEDULE_INTV - check_time)
|
|
|
+
|
|
|
+ def run(self):
|
|
|
+ self.get_uuid()
|
|
|
+ self.gen_qr_code(os.path.join(self.temp_pwd, 'wxqr.png'))
|
|
|
+ print '[INFO] Please use WeChat to scan the QR code .'
|
|
|
+
|
|
|
+ result = self.wait4login()
|
|
|
+ if result != SUCCESS:
|
|
|
+ print '[ERROR] Web WeChat login failed. failed code=%s' % (result,)
|
|
|
+ return
|
|
|
+
|
|
|
+ if self.login():
|
|
|
+ print '[INFO] Web WeChat login succeed .'
|
|
|
+ else:
|
|
|
+ print '[ERROR] Web WeChat login failed .'
|
|
|
+ return
|
|
|
+
|
|
|
+ if self.init():
|
|
|
+ print '[INFO] Web WeChat init succeed .'
|
|
|
+ else:
|
|
|
+ print '[INFO] Web WeChat init failed'
|
|
|
+ return
|
|
|
+ self.status_notify()
|
|
|
+ self.get_contact()
|
|
|
+ print '[INFO] Get %d contacts' % len(self.contact_list)
|
|
|
+ print '[INFO] Start to process messages .'
|
|
|
+
|
|
|
+ msg_thread = threading.Thread(target=self.msg_thread_proc)
|
|
|
+ msg_thread.setDaemon(True)
|
|
|
+ msg_thread.start()
|
|
|
+
|
|
|
+ msg_thread = threading.Thread(target=self.schedule_thread_proc)
|
|
|
+ msg_thread.setDaemon(True)
|
|
|
+ msg_thread.start()
|
|
|
+
|
|
|
+ self.proc_msg()
|
|
|
+
|
|
|
+ def apply_useradd_requests(self,RecommendInfo):
|
|
|
+ url = self.base_uri + '/webwxverifyuser?r='+str(int(time.time()))+'&lang=zh_CN'
|
|
|
+ params = {
|
|
|
+ "BaseRequest": self.base_request,
|
|
|
+ "Opcode": 3,
|
|
|
+ "VerifyUserListSize": 1,
|
|
|
+ "VerifyUserList": [
|
|
|
+ {
|
|
|
+ "Value": RecommendInfo['UserName'],
|
|
|
+ "VerifyUserTicket": RecommendInfo['Ticket'] }
|
|
|
+ ],
|
|
|
+ "VerifyContent": "",
|
|
|
+ "SceneListCount": 1,
|
|
|
+ "SceneList": [
|
|
|
+ 33
|
|
|
+ ],
|
|
|
+ "skey": self.skey
|
|
|
+ }
|
|
|
+ headers = {'content-type': 'application/json; charset=UTF-8'}
|
|
|
+ data = json.dumps(params, ensure_ascii=False).encode('utf8')
|
|
|
+ try:
|
|
|
+ r = self.session.post(url, data=data, headers=headers)
|
|
|
+ except (ConnectionError, ReadTimeout):
|
|
|
+ return False
|
|
|
+ dic = r.json()
|
|
|
+ return dic['BaseResponse']['Ret'] == 0
|
|
|
+
|
|
|
+ def add_groupuser_to_friend_by_uid(self, uid, VerifyContent):
|
|
|
+ """
|
|
|
+ 主动向群内人员打招呼,提交添加好友请求
|
|
|
+ uid-群内人员得uid VerifyContent-好友招呼内容
|
|
|
+ 慎用此接口!封号后果自负!慎用此接口!封号后果自负!慎用此接口!封号后果自负!
|
|
|
+ """
|
|
|
+ if self.is_contact(uid):
|
|
|
+ return True
|
|
|
+ url = self.base_uri + '/webwxverifyuser?r='+str(int(time.time()))+'&lang=zh_CN'
|
|
|
+ params ={
|
|
|
+ "BaseRequest": self.base_request,
|
|
|
+ "Opcode": 2,
|
|
|
+ "VerifyUserListSize": 1,
|
|
|
+ "VerifyUserList": [
|
|
|
+ {
|
|
|
+ "Value": uid,
|
|
|
+ "VerifyUserTicket": ""
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ "VerifyContent": VerifyContent,
|
|
|
+ "SceneListCount": 1,
|
|
|
+ "SceneList": [
|
|
|
+ 33
|
|
|
+ ],
|
|
|
+ "skey": self.skey
|
|
|
+ }
|
|
|
+ headers = {'content-type': 'application/json; charset=UTF-8'}
|
|
|
+ data = json.dumps(params, ensure_ascii=False).encode('utf8')
|
|
|
+ try:
|
|
|
+ r = self.session.post(url, data=data, headers=headers)
|
|
|
+ except (ConnectionError, ReadTimeout):
|
|
|
+ return False
|
|
|
+ dic = r.json()
|
|
|
+ return dic['BaseResponse']['Ret'] == 0
|
|
|
+
|
|
|
+ def add_friend_to_group(self,uid,group_name):
|
|
|
+ """
|
|
|
+ 将好友加入到群聊中
|
|
|
+ """
|
|
|
+ gid = ''
|
|
|
+ #通过群名获取群id,群没保存到通讯录中的话无法添加哦
|
|
|
+ for group in self.group_list:
|
|
|
+ if group['NickName'] == group_name:
|
|
|
+ gid = group['UserName']
|
|
|
+ if gid == '':
|
|
|
+ return False
|
|
|
+ #通过群id判断uid是否在群中
|
|
|
+ for user in self.group_members[gid]:
|
|
|
+ if user['UserName'] == uid:
|
|
|
+ #已经在群里面了,不用加了
|
|
|
+ return True
|
|
|
+ url = self.base_uri + '/webwxupdatechatroom?fun=addmember&pass_ticket=%s' % self.pass_ticket
|
|
|
+ params ={
|
|
|
+ "AddMemberList": uid,
|
|
|
+ "ChatRoomName": gid,
|
|
|
+ "BaseRequest": self.base_request
|
|
|
+ }
|
|
|
+ headers = {'content-type': 'application/json; charset=UTF-8'}
|
|
|
+ data = json.dumps(params, ensure_ascii=False).encode('utf8')
|
|
|
+ try:
|
|
|
+ r = self.session.post(url, data=data, headers=headers)
|
|
|
+ except (ConnectionError, ReadTimeout):
|
|
|
+ return False
|
|
|
+ dic = r.json()
|
|
|
+ return dic['BaseResponse']['Ret'] == 0
|
|
|
+
|
|
|
+ def delete_user_from_group(self,uname,gid):
|
|
|
+ """
|
|
|
+ 将群用户从群中剔除,只有群管理员有权限
|
|
|
+ """
|
|
|
+ uid = ""
|
|
|
+ for user in self.group_members[gid]:
|
|
|
+ if user['NickName'] == uname:
|
|
|
+ uid = user['UserName']
|
|
|
+ if uid == "":
|
|
|
+ return False
|
|
|
+ url = self.base_uri + '/webwxupdatechatroom?fun=delmember&pass_ticket=%s' % self.pass_ticket
|
|
|
+ params ={
|
|
|
+ "DelMemberList": uid,
|
|
|
+ "ChatRoomName": gid,
|
|
|
+ "BaseRequest": self.base_request
|
|
|
+ }
|
|
|
+ headers = {'content-type': 'application/json; charset=UTF-8'}
|
|
|
+ data = json.dumps(params, ensure_ascii=False).encode('utf8')
|
|
|
+ try:
|
|
|
+ r = self.session.post(url, data=data, headers=headers)
|
|
|
+ except (ConnectionError, ReadTimeout):
|
|
|
+ return False
|
|
|
+ dic = r.json()
|
|
|
+ return dic['BaseResponse']['Ret'] == 0
|
|
|
+
|
|
|
def send_msg_by_uid(self, word, dst='filehelper'):
|
|
|
url = self.base_uri + '/webwxsendmsg?pass_ticket=%s' % self.pass_ticket
|
|
|
msg_id = str(int(time.time() * 1000)) + str(random.random())[:5].replace('.', '')
|
|
@@ -678,6 +848,100 @@ class WXBot:
|
|
|
dic = r.json()
|
|
|
return dic['BaseResponse']['Ret'] == 0
|
|
|
|
|
|
+ def upload_media(self, fpath, is_img=False):
|
|
|
+ if not os.path.exists(fpath):
|
|
|
+ print '[ERROR] File not exists.'
|
|
|
+ return None
|
|
|
+ url_1 = 'https://file.wx.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json'
|
|
|
+ url_2 = 'https://file2.wx.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json'
|
|
|
+ flen = str(os.path.getsize(fpath))
|
|
|
+ ftype = mimetypes.guess_type(fpath)[0] or 'application/octet-stream'
|
|
|
+ files = {
|
|
|
+ 'id': (None, 'WU_FILE_%s' % str(self.file_index)),
|
|
|
+ 'name': (None, os.path.basename(fpath)),
|
|
|
+ 'type': (None, ftype),
|
|
|
+ 'lastModifiedDate': (None, time.strftime('%m/%d/%Y, %H:%M:%S GMT+0800 (CST)')),
|
|
|
+ 'size': (None, flen),
|
|
|
+ 'mediatype': (None, 'pic' if is_img else 'doc'),
|
|
|
+ 'uploadmediarequest': (None, json.dumps({
|
|
|
+ 'BaseRequest': self.base_request,
|
|
|
+ 'ClientMediaId': int(time.time()),
|
|
|
+ 'TotalLen': flen,
|
|
|
+ 'StartPos': 0,
|
|
|
+ 'DataLen': flen,
|
|
|
+ 'MediaType': 4,
|
|
|
+ })),
|
|
|
+ 'webwx_data_ticket': (None, self.session.cookies['webwx_data_ticket']),
|
|
|
+ 'pass_ticket': (None, self.pass_ticket),
|
|
|
+ 'filename': (os.path.basename(fpath), open(fpath, 'rb'),ftype.split('/')[1]),
|
|
|
+ }
|
|
|
+ self.file_index += 1
|
|
|
+ try:
|
|
|
+ r = self.session.post(url_1, files=files)
|
|
|
+ if json.loads(r.text)['BaseResponse']['Ret'] != 0:
|
|
|
+ # 当file返回值不为0时则为上传失败,尝试第二服务器上传
|
|
|
+ r = self.session.post(url_2, files=files)
|
|
|
+ if json.loads(r.text)['BaseResponse']['Ret'] != 0:
|
|
|
+ print '[ERROR] Upload media failure.'
|
|
|
+ return None
|
|
|
+ mid = json.loads(r.text)['MediaId']
|
|
|
+ return mid
|
|
|
+ except Exception,e:
|
|
|
+ return None
|
|
|
+
|
|
|
+ def send_file_msg_by_uid(self, fpath, uid):
|
|
|
+ mid = self.upload_media(fpath)
|
|
|
+ if mid is None or not mid:
|
|
|
+ return False
|
|
|
+ url = self.base_uri + '/webwxsendappmsg?fun=async&f=json&pass_ticket=' + self.pass_ticket
|
|
|
+ msg_id = str(int(time.time() * 1000)) + str(random.random())[:5].replace('.', '')
|
|
|
+ data = {
|
|
|
+ 'BaseRequest': self.base_request,
|
|
|
+ 'Msg': {
|
|
|
+ 'Type': 6,
|
|
|
+ 'Content': ("<appmsg appid='wxeb7ec651dd0aefa9' sdkver=''><title>%s</title><des></des><action></action><type>6</type><content></content><url></url><lowurl></lowurl><appattach><totallen>%s</totallen><attachid>%s</attachid><fileext>%s</fileext></appattach><extinfo></extinfo></appmsg>" % (os.path.basename(fpath).encode('utf-8'), str(os.path.getsize(fpath)), mid, fpath.split('.')[-1])).encode('utf8'),
|
|
|
+ 'FromUserName': self.my_account['UserName'],
|
|
|
+ 'ToUserName': uid,
|
|
|
+ 'LocalID': msg_id,
|
|
|
+ 'ClientMsgId': msg_id, }, }
|
|
|
+ try:
|
|
|
+ r = self.session.post(url, data=json.dumps(data))
|
|
|
+ res = json.loads(r.text)
|
|
|
+ if res['BaseResponse']['Ret'] == 0:
|
|
|
+ return True
|
|
|
+ else:
|
|
|
+ return False
|
|
|
+ except Exception,e:
|
|
|
+ return False
|
|
|
+
|
|
|
+ def send_img_msg_by_uid(self, fpath, uid):
|
|
|
+ mid = self.upload_media(fpath, is_img=True)
|
|
|
+ if mid is None:
|
|
|
+ return False
|
|
|
+ url = self.base_uri + '/webwxsendmsgimg?fun=async&f=json'
|
|
|
+ data = {
|
|
|
+ 'BaseRequest': self.base_request,
|
|
|
+ 'Msg': {
|
|
|
+ 'Type': 3,
|
|
|
+ 'MediaId': mid,
|
|
|
+ 'FromUserName': self.my_account['UserName'],
|
|
|
+ 'ToUserName': uid,
|
|
|
+ 'LocalID': str(time.time() * 1e7),
|
|
|
+ 'ClientMsgId': str(time.time() * 1e7), }, }
|
|
|
+ if fpath[-4:] == '.gif':
|
|
|
+ url = self.base_uri + '/webwxsendemoticon?fun=sys'
|
|
|
+ data['Msg']['Type'] = 47
|
|
|
+ data['Msg']['EmojiFlag'] = 2
|
|
|
+ try:
|
|
|
+ r = self.session.post(url, data=json.dumps(data))
|
|
|
+ res = json.loads(r.text)
|
|
|
+ if res['BaseResponse']['Ret'] == 0:
|
|
|
+ return True
|
|
|
+ else:
|
|
|
+ return False
|
|
|
+ except Exception,e:
|
|
|
+ return False
|
|
|
+
|
|
|
def get_user_id(self, name):
|
|
|
if name == '':
|
|
|
return None
|
|
@@ -689,6 +953,14 @@ class WXBot:
|
|
|
return contact['UserName']
|
|
|
elif 'DisplayName' in contact and contact['DisplayName'] == name:
|
|
|
return contact['UserName']
|
|
|
+ for group in self.group_list:
|
|
|
+ if 'RemarkName' in group and group['RemarkName'] == name:
|
|
|
+ return group['UserName']
|
|
|
+ if 'NickName' in group and group['NickName'] == name:
|
|
|
+ return group['UserName']
|
|
|
+ if 'DisplayName' in group and group['DisplayName'] == name:
|
|
|
+ return group['UserName']
|
|
|
+
|
|
|
return ''
|
|
|
|
|
|
def send_msg(self, name, word, isfile=False):
|
|
@@ -729,33 +1001,6 @@ class WXBot:
|
|
|
return pm.group(1)
|
|
|
return 'unknown'
|
|
|
|
|
|
- def run(self):
|
|
|
- self.get_uuid()
|
|
|
- self.gen_qr_code('qr.png')
|
|
|
- print '[INFO] Please use WeChat to scan the QR code .'
|
|
|
-
|
|
|
- result = self.wait4login()
|
|
|
- if result != SUCCESS:
|
|
|
- print '[ERROR] Web WeChat login failed. failed code=%s'%(result, )
|
|
|
- return
|
|
|
-
|
|
|
- if self.login():
|
|
|
- print '[INFO] Web WeChat login succeed .'
|
|
|
- else:
|
|
|
- print '[ERROR] Web WeChat login failed .'
|
|
|
- return
|
|
|
-
|
|
|
- if self.init():
|
|
|
- print '[INFO] Web WeChat init succeed .'
|
|
|
- else:
|
|
|
- print '[INFO] Web WeChat init failed'
|
|
|
- return
|
|
|
- self.status_notify()
|
|
|
- self.get_contact()
|
|
|
- print '[INFO] Get %d contacts' % len(self.contact_list)
|
|
|
- print '[INFO] Start to process messages .'
|
|
|
- self.proc_msg()
|
|
|
-
|
|
|
def get_uuid(self):
|
|
|
url = 'https://login.weixin.qq.com/jslogin'
|
|
|
params = {
|
|
@@ -825,14 +1070,14 @@ class WXBot:
|
|
|
self.base_uri = redirect_uri[:redirect_uri.rfind('/')]
|
|
|
return code
|
|
|
elif code == TIMEOUT:
|
|
|
- print '[ERROR] WeChat login timeout. retry in %s secs later...'%(try_later_secs, )
|
|
|
+ print '[ERROR] WeChat login timeout. retry in %s secs later...' % (try_later_secs,)
|
|
|
|
|
|
- tip = 1 # 重置
|
|
|
+ tip = 1 # 重置
|
|
|
retry_time -= 1
|
|
|
time.sleep(try_later_secs)
|
|
|
else:
|
|
|
print ('[ERROR] WeChat login exception return_code=%s. retry in %s secs later...' %
|
|
|
- (code, try_later_secs))
|
|
|
+ (code, try_later_secs))
|
|
|
tip = 1
|
|
|
retry_time -= 1
|
|
|
time.sleep(try_later_secs)
|
|
@@ -927,7 +1172,7 @@ class WXBot:
|
|
|
selector = pm.group(2)
|
|
|
return [retcode, selector]
|
|
|
except:
|
|
|
- return ['-1', '-1']
|
|
|
+ return [-1, -1]
|
|
|
|
|
|
def sync(self):
|
|
|
url = self.base_uri + '/webwxsync?sid=%s&skey=%s&lang=en_US&pass_ticket=%s' \
|
|
@@ -949,100 +1194,6 @@ class WXBot:
|
|
|
except:
|
|
|
return None
|
|
|
|
|
|
- def upload_media(self, fpath, is_img=False):
|
|
|
- if not os.path.exists(fpath):
|
|
|
- print '[ERROR] File not exists.'
|
|
|
- return None
|
|
|
- url_1 = 'https://file.wx.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json'
|
|
|
- url_2 = 'https://file2.wx.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json'
|
|
|
- flen = str(os.path.getsize(fpath))
|
|
|
- ftype = mimetypes.guess_type(fpath)[0] or 'application/octet-stream'
|
|
|
- files = {
|
|
|
- 'id': (None, 'WU_FILE_%s' % str(self.file_index)),
|
|
|
- 'name': (None, os.path.basename(fpath)),
|
|
|
- 'type': (None, ftype),
|
|
|
- 'lastModifiedDate': (None, time.strftime('%m/%d/%Y, %H:%M:%S GMT+0800 (CST)')),
|
|
|
- 'size': (None, flen),
|
|
|
- 'mediatype': (None, 'pic' if is_img else 'doc'),
|
|
|
- 'uploadmediarequest': (None, json.dumps({
|
|
|
- 'BaseRequest': self.base_request,
|
|
|
- 'ClientMediaId': int(time.time()),
|
|
|
- 'TotalLen': flen,
|
|
|
- 'StartPos': 0,
|
|
|
- 'DataLen': flen,
|
|
|
- 'MediaType': 4,
|
|
|
- })),
|
|
|
- 'webwx_data_ticket': (None, self.session.cookies['webwx_data_ticket']),
|
|
|
- 'pass_ticket': (None, self.pass_ticket),
|
|
|
- 'filename': (os.path.basename(fpath), open(fpath, 'rb'),ftype.split('/')[1]),
|
|
|
- }
|
|
|
- self.file_index += 1
|
|
|
- try:
|
|
|
- r = self.session.post(url_1, files=files)
|
|
|
- if json.loads(r.text)['BaseResponse']['Ret'] != 0:
|
|
|
- # 当file返回值不为0时则为上传失败,尝试第二服务器上传
|
|
|
- r = self.session.post(url_2, files=files)
|
|
|
- if json.loads(r.text)['BaseResponse']['Ret'] != 0:
|
|
|
- print '[ERROR] Upload media failure.'
|
|
|
- return None
|
|
|
- mid = json.loads(r.text)['MediaId']
|
|
|
- return mid
|
|
|
- except Exception,e:
|
|
|
- return None
|
|
|
-
|
|
|
- def send_file_msg_by_uid(self, fpath, uid):
|
|
|
- mid = self.upload_media(fpath)
|
|
|
- if mid is None or not mid:
|
|
|
- return False
|
|
|
- url = self.base_uri + '/webwxsendappmsg?fun=async&f=json&pass_ticket=' + self.pass_ticket
|
|
|
- msg_id = str(int(time.time() * 1000)) + str(random.random())[:5].replace('.', '')
|
|
|
- data = {
|
|
|
- 'BaseRequest': self.base_request,
|
|
|
- 'Msg': {
|
|
|
- 'Type': 6,
|
|
|
- 'Content': ("<appmsg appid='wxeb7ec651dd0aefa9' sdkver=''><title>%s</title><des></des><action></action><type>6</type><content></content><url></url><lowurl></lowurl><appattach><totallen>%s</totallen><attachid>%s</attachid><fileext>%s</fileext></appattach><extinfo></extinfo></appmsg>" % (os.path.basename(fpath).encode('utf-8'), str(os.path.getsize(fpath)), mid, fpath.split('.')[-1])).encode('utf8'),
|
|
|
- 'FromUserName': self.my_account['UserName'],
|
|
|
- 'ToUserName': uid,
|
|
|
- 'LocalID': msg_id,
|
|
|
- 'ClientMsgId': msg_id, }, }
|
|
|
- try:
|
|
|
- r = self.session.post(url, data=json.dumps(data))
|
|
|
- res = json.loads(r.text)
|
|
|
- if res['BaseResponse']['Ret'] == 0:
|
|
|
- return True
|
|
|
- else:
|
|
|
- return False
|
|
|
- except Exception,e:
|
|
|
- return False
|
|
|
-
|
|
|
- def send_img_msg_by_uid(self, fpath, uid):
|
|
|
- mid = self.upload_media(fpath, is_img=True)
|
|
|
- if mid is None:
|
|
|
- return False
|
|
|
- url = self.base_uri + '/webwxsendmsgimg?fun=async&f=json'
|
|
|
- data = {
|
|
|
- 'BaseRequest': self.base_request,
|
|
|
- 'Msg': {
|
|
|
- 'Type': 3,
|
|
|
- 'MediaId': mid,
|
|
|
- 'FromUserName': self.my_account['UserName'],
|
|
|
- 'ToUserName': uid,
|
|
|
- 'LocalID': str(time.time() * 1e7),
|
|
|
- 'ClientMsgId': str(time.time() * 1e7), }, }
|
|
|
- if fpath[-4:] == '.gif':
|
|
|
- url = self.base_uri + '/webwxsendemoticon?fun=sys'
|
|
|
- data['Msg']['Type'] = 47
|
|
|
- data['Msg']['EmojiFlag'] = 2
|
|
|
- try:
|
|
|
- r = self.session.post(url, data=json.dumps(data))
|
|
|
- res = json.loads(r.text)
|
|
|
- if res['BaseResponse']['Ret'] == 0:
|
|
|
- return True
|
|
|
- else:
|
|
|
- return False
|
|
|
- except Exception,e:
|
|
|
- return False
|
|
|
-
|
|
|
def get_icon(self, uid, gid=None):
|
|
|
"""
|
|
|
获取联系人或者群聊成员头像
|
|
@@ -1052,11 +1203,12 @@ class WXBot:
|
|
|
if gid is None:
|
|
|
url = self.base_uri + '/webwxgeticon?username=%s&skey=%s' % (uid, self.skey)
|
|
|
else:
|
|
|
- url = self.base_uri + '/webwxgeticon?username=%s&skey=%s&chatroomid=%s' % (uid, self.skey, self.encry_chat_room_id_list[gid])
|
|
|
+ url = self.base_uri + '/webwxgeticon?username=%s&skey=%s&chatroomid=%s' % (
|
|
|
+ uid, self.skey, self.encry_chat_room_id_list[gid])
|
|
|
r = self.session.get(url)
|
|
|
data = r.content
|
|
|
fn = 'icon_' + uid + '.jpg'
|
|
|
- with open(fn, 'wb') as f:
|
|
|
+ with open(os.path.join(self.temp_pwd,fn), 'wb') as f:
|
|
|
f.write(data)
|
|
|
return fn
|
|
|
|
|
@@ -1069,7 +1221,7 @@ class WXBot:
|
|
|
r = self.session.get(url)
|
|
|
data = r.content
|
|
|
fn = 'head_' + uid + '.jpg'
|
|
|
- with open(fn, 'wb') as f:
|
|
|
+ with open(os.path.join(self.temp_pwd,fn), 'wb') as f:
|
|
|
f.write(data)
|
|
|
return fn
|
|
|
|
|
@@ -1086,7 +1238,7 @@ class WXBot:
|
|
|
r = self.session.get(url)
|
|
|
data = r.content
|
|
|
fn = 'img_' + msgid + '.jpg'
|
|
|
- with open(fn, 'wb') as f:
|
|
|
+ with open(os.path.join(self.temp_pwd,fn), 'wb') as f:
|
|
|
f.write(data)
|
|
|
return fn
|
|
|
|
|
@@ -1103,6 +1255,23 @@ class WXBot:
|
|
|
r = self.session.get(url)
|
|
|
data = r.content
|
|
|
fn = 'voice_' + msgid + '.mp3'
|
|
|
- with open(fn, 'wb') as f:
|
|
|
+ with open(os.path.join(self.temp_pwd,fn), 'wb') as f:
|
|
|
f.write(data)
|
|
|
return fn
|
|
|
+
|
|
|
+ def set_remark_name(self, uid, name): # 设置联系人的备注名
|
|
|
+ url = self.base_uri + '/webwxoplog?lang=zh_CN&pass_ticket=%s' % self.pass_ticket
|
|
|
+ remark_name = self.to_unicode(name)
|
|
|
+ params = {
|
|
|
+ 'BaseRequest': self.base_request,
|
|
|
+ 'CmdId': 2,
|
|
|
+ 'RemarkName': remark_name,
|
|
|
+ 'UserName': uid
|
|
|
+ }
|
|
|
+ try:
|
|
|
+ r = self.session.post(url, data=json.dumps(params), timeout=60)
|
|
|
+ r.encoding = 'utf-8'
|
|
|
+ dic = json.loads(r.text)
|
|
|
+ return dic['BaseResponse']['ErrMsg']
|
|
|
+ except:
|
|
|
+ return None
|