i just come out and take a bubble, haha \O^O/
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
from wechat import WeChat
|
||||
@@ -0,0 +1,359 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
#===================================================
|
||||
from config import Log
|
||||
from config import Constant
|
||||
#---------------------------------------------------
|
||||
import qrcode
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import urllib
|
||||
import urllib2
|
||||
import cookielib
|
||||
import cPickle as pickle
|
||||
import traceback
|
||||
import time
|
||||
import hashlib
|
||||
#===================================================
|
||||
|
||||
|
||||
def _decode_data(data):
|
||||
"""
|
||||
@brief decode array or dict to utf-8
|
||||
@param data array or dict
|
||||
@return utf-8
|
||||
"""
|
||||
if isinstance(data, dict):
|
||||
rv = {}
|
||||
for key, value in data.iteritems():
|
||||
if isinstance(key, unicode):
|
||||
key = key.encode('utf-8')
|
||||
rv[key] = _decode_data(value)
|
||||
return rv
|
||||
elif isinstance(data, list):
|
||||
rv = []
|
||||
for item in data:
|
||||
item = _decode_data(item)
|
||||
rv.append(item)
|
||||
return rv
|
||||
elif isinstance(data, unicode):
|
||||
return data.encode('utf-8')
|
||||
else:
|
||||
return data
|
||||
|
||||
|
||||
def str2qr_terminal(text):
|
||||
"""
|
||||
@brief convert string to qrcode matrix and outprint
|
||||
@param text The string
|
||||
"""
|
||||
Log.debug(text)
|
||||
qr = qrcode.QRCode()
|
||||
qr.border = 1
|
||||
qr.add_data(text)
|
||||
mat = qr.get_matrix()
|
||||
print_qr(mat)
|
||||
|
||||
|
||||
def str2qr_image(text, image_path):
|
||||
"""
|
||||
@brief convert string to qrcode image & save
|
||||
@param text The string
|
||||
@param image_path Save image to the path
|
||||
"""
|
||||
qr = qrcode.QRCode()
|
||||
qr.border = 1
|
||||
qr.add_data(text)
|
||||
qr.make(fit=True)
|
||||
img = qr.make_image()
|
||||
img.save(image_path)
|
||||
|
||||
|
||||
def print_qr(mat):
|
||||
for i in mat:
|
||||
BLACK = Constant.QRCODE_BLACK
|
||||
WHITE = Constant.QRCODE_WHITE
|
||||
echo(''.join([BLACK if j else WHITE for j in i])+'\n')
|
||||
|
||||
|
||||
def echo(str):
|
||||
Log.info(str[:-1])
|
||||
sys.stdout.write(str)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def run(str, func, *args):
|
||||
t = time.time()
|
||||
echo(str)
|
||||
r = False
|
||||
try:
|
||||
r = func(*args)
|
||||
except:
|
||||
Log.error(traceback.format_exc())
|
||||
if r:
|
||||
totalTime = int(time.time() - t)
|
||||
echo(Constant.RUN_RESULT_SUCCESS % totalTime)
|
||||
else:
|
||||
echo(Constant.RUN_RESULT_FAIL)
|
||||
exit()
|
||||
|
||||
|
||||
def get(url, api=None):
|
||||
"""
|
||||
@brief http get request
|
||||
@param url String
|
||||
@param api wechat api
|
||||
@return http response
|
||||
"""
|
||||
Log.debug('GET -> ' + url)
|
||||
request = urllib2.Request(url=url)
|
||||
request.add_header(*Constant.HTTP_HEADER_CONNECTION)
|
||||
request.add_header(*Constant.HTTP_HEADER_REFERER)
|
||||
if api in ['webwxgetvoice', 'webwxgetvideo']:
|
||||
request.add_header(*Constant.HTTP_HEADER_RANGE)
|
||||
|
||||
while True:
|
||||
try:
|
||||
response = urllib2.urlopen(request, timeout=30)
|
||||
data = response.read()
|
||||
response.close()
|
||||
if api == None:
|
||||
Log.debug(data)
|
||||
return data
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
Log.error(traceback.format_exc())
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def post(url, params, jsonfmt=True):
|
||||
"""
|
||||
@brief http post request
|
||||
@param url String
|
||||
@param params Dict, post params
|
||||
@param jsonfmt Bool, whether is json format
|
||||
@return http response
|
||||
"""
|
||||
Log.debug('POST -> '+url)
|
||||
Log.debug(params)
|
||||
if jsonfmt:
|
||||
request = urllib2.Request(url=url, data=json.dumps(params, ensure_ascii=False).encode('utf8'))
|
||||
request.add_header(*Constant.HTTP_HEADER_CONTENTTYPE)
|
||||
else:
|
||||
request = urllib2.Request(url=url, data=urllib.urlencode(params))
|
||||
|
||||
while True:
|
||||
try:
|
||||
response = urllib2.urlopen(request, timeout=30)
|
||||
data = response.read()
|
||||
response.close()
|
||||
|
||||
if jsonfmt:
|
||||
Log.debug(data)
|
||||
return json.loads(data, object_hook=_decode_data)
|
||||
return data
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
Log.error(traceback.format_exc())
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def set_cookie(cookie_file):
|
||||
"""
|
||||
@brief Load cookie from file
|
||||
@param cookie_file
|
||||
@param user_agent
|
||||
@return cookie, LWPCookieJar
|
||||
"""
|
||||
cookie = cookielib.LWPCookieJar(cookie_file)
|
||||
try:
|
||||
cookie.load(ignore_discard=True)
|
||||
except:
|
||||
Log.error(traceback.format_exc())
|
||||
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))
|
||||
opener.addheaders = Constant.HTTP_HEADER_USERAGENT
|
||||
urllib2.install_opener(opener)
|
||||
return cookie
|
||||
|
||||
|
||||
def generate_file_name(filename):
|
||||
"""
|
||||
@brief generate file name
|
||||
@return new file name
|
||||
"""
|
||||
i = filename.rfind('.')
|
||||
ext = filename[i:]
|
||||
tmp = filename + str(int(time.time()))
|
||||
hash_md5 = hashlib.md5(tmp)
|
||||
return hash_md5.hexdigest() + ext
|
||||
|
||||
|
||||
def save_file(filename, data, dirName):
|
||||
"""
|
||||
@brief Saves raw data to file.
|
||||
@param filename String
|
||||
@param data Binary data
|
||||
@param dirName String
|
||||
@return file path
|
||||
"""
|
||||
Log.debug('save file: ' + filename)
|
||||
fn = filename
|
||||
if not os.path.exists(dirName):
|
||||
os.makedirs(dirName)
|
||||
fn = os.path.join(dirName, filename)
|
||||
with open(fn, 'wb') as f:
|
||||
f.write(data)
|
||||
return fn
|
||||
|
||||
|
||||
def save_json(filename, data, dirName, mode='w+'):
|
||||
"""
|
||||
@brief Saves dict to json file.
|
||||
@param filename String
|
||||
@param data Dict
|
||||
@param dirName String
|
||||
@return file path
|
||||
"""
|
||||
Log.debug('save json: ' + filename)
|
||||
fn = filename
|
||||
if not os.path.exists(dirName):
|
||||
os.makedirs(dirName)
|
||||
fn = os.path.join(dirName, filename)
|
||||
with open(fn, mode) as f:
|
||||
f.write(json.dumps(data, indent=4)+'\n')
|
||||
return fn
|
||||
|
||||
|
||||
def pickle_save(data, file):
|
||||
"""
|
||||
@brief Use pickle to save python object into file
|
||||
@param data The pyhton data
|
||||
@param file The file
|
||||
"""
|
||||
basedir = os.path.dirname(file)
|
||||
if not os.path.exists(basedir):
|
||||
os.makedirs(basedir)
|
||||
with open(file, 'wb') as f:
|
||||
pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
|
||||
def pickle_load(file):
|
||||
"""
|
||||
@brief Use pickle to load python object from file
|
||||
@param file The file
|
||||
@return python data
|
||||
"""
|
||||
if os.path.isfile(file):
|
||||
with open(file, 'rb') as f:
|
||||
return pickle.load(f)
|
||||
return None
|
||||
|
||||
|
||||
def search_content(key, content, fmat='attr'):
|
||||
"""
|
||||
@brief Search content from xml or html format
|
||||
@param key String
|
||||
@param content String
|
||||
@param fmat attr
|
||||
xml
|
||||
@return String
|
||||
"""
|
||||
if fmat == 'attr':
|
||||
pm = re.search(key + '\s?=\s?"([^"<]+)"', content)
|
||||
if pm:
|
||||
return pm.group(1)
|
||||
elif fmat == 'xml':
|
||||
pm = re.search('<{0}>([^<]+)</{0}>'.format(key), content)
|
||||
if not pm:
|
||||
pm = re.search(
|
||||
'<{0}><\!\[CDATA\[(.*?)\]\]></{0}>'.format(key), content)
|
||||
if pm:
|
||||
return pm.group(1)
|
||||
return 'unknown'
|
||||
|
||||
|
||||
def is_str(s):
|
||||
"""
|
||||
@brief Determines if string.
|
||||
@param s String
|
||||
@return True if string, False otherwise.
|
||||
"""
|
||||
return isinstance(s, basestring)
|
||||
|
||||
|
||||
def trans_coding(data):
|
||||
"""
|
||||
@brief Transform string to unicode
|
||||
@param data String
|
||||
@return unicode
|
||||
"""
|
||||
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 trans_emoji(text):
|
||||
"""
|
||||
@brief Transform emoji html text to unicode
|
||||
@param text String
|
||||
@return emoji unicode
|
||||
"""
|
||||
def _emoji(matched):
|
||||
hex = matched.group(1)
|
||||
return ('\\U%08x' % int(hex, 16)).decode('unicode-escape').encode('utf-8')
|
||||
|
||||
replace_t = re.sub(Constant.REGEX_EMOJI, _emoji, text)
|
||||
return replace_t
|
||||
|
||||
|
||||
def auto_reload(mod):
|
||||
"""
|
||||
@brief reload modules
|
||||
@param mod: the need reload modules
|
||||
"""
|
||||
try:
|
||||
module = sys.modules[mod]
|
||||
except:
|
||||
Log.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
filename = module.__file__
|
||||
# .pyc 修改时间不会变
|
||||
# 所以就用 .py 的修改时间
|
||||
if filename.endswith(".pyc"):
|
||||
filename = filename.replace(".pyc", ".py")
|
||||
mod_time = os.path.getmtime(filename)
|
||||
if not "loadtime" in module.__dict__:
|
||||
module.loadtime = 0
|
||||
|
||||
try:
|
||||
if mod_time > module.loadtime:
|
||||
reload(module)
|
||||
else:
|
||||
return False
|
||||
except:
|
||||
Log.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
module.loadtime = mod_time
|
||||
|
||||
echo('[*] load \'%s\' successful.\n' % mod)
|
||||
return True
|
||||
|
||||
|
||||
def split_array(arr, n):
|
||||
for i in xrange(0, len(arr), n):
|
||||
yield arr[i:i+n]
|
||||
|
||||
@@ -0,0 +1,697 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
#===================================================
|
||||
from utils import *
|
||||
from wechat_apis import WXAPI
|
||||
from config import ConfigManager
|
||||
from config import Constant
|
||||
from config import Log
|
||||
#---------------------------------------------------
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
from collections import defaultdict
|
||||
from datetime import timedelta
|
||||
import traceback
|
||||
import Queue
|
||||
import threading
|
||||
#===================================================
|
||||
|
||||
|
||||
class WeChat(WXAPI):
|
||||
|
||||
def __str__(self):
|
||||
description = \
|
||||
"=========================\n" + \
|
||||
"[#] Web WeChat\n" + \
|
||||
"[#] UUID: " + self.uuid + "\n" + \
|
||||
"[#] Uin: " + str(self.uin) + "\n" + \
|
||||
"[#] Sid: " + self.sid + "\n" + \
|
||||
"[#] Skey: " + self.skey + "\n" + \
|
||||
"[#] DeviceId: " + self.device_id + "\n" + \
|
||||
"[#] PassTicket: " + self.pass_ticket + "\n" + \
|
||||
"========================="
|
||||
return description
|
||||
|
||||
def __init__(self, host='wx.qq.com'):
|
||||
super(WeChat, self).__init__(host)
|
||||
|
||||
self.db = None
|
||||
self.save_data_folder = '' # 保存图片,语音,小视频的文件夹
|
||||
self.last_login = 0 # 上次退出的时间
|
||||
self.time_out = 5 # 同步时间间隔(单位:秒)
|
||||
self.msg_handler = None
|
||||
self.start_time = time.time()
|
||||
self.bot = None
|
||||
|
||||
cm = ConfigManager()
|
||||
self.save_data_folders = cm.get_wechat_media_dir()
|
||||
self.cookie_file = cm.get_cookie()
|
||||
self.pickle_file = cm.get_pickle_files()
|
||||
self.log_mode = cm.get('setting', 'log_mode') == 'True'
|
||||
self.exit_code = 0
|
||||
|
||||
def start(self):
|
||||
echo(Constant.LOG_MSG_START)
|
||||
run(Constant.LOG_MSG_RECOVER, self.recover)
|
||||
|
||||
timeOut = time.time() - self.last_login
|
||||
echo(Constant.LOG_MSG_TRY_INIT)
|
||||
if self.webwxinit():
|
||||
echo(Constant.LOG_MSG_SUCCESS)
|
||||
run(Constant.LOG_MSG_RECOVER_CONTACT, self.recover_contacts)
|
||||
else:
|
||||
echo(Constant.LOG_MSG_FAIL)
|
||||
|
||||
while True:
|
||||
# first try to login by uin without qrcode
|
||||
echo(Constant.LOG_MSG_ASSOCIATION_LOGIN)
|
||||
if self.association_login():
|
||||
echo(Constant.LOG_MSG_SUCCESS)
|
||||
else:
|
||||
echo(Constant.LOG_MSG_FAIL)
|
||||
# scan qrcode to login
|
||||
run(Constant.LOG_MSG_GET_UUID, self.getuuid)
|
||||
echo(Constant.LOG_MSG_GET_QRCODE)
|
||||
self.genqrcode()
|
||||
echo(Constant.LOG_MSG_SCAN_QRCODE)
|
||||
|
||||
if not self.waitforlogin():
|
||||
continue
|
||||
echo(Constant.LOG_MSG_CONFIRM_LOGIN)
|
||||
if not self.waitforlogin(0):
|
||||
continue
|
||||
break
|
||||
|
||||
run(Constant.LOG_MSG_LOGIN, self.login)
|
||||
run(Constant.LOG_MSG_INIT, self.webwxinit)
|
||||
run(Constant.LOG_MSG_STATUS_NOTIFY, self.webwxstatusnotify)
|
||||
run(Constant.LOG_MSG_GET_CONTACT, self.webwxgetcontact)
|
||||
echo(Constant.LOG_MSG_CONTACT_COUNT % (
|
||||
self.MemberCount, len(self.MemberList)
|
||||
))
|
||||
echo(Constant.LOG_MSG_OTHER_CONTACT_COUNT % (
|
||||
len(self.GroupList), len(self.ContactList),
|
||||
len(self.SpecialUsersList), len(self.PublicUsersList)
|
||||
))
|
||||
run(Constant.LOG_MSG_GET_GROUP_MEMBER, self.fetch_group_contacts)
|
||||
|
||||
run(Constant.LOG_MSG_SNAPSHOT, self.snapshot)
|
||||
|
||||
while True:
|
||||
[retcode, selector] = self.synccheck()
|
||||
Log.debug('retcode: %s, selector: %s' % (retcode, selector))
|
||||
self.exit_code = int(retcode)
|
||||
|
||||
if retcode == '1100':
|
||||
echo(Constant.LOG_MSG_LOGOUT)
|
||||
break
|
||||
if retcode == '1101':
|
||||
echo(Constant.LOG_MSG_LOGIN_OTHERWHERE)
|
||||
break
|
||||
if retcode == '1102':
|
||||
echo(Constant.LOG_MSG_QUIT_ON_PHONE)
|
||||
break
|
||||
elif retcode == '0':
|
||||
if selector == '2':
|
||||
r = self.webwxsync()
|
||||
if r is not None:
|
||||
try:
|
||||
self.handle_msg(r)
|
||||
except:
|
||||
Log.error(traceback.format_exc())
|
||||
elif selector == '7':
|
||||
r = self.webwxsync()
|
||||
elif selector == '0':
|
||||
time.sleep(self.time_out)
|
||||
elif selector == '4':
|
||||
# 保存群聊到通讯录
|
||||
# 修改群名称
|
||||
# 新增或删除联系人
|
||||
# 群聊成员数目变化
|
||||
r = self.webwxsync()
|
||||
if r is not None:
|
||||
try:
|
||||
self.handle_mod(r)
|
||||
except:
|
||||
Log.error(traceback.format_exc())
|
||||
elif selector == '3' or selector == '6':
|
||||
break
|
||||
else:
|
||||
r = self.webwxsync()
|
||||
Log.debug('webwxsync: %s\n' % json.dumps(r))
|
||||
|
||||
# 执行定时任务
|
||||
if self.msg_handler:
|
||||
self.msg_handler.check_schedule_task()
|
||||
|
||||
# if self.bot:
|
||||
# r = self.bot.time_schedule()
|
||||
# if r:
|
||||
# for g in self.GroupList:
|
||||
# echo('[*] 推送 -> %s: %s' % (g['NickName'], r))
|
||||
# g_id = g['UserName']
|
||||
# self.webwxsendmsg(r, g_id)
|
||||
|
||||
def get_run_time(self):
|
||||
"""
|
||||
@brief get how long this run
|
||||
@return String
|
||||
"""
|
||||
totalTime = int(time.time() - self.start_time)
|
||||
t = timedelta(seconds=totalTime)
|
||||
return '%s Day %s' % (t.days, t)
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
@brief Save some data and use shell to kill this process
|
||||
"""
|
||||
run(Constant.LOG_MSG_SNAPSHOT, self.snapshot)
|
||||
echo(Constant.LOG_MSG_RUNTIME % self.get_run_time())
|
||||
# close database connect
|
||||
self.db.close()
|
||||
|
||||
def fetch_group_contacts(self):
|
||||
"""
|
||||
@brief Fetches all groups contacts.
|
||||
@return Bool: whether operation succeed.
|
||||
@note This function must be finished in 180s
|
||||
"""
|
||||
Log.debug('fetch_group_contacts')
|
||||
# clean database
|
||||
if self.msg_handler:
|
||||
self.msg_handler.clean_db()
|
||||
|
||||
# sqlite
|
||||
# ----------------------------------------------------
|
||||
# group max_thread_num max_fetch_group_num time(s)
|
||||
# 197 10 10 108
|
||||
# 197 10 15 95
|
||||
# 197 20 10 103
|
||||
# 197 10 20 55
|
||||
# 197 5 30 39
|
||||
# 197 4 50 35
|
||||
# ----------------------------------------------------
|
||||
# mysql
|
||||
# ----------------------------------------------------
|
||||
# group max_thread_num max_fetch_group_num time(s)
|
||||
# 197 4 50 20
|
||||
# ----------------------------------------------------
|
||||
|
||||
max_thread_num = 4
|
||||
max_fetch_group_num = 50
|
||||
group_list_queue = Queue.Queue()
|
||||
|
||||
class GroupListThread(threading.Thread):
|
||||
|
||||
def __init__(self, group_list_queue, wechat):
|
||||
threading.Thread.__init__(self)
|
||||
self.group_list_queue = group_list_queue
|
||||
self.wechat = wechat
|
||||
|
||||
def run(self):
|
||||
while not self.group_list_queue.empty():
|
||||
g_list = self.group_list_queue.get()
|
||||
gid_list = []
|
||||
g_dict = {}
|
||||
for g in g_list:
|
||||
gid = g['UserName']
|
||||
gid_list.append(gid)
|
||||
g_dict[gid] = g
|
||||
|
||||
group_member_list = self.wechat.webwxbatchgetcontact(gid_list)
|
||||
|
||||
for member_list in group_member_list:
|
||||
gid = member_list['UserName']
|
||||
g = g_dict[gid]
|
||||
g['MemberCount'] = member_list['MemberCount']
|
||||
g['OwnerUin'] = member_list['OwnerUin']
|
||||
self.wechat.GroupMemeberList[gid] = member_list['MemberList']
|
||||
|
||||
# 如果使用 Mysql 则可以在多线程里操作数据库
|
||||
# 否则请注释下列代码在主线程里更新群列表
|
||||
# -----------------------------------
|
||||
# 处理群成员
|
||||
# if self.wechat.msg_handler:
|
||||
# self.wechat.msg_handler.handle_group_member_list(gid, member_list['MemberList'])
|
||||
# -----------------------------------
|
||||
|
||||
self.group_list_queue.task_done()
|
||||
|
||||
for g_list in split_array(self.GroupList, max_fetch_group_num):
|
||||
group_list_queue.put(g_list)
|
||||
|
||||
for i in range(max_thread_num):
|
||||
t = GroupListThread(group_list_queue, self)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
group_list_queue.join()
|
||||
|
||||
if self.msg_handler:
|
||||
# 处理群
|
||||
if self.GroupList:
|
||||
self.msg_handler.handle_group_list(self.GroupList)
|
||||
|
||||
# 这个是用 sqlite 来存储群列表,sqlite 对多线程的支持不太好
|
||||
# ----------------------------------------------------
|
||||
# 处理群成员
|
||||
for (gid, member_list) in self.GroupMemeberList.items():
|
||||
self.msg_handler.handle_group_member_list(gid, member_list)
|
||||
# ----------------------------------------------------
|
||||
|
||||
return True
|
||||
|
||||
def snapshot(self):
|
||||
"""
|
||||
@brief Save basic infos for next login.
|
||||
@return Bool: whether operation succeed.
|
||||
"""
|
||||
try:
|
||||
conf = {
|
||||
'uuid': self.uuid,
|
||||
'redirect_uri': self.redirect_uri,
|
||||
'uin': self.uin,
|
||||
'sid': self.sid,
|
||||
'skey': self.skey,
|
||||
'pass_ticket': self.pass_ticket,
|
||||
'synckey': self.synckey,
|
||||
'device_id': self.device_id,
|
||||
'last_login': time.time(),
|
||||
}
|
||||
cm = ConfigManager()
|
||||
Log.debug('save wechat config')
|
||||
cm.set_wechat_config(conf)
|
||||
|
||||
# save cookie
|
||||
Log.debug('save cookie')
|
||||
if self.cookie:
|
||||
self.cookie.save(ignore_discard=True)
|
||||
|
||||
# save contacts
|
||||
Log.debug('save contacts')
|
||||
self.save_contacts()
|
||||
except Exception, e:
|
||||
Log.error(traceback.format_exc())
|
||||
return False
|
||||
return True
|
||||
|
||||
def recover(self):
|
||||
"""
|
||||
@brief Recover from snapshot data.
|
||||
@return Bool: whether operation succeed.
|
||||
"""
|
||||
cm = ConfigManager()
|
||||
[self.uuid, self.redirect_uri, self.uin,
|
||||
self.sid, self.skey, self.pass_ticket,
|
||||
self.synckey, device_id, self.last_login] = cm.get_wechat_config()
|
||||
|
||||
if device_id:
|
||||
self.device_id = device_id
|
||||
|
||||
self.base_request = {
|
||||
'Uin': int(self.uin),
|
||||
'Sid': self.sid,
|
||||
'Skey': self.skey,
|
||||
'DeviceID': self.device_id,
|
||||
}
|
||||
|
||||
# set cookie
|
||||
Log.debug('set cookie')
|
||||
self.cookie = set_cookie(self.cookie_file)
|
||||
|
||||
return True
|
||||
|
||||
def save_contacts(self):
|
||||
"""
|
||||
@brief Save contacts.
|
||||
"""
|
||||
pickle_save(self.User, self.pickle_file['User'])
|
||||
pickle_save(self.MemberList, self.pickle_file['MemberList'])
|
||||
pickle_save(self.GroupList, self.pickle_file['GroupList'])
|
||||
pickle_save(self.GroupMemeberList, self.pickle_file['GroupMemeberList'])
|
||||
pickle_save(self.SpecialUsersList, self.pickle_file['SpecialUsersList'])
|
||||
|
||||
def recover_contacts(self):
|
||||
"""
|
||||
@brief recover contacts.
|
||||
@return Bool: whether operation succeed.
|
||||
"""
|
||||
try:
|
||||
self.User = pickle_load(self.pickle_file['User'])
|
||||
self.MemberList = pickle_load(self.pickle_file['MemberList'])
|
||||
self.GroupList = pickle_load(self.pickle_file['GroupList'])
|
||||
self.GroupMemeberList = pickle_load(self.pickle_file['GroupMemeberList'])
|
||||
self.SpecialUsersList = pickle_load(self.pickle_file['SpecialUsersList'])
|
||||
return True
|
||||
except Exception, e:
|
||||
Log.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
def handle_mod(self, r):
|
||||
# ModContactCount: 变更联系人或群聊成员数目
|
||||
# ModContactList: 变更联系人或群聊列表,或群名称改变
|
||||
Log.debug('handle modify')
|
||||
self.handle_msg(r)
|
||||
for m in r['ModContactList']:
|
||||
if m['UserName'][:2] == '@@':
|
||||
# group
|
||||
in_list = False
|
||||
g_id = m['UserName']
|
||||
for g in self.GroupList:
|
||||
# group member change
|
||||
if g_id == g['UserName']:
|
||||
g['MemberCount'] = m['MemberCount']
|
||||
g['NickName'] = m['NickName']
|
||||
self.GroupMemeberList[g_id] = m['MemberList']
|
||||
in_list = True
|
||||
if self.msg_handler:
|
||||
self.msg_handler.handle_group_member_change(g_id, m['MemberList'])
|
||||
break
|
||||
if not in_list:
|
||||
# a new group
|
||||
self.GroupList.append(m)
|
||||
self.GroupMemeberList[g_id] = m['MemberList']
|
||||
if self.msg_handler:
|
||||
self.msg_handler.handle_group_list_change(m)
|
||||
self.msg_handler.handle_group_member_change(g_id, m['MemberList'])
|
||||
|
||||
elif m['UserName'][0] == '@':
|
||||
# user
|
||||
in_list = False
|
||||
for u in self.MemberList:
|
||||
u_id = m['UserName']
|
||||
if u_id == u['UserName']:
|
||||
u = m
|
||||
in_list = True
|
||||
break
|
||||
# if don't have then add it
|
||||
if not in_list:
|
||||
self.MemberList.append(m)
|
||||
|
||||
def handle_msg(self, r):
|
||||
"""
|
||||
@brief Recover from snapshot data.
|
||||
@param r Dict: message json
|
||||
"""
|
||||
Log.debug('handle message')
|
||||
if self.msg_handler:
|
||||
self.msg_handler.handle_wxsync(r)
|
||||
|
||||
n = len(r['AddMsgList'])
|
||||
if n == 0:
|
||||
return
|
||||
|
||||
if self.log_mode:
|
||||
echo(Constant.LOG_MSG_NEW_MSG % n)
|
||||
|
||||
for msg in r['AddMsgList']:
|
||||
|
||||
msgType = msg['MsgType']
|
||||
msgId = msg['MsgId']
|
||||
content = msg['Content'].replace('<', '<').replace('>', '>')
|
||||
raw_msg = None
|
||||
|
||||
if msgType == self.wx_conf['MSGTYPE_TEXT']:
|
||||
# 地理位置消息
|
||||
if content.find('pictype=location') != -1:
|
||||
location = content.split('<br/>')[1][:-1]
|
||||
raw_msg = {
|
||||
'raw_msg': msg,
|
||||
'location': location,
|
||||
'log': Constant.LOG_MSG_LOCATION % location
|
||||
}
|
||||
# 普通文本消息
|
||||
else:
|
||||
text = content.split(':<br/>')[-1]
|
||||
raw_msg = {
|
||||
'raw_msg': msg,
|
||||
'text': text,
|
||||
'log': text.replace('<br/>', '\n')
|
||||
}
|
||||
elif msgType == self.wx_conf['MSGTYPE_IMAGE']:
|
||||
data = self.webwxgetmsgimg(msgId)
|
||||
fn = 'img_' + msgId + '.jpg'
|
||||
dir = self.save_data_folders['webwxgetmsgimg']
|
||||
path = save_file(fn, data, dir)
|
||||
raw_msg = {'raw_msg': msg,
|
||||
'image': path,
|
||||
'log': Constant.LOG_MSG_PICTURE % path}
|
||||
elif msgType == self.wx_conf['MSGTYPE_VOICE']:
|
||||
data = self.webwxgetvoice(msgId)
|
||||
fn = 'voice_' + msgId + '.mp3'
|
||||
dir = self.save_data_folders['webwxgetvoice']
|
||||
path = save_file(fn, data, dir)
|
||||
raw_msg = {'raw_msg': msg,
|
||||
'voice': path,
|
||||
'log': Constant.LOG_MSG_VOICE % path}
|
||||
elif msgType == self.wx_conf['MSGTYPE_SHARECARD']:
|
||||
info = msg['RecommendInfo']
|
||||
card = Constant.LOG_MSG_NAME_CARD % (
|
||||
info['NickName'],
|
||||
info['Alias'],
|
||||
info['Province'], info['City'],
|
||||
Constant.LOG_MSG_SEX_OPTION[info['Sex']]
|
||||
)
|
||||
namecard = '%s %s %s %s %s' % (
|
||||
info['NickName'], info['Alias'], info['Province'],
|
||||
info['City'], Constant.LOG_MSG_SEX_OPTION[info['Sex']]
|
||||
)
|
||||
raw_msg = {
|
||||
'raw_msg': msg,
|
||||
'namecard': namecard,
|
||||
'log': card
|
||||
}
|
||||
elif msgType == self.wx_conf['MSGTYPE_EMOTICON']:
|
||||
url = search_content('cdnurl', content)
|
||||
raw_msg = {'raw_msg': msg,
|
||||
'emoticon': url,
|
||||
'log': Constant.LOG_MSG_EMOTION % url}
|
||||
elif msgType == self.wx_conf['MSGTYPE_APP']:
|
||||
card = ''
|
||||
# 链接, 音乐, 微博
|
||||
if msg['AppMsgType'] in [
|
||||
self.wx_conf['APPMSGTYPE_AUDIO'],
|
||||
self.wx_conf['APPMSGTYPE_URL'],
|
||||
self.wx_conf['APPMSGTYPE_OPEN']
|
||||
]:
|
||||
card = Constant.LOG_MSG_APP_LINK % (
|
||||
Constant.LOG_MSG_APP_LINK_TYPE[msg['AppMsgType']],
|
||||
msg['FileName'],
|
||||
search_content('des', content, 'xml'),
|
||||
msg['Url'],
|
||||
search_content('appname', content, 'xml')
|
||||
)
|
||||
raw_msg = {
|
||||
'raw_msg': msg,
|
||||
'link': msg['Url'],
|
||||
'log': card
|
||||
}
|
||||
# 图片
|
||||
elif msg['AppMsgType'] == self.wx_conf['APPMSGTYPE_IMG']:
|
||||
data = self.webwxgetmsgimg(msgId)
|
||||
fn = 'img_' + msgId + '.jpg'
|
||||
dir = self.save_data_folders['webwxgetmsgimg']
|
||||
path = save_file(fn, data, dir)
|
||||
card = Constant.LOG_MSG_APP_IMG % (
|
||||
path,
|
||||
search_content('appname', content, 'xml')
|
||||
)
|
||||
raw_msg = {
|
||||
'raw_msg': msg,
|
||||
'image': path,
|
||||
'log': card
|
||||
}
|
||||
else:
|
||||
raw_msg = {
|
||||
'raw_msg': msg,
|
||||
'log': Constant.LOG_MSG_UNKNOWN_MSG % (msgType, content)
|
||||
}
|
||||
elif msgType == self.wx_conf['MSGTYPE_STATUSNOTIFY']:
|
||||
Log.info(Constant.LOG_MSG_NOTIFY_PHONE)
|
||||
elif msgType == self.wx_conf['MSGTYPE_MICROVIDEO']:
|
||||
data = self.webwxgetvideo(msgId)
|
||||
fn = 'video_' + msgId + '.mp4'
|
||||
dir = self.save_data_folders['webwxgetvideo']
|
||||
path = save_file(fn, data, dir)
|
||||
raw_msg = {'raw_msg': msg,
|
||||
'video': path,
|
||||
'log': Constant.LOG_MSG_VIDEO % path}
|
||||
elif msgType == self.wx_conf['MSGTYPE_RECALLED']:
|
||||
recall_id = search_content('msgid', content, 'xml')
|
||||
text = Constant.LOG_MSG_RECALL
|
||||
raw_msg = {
|
||||
'raw_msg': msg,
|
||||
'text': text,
|
||||
'recall_msg_id': recall_id,
|
||||
'log': text
|
||||
}
|
||||
elif msgType == self.wx_conf['MSGTYPE_SYS']:
|
||||
raw_msg = {
|
||||
'raw_msg': msg,
|
||||
'sys_notif': content,
|
||||
'log': content
|
||||
}
|
||||
elif msgType == self.wx_conf['MSGTYPE_VERIFYMSG']:
|
||||
name = search_content('fromnickname', content)
|
||||
raw_msg = {
|
||||
'raw_msg': msg,
|
||||
'log': Constant.LOG_MSG_ADD_FRIEND % name
|
||||
}
|
||||
else:
|
||||
raw_msg = {
|
||||
'raw_msg': msg,
|
||||
'log': Constant.LOG_MSG_UNKNOWN_MSG % (msgType, content)
|
||||
}
|
||||
|
||||
isGroupMsg = '@@' in msg['FromUserName']+msg['ToUserName']
|
||||
if self.msg_handler and raw_msg:
|
||||
if isGroupMsg:
|
||||
# handle group messages
|
||||
g_msg = self.make_group_msg(raw_msg)
|
||||
self.msg_handler.handle_group_msg(g_msg)
|
||||
else:
|
||||
# handle personal messages
|
||||
self.msg_handler.handle_user_msg(raw_msg)
|
||||
|
||||
if self.log_mode:
|
||||
self.show_msg(raw_msg)
|
||||
|
||||
def make_group_msg(self, msg):
|
||||
"""
|
||||
@brief Package the group message for storage.
|
||||
@param msg Dict: raw msg
|
||||
@return raw_msg Dict: packged msg
|
||||
"""
|
||||
Log.debug('make group message')
|
||||
raw_msg = {
|
||||
'raw_msg': msg['raw_msg'],
|
||||
'msg_id': msg['raw_msg']['MsgId'],
|
||||
'group_owner_uin': '',
|
||||
'group_name': '',
|
||||
'group_count': '',
|
||||
'from_user_name': msg['raw_msg']['FromUserName'],
|
||||
'to_user_name': msg['raw_msg']['ToUserName'],
|
||||
'user_attrstatus': '',
|
||||
'user_display_name': '',
|
||||
'user_nickname': '',
|
||||
'msg_type': msg['raw_msg']['MsgType'],
|
||||
'text': '',
|
||||
'link': '',
|
||||
'image': '',
|
||||
'video': '',
|
||||
'voice': '',
|
||||
'emoticon': '',
|
||||
'namecard': '',
|
||||
'location': '',
|
||||
'recall_msg_id': '',
|
||||
'sys_notif': '',
|
||||
'time': '',
|
||||
'timestamp': '',
|
||||
'log': '',
|
||||
}
|
||||
content = msg['raw_msg']['Content'].replace(
|
||||
'<', '<').replace('>', '>')
|
||||
|
||||
group = None
|
||||
src = None
|
||||
|
||||
if msg['raw_msg']['FromUserName'][:2] == '@@':
|
||||
# 接收到来自群的消息
|
||||
g_id = msg['raw_msg']['FromUserName']
|
||||
group = self.get_group_by_id(g_id)
|
||||
|
||||
if re.search(":<br/>", content, re.IGNORECASE):
|
||||
u_id = content.split(':<br/>')[0]
|
||||
src = self.get_group_user_by_id(u_id, g_id)
|
||||
|
||||
elif msg['raw_msg']['ToUserName'][:2] == '@@':
|
||||
# 自己发给群的消息
|
||||
g_id = msg['raw_msg']['ToUserName']
|
||||
u_id = msg['raw_msg']['FromUserName']
|
||||
src = self.get_group_user_by_id(u_id, g_id)
|
||||
group = self.get_group_by_id(g_id)
|
||||
|
||||
if src:
|
||||
raw_msg['user_attrstatus'] = src['AttrStatus']
|
||||
raw_msg['user_display_name'] = src['DisplayName']
|
||||
raw_msg['user_nickname'] = src['NickName']
|
||||
if group:
|
||||
raw_msg['group_count'] = group['MemberCount']
|
||||
raw_msg['group_owner_uin'] = group['OwnerUin']
|
||||
raw_msg['group_name'] = group['ShowName']
|
||||
|
||||
raw_msg['timestamp'] = msg['raw_msg']['CreateTime']
|
||||
t = time.localtime(float(raw_msg['timestamp']))
|
||||
raw_msg['time'] = time.strftime("%Y-%m-%d %T", t)
|
||||
|
||||
for key in [
|
||||
'text', 'link', 'image', 'video', 'voice',
|
||||
'emoticon', 'namecard', 'location', 'log',
|
||||
'recall_msg_id', 'sys_notif'
|
||||
]:
|
||||
if key in msg:
|
||||
raw_msg[key] = msg[key]
|
||||
|
||||
return raw_msg
|
||||
|
||||
def show_msg(self, message):
|
||||
"""
|
||||
@brief Log the message to stdout
|
||||
@param message Dict
|
||||
"""
|
||||
msg = message
|
||||
src = None
|
||||
dst = None
|
||||
group = None
|
||||
|
||||
if msg and msg['raw_msg']:
|
||||
|
||||
content = msg['raw_msg']['Content']
|
||||
content = content.replace('<', '<').replace('>', '>')
|
||||
msg_id = msg['raw_msg']['MsgId']
|
||||
|
||||
if msg['raw_msg']['FromUserName'][:2] == '@@':
|
||||
# 接收到来自群的消息
|
||||
g_id = msg['raw_msg']['FromUserName']
|
||||
group = self.get_group_by_id(g_id)
|
||||
|
||||
if re.search(":<br/>", content, re.IGNORECASE):
|
||||
u_id = content.split(':<br/>')[0]
|
||||
src = self.get_group_user_by_id(u_id, g_id)
|
||||
dst = {'ShowName': 'GROUP'}
|
||||
else:
|
||||
u_id = msg['raw_msg']['ToUserName']
|
||||
src = {'ShowName': 'SYSTEM'}
|
||||
dst = self.get_group_user_by_id(u_id, g_id)
|
||||
elif msg['raw_msg']['ToUserName'][:2] == '@@':
|
||||
# 自己发给群的消息
|
||||
g_id = msg['raw_msg']['ToUserName']
|
||||
u_id = msg['raw_msg']['FromUserName']
|
||||
group = self.get_group_by_id(g_id)
|
||||
src = self.get_group_user_by_id(u_id, g_id)
|
||||
dst = {'ShowName': 'GROUP'}
|
||||
else:
|
||||
# 非群聊消息
|
||||
src = self.get_user_by_id(msg['raw_msg']['FromUserName'])
|
||||
dst = self.get_user_by_id(msg['raw_msg']['ToUserName'])
|
||||
|
||||
if group:
|
||||
echo('%s |%s| %s -> %s: %s\n' % (
|
||||
msg_id,
|
||||
trans_emoji(group['ShowName']),
|
||||
trans_emoji(src['ShowName']),
|
||||
dst['ShowName'],
|
||||
trans_emoji(msg['log'])
|
||||
))
|
||||
else:
|
||||
echo('%s %s -> %s: %s\n' % (
|
||||
msg_id,
|
||||
trans_emoji(src['ShowName']),
|
||||
trans_emoji(dst['ShowName']),
|
||||
trans_emoji(msg['log'])
|
||||
))
|
||||
@@ -0,0 +1,969 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
#===================================================
|
||||
from utils import *
|
||||
from config import Constant
|
||||
from config import Log
|
||||
#---------------------------------------------------
|
||||
import sys
|
||||
import os
|
||||
import cookielib
|
||||
import random
|
||||
import requests
|
||||
import time
|
||||
import xml.dom.minidom
|
||||
# for media upload
|
||||
import mimetypes
|
||||
from requests_toolbelt.multipart.encoder import MultipartEncoder
|
||||
#===================================================
|
||||
|
||||
|
||||
class WXAPI(object):
|
||||
|
||||
def __init__(self, host):
|
||||
self.wx_host = host
|
||||
self.wx_filehost = ''
|
||||
self.wx_conf = {}
|
||||
# jsLogin时这个appid只能使用: wx782c26e4c19acffb
|
||||
self.appid = Constant.API_APPID
|
||||
self.uuid = ''
|
||||
self.redirect_uri = ''
|
||||
self.skey = ''
|
||||
self.sid = ''
|
||||
self.uin = ''
|
||||
self.pass_ticket = ''
|
||||
self.base_request = {}
|
||||
self.synckey_dic = {}
|
||||
self.synckey = ''
|
||||
self.device_id = 'e' + repr(random.random())[2:17]
|
||||
# device_id: 登录手机设备
|
||||
# web wechat 的格式为: e123456789012345 (e+15位随机数)
|
||||
# mobile wechat 的格式为: A1234567890abcde (A+15位随机数字或字母)
|
||||
self.user_agent = Constant.API_USER_AGENT
|
||||
self.cookie = None
|
||||
|
||||
self.conf_factory()
|
||||
|
||||
self.User = [] # 登陆账号信息
|
||||
self.MemberList = [] # 好友+群聊+公众号+特殊账号
|
||||
self.MemberCount = 0
|
||||
self.ContactList = [] # 好友
|
||||
self.GroupList = [] # 群
|
||||
self.GroupMemeberList = {} # 群聊成员字典
|
||||
# "group_id": [
|
||||
# {member}, ...
|
||||
# ]
|
||||
self.PublicUsersList = [] # 公众号/服务号
|
||||
self.SpecialUsersList = [] # 特殊账号
|
||||
|
||||
self.media_count = 0
|
||||
|
||||
def conf_factory(self):
|
||||
e = self.wx_host # wx.qq.com
|
||||
t, o, n = "login.weixin.qq.com", "file.wx.qq.com", "webpush.weixin.qq.com"
|
||||
|
||||
if e.find("wx2.qq.com") > -1:
|
||||
t, o, n = "login.wx2.qq.com", "file.wx2.qq.com", "webpush.wx2.qq.com"
|
||||
elif e.find("wx8.qq.com") > -1:
|
||||
t, o, n = "login.wx8.qq.com", "file.wx8.qq.com", "webpush.wx8.qq.com"
|
||||
elif e.find("qq.com") > -1:
|
||||
t, o, n = "login.wx.qq.com", "file.wx.qq.com", "webpush.wx.qq.com"
|
||||
elif e.find("web2.wechat.com") > -1:
|
||||
t, o, n = "login.web2.wechat.com", "file.web2.wechat.com", "webpush.web2.wechat.com"
|
||||
elif e.find("wechat.com") > -1:
|
||||
t, o, n = "login.web.wechat.com", "file.web.wechat.com", "webpush.web.wechat.com"
|
||||
|
||||
self.wx_filehost = o
|
||||
conf = {
|
||||
'LANG': Constant.API_LANG,
|
||||
'SpecialUsers': Constant.API_SPECIAL_USER,
|
||||
'API_jsLogin': "https://" + t + "/jslogin",
|
||||
'API_qrcode': "https://login.weixin.qq.com/l/",
|
||||
'API_qrcode_img': "https://login.weixin.qq.com/qrcode/",
|
||||
'API_login': "https://" + t + "/cgi-bin/mmwebwx-bin/login",
|
||||
'API_synccheck': "https://" + n + "/cgi-bin/mmwebwx-bin/synccheck",
|
||||
'API_webwxdownloadmedia': "https://" + o + "/cgi-bin/mmwebwx-bin/webwxgetmedia",
|
||||
'API_webwxuploadmedia': "https://" + o + "/cgi-bin/mmwebwx-bin/webwxuploadmedia",
|
||||
'API_webwxpreview': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxpreview",
|
||||
'API_webwxinit': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxinit",
|
||||
'API_webwxgetcontact': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxgetcontact",
|
||||
'API_webwxsync': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxsync",
|
||||
'API_webwxbatchgetcontact': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxbatchgetcontact",
|
||||
'API_webwxgeticon': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxgeticon",
|
||||
'API_webwxsendmsg': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxsendmsg",
|
||||
'API_webwxsendmsgimg': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxsendmsgimg",
|
||||
'API_webwxsendmsgvedio': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxsendvideomsg",
|
||||
'API_webwxsendemoticon': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxsendemoticon",
|
||||
'API_webwxsendappmsg': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxsendappmsg",
|
||||
'API_webwxgetheadimg': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxgetheadimg",
|
||||
'API_webwxgetmsgimg': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxgetmsgimg",
|
||||
'API_webwxgetmedia': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxgetmedia",
|
||||
'API_webwxgetvideo': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxgetvideo",
|
||||
'API_webwxlogout': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxlogout",
|
||||
'API_webwxgetvoice': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxgetvoice",
|
||||
'API_webwxupdatechatroom': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxupdatechatroom",
|
||||
'API_webwxcreatechatroom': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxcreatechatroom",
|
||||
'API_webwxstatusnotify': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxstatusnotify",
|
||||
'API_webwxcheckurl': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxcheckurl",
|
||||
'API_webwxverifyuser': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxverifyuser",
|
||||
'API_webwxfeedback': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxsendfeedback",
|
||||
'API_webwxreport': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxstatreport",
|
||||
'API_webwxsearch': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxsearchcontact",
|
||||
'API_webwxoplog': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxoplog",
|
||||
'API_checkupload': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxcheckupload",
|
||||
'API_webwxrevokemsg': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxrevokemsg",
|
||||
'API_webwxpushloginurl': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxpushloginurl",
|
||||
'CONTACTFLAG_CONTACT': 1,
|
||||
'CONTACTFLAG_CHATCONTACT': 2,
|
||||
'CONTACTFLAG_CHATROOMCONTACT': 4,
|
||||
'CONTACTFLAG_BLACKLISTCONTACT': 8,
|
||||
'CONTACTFLAG_DOMAINCONTACT': 16,
|
||||
'CONTACTFLAG_HIDECONTACT': 32,
|
||||
'CONTACTFLAG_FAVOURCONTACT': 64,
|
||||
'CONTACTFLAG_3RDAPPCONTACT': 128,
|
||||
'CONTACTFLAG_SNSBLACKLISTCONTACT': 256,
|
||||
'CONTACTFLAG_NOTIFYCLOSECONTACT': 512,
|
||||
'CONTACTFLAG_TOPCONTACT': 2048,
|
||||
'MSGTYPE_TEXT': 1,
|
||||
'MSGTYPE_IMAGE': 3,
|
||||
'MSGTYPE_VOICE': 34,
|
||||
'MSGTYPE_VIDEO': 43,
|
||||
'MSGTYPE_MICROVIDEO': 62,
|
||||
'MSGTYPE_EMOTICON': 47,
|
||||
'MSGTYPE_APP': 49,
|
||||
'MSGTYPE_VOIPMSG': 50,
|
||||
'MSGTYPE_VOIPNOTIFY': 52,
|
||||
'MSGTYPE_VOIPINVITE': 53,
|
||||
'MSGTYPE_LOCATION': 48,
|
||||
'MSGTYPE_STATUSNOTIFY': 51,
|
||||
'MSGTYPE_SYSNOTICE': 9999,
|
||||
'MSGTYPE_POSSIBLEFRIEND_MSG': 40,
|
||||
'MSGTYPE_VERIFYMSG': 37,
|
||||
'MSGTYPE_SHARECARD': 42,
|
||||
'MSGTYPE_SYS': 10000,
|
||||
'MSGTYPE_RECALLED': 10002,
|
||||
'APPMSGTYPE_TEXT': 1,
|
||||
'APPMSGTYPE_IMG': 2,
|
||||
'APPMSGTYPE_AUDIO': 3,
|
||||
'APPMSGTYPE_VIDEO': 4,
|
||||
'APPMSGTYPE_URL': 5,
|
||||
'APPMSGTYPE_ATTACH': 6,
|
||||
'APPMSGTYPE_OPEN': 7,
|
||||
'APPMSGTYPE_EMOJI': 8,
|
||||
'APPMSGTYPE_VOICE_REMIND': 9,
|
||||
'APPMSGTYPE_SCAN_GOOD': 10,
|
||||
'APPMSGTYPE_GOOD': 13,
|
||||
'APPMSGTYPE_EMOTION': 15,
|
||||
'APPMSGTYPE_CARD_TICKET': 16,
|
||||
'APPMSGTYPE_REALTIME_SHARE_LOCATION': 17,
|
||||
'APPMSGTYPE_TRANSFERS': 2e3,
|
||||
'APPMSGTYPE_RED_ENVELOPES': 2001,
|
||||
'APPMSGTYPE_READER_TYPE': 100001,
|
||||
'UPLOAD_MEDIA_TYPE_IMAGE': 1,
|
||||
'UPLOAD_MEDIA_TYPE_VIDEO': 2,
|
||||
'UPLOAD_MEDIA_TYPE_AUDIO': 3,
|
||||
'UPLOAD_MEDIA_TYPE_ATTACHMENT': 4,
|
||||
}
|
||||
self.wx_conf = conf
|
||||
|
||||
def getuuid(self):
|
||||
"""
|
||||
@brief Gets the uuid just used for login.
|
||||
@return Bool: whether operation succeed.
|
||||
"""
|
||||
url = self.wx_conf['API_jsLogin']
|
||||
params = {
|
||||
'appid': self.appid,
|
||||
'fun': 'new',
|
||||
'lang': self.wx_conf['LANG'],
|
||||
'_': int(time.time()),
|
||||
}
|
||||
data = 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):
|
||||
"""
|
||||
@brief outprint the qrcode to stdout on macos/linux
|
||||
or open image on windows
|
||||
"""
|
||||
if sys.platform.startswith('win'):
|
||||
url = self.wx_conf['API_qrcode_img'] + self.uuid
|
||||
params = {
|
||||
't': 'webwx',
|
||||
'_': int(time.time())
|
||||
}
|
||||
data = post(url, params, False)
|
||||
if data == '':
|
||||
return
|
||||
qrcode_path = save_file('qrcode.jpg', data, './')
|
||||
os.startfile(qrcode_path)
|
||||
else:
|
||||
str2qr_terminal(self.wx_conf['API_qrcode'] + self.uuid)
|
||||
|
||||
def waitforlogin(self, tip=1):
|
||||
"""
|
||||
@brief wait for scaning qrcode to login
|
||||
@param tip 1: wait for scan qrcode
|
||||
0: wait for confirm
|
||||
@return Bool: whether operation succeed
|
||||
"""
|
||||
time.sleep(tip)
|
||||
url = self.wx_conf['API_login'] + '?tip=%s&uuid=%s&_=%s' % (
|
||||
tip, self.uuid, int(time.time()))
|
||||
data = 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.wx_host = r_uri.split('://')[1].split('/')[0]
|
||||
self.conf_factory()
|
||||
return True
|
||||
elif code == '408':
|
||||
echo(Constant.LOG_MSG_WAIT_LOGIN_ERR1)
|
||||
else:
|
||||
echo(Constant.LOG_MSG_WAIT_LOGIN_ERR2)
|
||||
return False
|
||||
|
||||
def login(self):
|
||||
"""
|
||||
@brief login
|
||||
redirect_uri 有效时间是从扫码成功后算起,
|
||||
大概是 300 秒,在此期间可以重新登录,但获取的联系人和群ID会改变
|
||||
@return Bool: whether operation succeed
|
||||
"""
|
||||
data = get(self.redirect_uri)
|
||||
doc = xml.dom.minidom.parseString(data)
|
||||
root = doc.documentElement
|
||||
|
||||
for node in root.childNodes:
|
||||
if node.nodeName == 'ret':
|
||||
if node.childNodes[0].data != "0":
|
||||
return False
|
||||
elif 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.base_request = {
|
||||
'Uin': int(self.uin),
|
||||
'Sid': self.sid,
|
||||
'Skey': self.skey,
|
||||
'DeviceID': self.device_id,
|
||||
}
|
||||
|
||||
return True
|
||||
|
||||
def webwxinit(self):
|
||||
"""
|
||||
@brief wechat initial
|
||||
掉线后 300 秒可以重新使用此 api 登录
|
||||
获取的联系人和群ID保持不变
|
||||
@return Bool: whether operation succeed
|
||||
"""
|
||||
url = self.wx_conf['API_webwxinit'] + \
|
||||
'?pass_ticket=%s&skey=%s&r=%s' % (
|
||||
self.pass_ticket, self.skey, int(time.time())
|
||||
)
|
||||
params = {
|
||||
'BaseRequest': self.base_request
|
||||
}
|
||||
dic = post(url, params)
|
||||
self.User = dic['User']
|
||||
self.make_synckey(dic)
|
||||
|
||||
return dic['BaseResponse']['Ret'] == 0
|
||||
|
||||
def webwxstatusnotify(self):
|
||||
"""
|
||||
@brief notify the mobile phone, this not necessary
|
||||
@return Bool: whether operation succeed
|
||||
"""
|
||||
url = self.wx_conf['API_webwxstatusnotify'] + \
|
||||
'?lang=%s&pass_ticket=%s' % (
|
||||
self.wx_conf['LANG'], self.pass_ticket
|
||||
)
|
||||
params = {
|
||||
'BaseRequest': self.base_request,
|
||||
"Code": 3,
|
||||
"FromUserName": self.User['UserName'],
|
||||
"ToUserName": self.User['UserName'],
|
||||
"ClientMsgId": int(time.time())
|
||||
}
|
||||
dic = post(url, params)
|
||||
|
||||
return dic['BaseResponse']['Ret'] == 0
|
||||
|
||||
def webwxgetcontact(self):
|
||||
"""
|
||||
@brief get all contacts: people, group, public user, special user
|
||||
@return Bool: whether operation succeed
|
||||
"""
|
||||
SpecialUsers = self.wx_conf['SpecialUsers']
|
||||
url = self.wx_conf['API_webwxgetcontact'] + \
|
||||
'?pass_ticket=%s&skey=%s&r=%s' % (
|
||||
self.pass_ticket, self.skey, int(time.time())
|
||||
)
|
||||
dic = post(url, {})
|
||||
|
||||
self.MemberCount = dic['MemberCount']
|
||||
self.MemberList = dic['MemberList']
|
||||
ContactList = self.MemberList[:]
|
||||
GroupList = self.GroupList[:]
|
||||
PublicUsersList = self.PublicUsersList[:]
|
||||
SpecialUsersList = self.SpecialUsersList[:]
|
||||
|
||||
for i in xrange(len(ContactList) - 1, -1, -1):
|
||||
Contact = ContactList[i]
|
||||
if Contact['VerifyFlag'] & 8 != 0: # 公众号/服务号
|
||||
ContactList.remove(Contact)
|
||||
self.PublicUsersList.append(Contact)
|
||||
elif Contact['UserName'] in SpecialUsers: # 特殊账号
|
||||
ContactList.remove(Contact)
|
||||
self.SpecialUsersList.append(Contact)
|
||||
elif Contact['UserName'].find('@@') != -1: # 群聊
|
||||
ContactList.remove(Contact)
|
||||
self.GroupList.append(Contact)
|
||||
elif Contact['UserName'] == self.User['UserName']: # 自己
|
||||
ContactList.remove(Contact)
|
||||
self.ContactList = ContactList
|
||||
|
||||
return True
|
||||
|
||||
def webwxbatchgetcontact(self, gid_list):
|
||||
"""
|
||||
@brief get group contacts
|
||||
@param gid_list, The list of group id
|
||||
@return List, list of group contacts
|
||||
"""
|
||||
url = self.wx_conf['API_webwxbatchgetcontact'] + \
|
||||
'?type=ex&r=%s&pass_ticket=%s' % (
|
||||
int(time.time()), self.pass_ticket
|
||||
)
|
||||
params = {
|
||||
'BaseRequest': self.base_request,
|
||||
"Count": len(gid_list),
|
||||
"List": [{"UserName": gid, "EncryChatRoomId": ""} for gid in gid_list]
|
||||
}
|
||||
dic = post(url, params)
|
||||
return dic['ContactList']
|
||||
|
||||
def synccheck(self):
|
||||
"""
|
||||
@brief check whether there's a message
|
||||
@return [retcode, selector]
|
||||
retcode: 0 successful
|
||||
1100 logout
|
||||
1101 login otherwhere
|
||||
selector: 0 nothing
|
||||
2 message
|
||||
6 unkonwn
|
||||
7 webwxsync
|
||||
"""
|
||||
params = {
|
||||
'r': int(time.time()),
|
||||
'sid': self.sid,
|
||||
'uin': self.uin,
|
||||
'skey': self.skey,
|
||||
'deviceid': self.device_id,
|
||||
'synckey': self.synckey,
|
||||
'_': int(time.time()),
|
||||
}
|
||||
url = self.wx_conf['API_synccheck'] + '?' + urllib.urlencode(params)
|
||||
data = get(url)
|
||||
reg = r'window.synccheck={retcode:"(\d+)",selector:"(\d+)"}'
|
||||
pm = re.search(reg, data)
|
||||
retcode = pm.group(1)
|
||||
selector = pm.group(2)
|
||||
return [retcode, selector]
|
||||
|
||||
def webwxsync(self):
|
||||
"""
|
||||
@brief sync the messages
|
||||
@return Dict{}
|
||||
"""
|
||||
url = self.wx_conf['API_webwxsync'] + \
|
||||
'?sid=%s&skey=%s&pass_ticket=%s' % (
|
||||
self.sid, self.skey, self.pass_ticket
|
||||
)
|
||||
params = {
|
||||
'BaseRequest': self.base_request,
|
||||
'SyncKey': self.synckey_dic,
|
||||
'rr': ~int(time.time())
|
||||
}
|
||||
dic = post(url, params)
|
||||
|
||||
if dic['BaseResponse']['Ret'] == 0:
|
||||
self.make_synckey(dic)
|
||||
return dic
|
||||
|
||||
def webwxgetmsgimg(self, msgid):
|
||||
"""
|
||||
@brief get image in message
|
||||
@param msgid The id of message
|
||||
@return binary data of image
|
||||
"""
|
||||
url = self.wx_conf['API_webwxgetmsgimg'] + \
|
||||
'?MsgID=%s&skey=%s' % (msgid, self.skey)
|
||||
data = get(url, api='webwxgetmsgimg')
|
||||
return data
|
||||
|
||||
def webwxgetvoice(self, msgid):
|
||||
"""
|
||||
@brief get voice in message
|
||||
@param msgid The id of message
|
||||
@return binary data of voice
|
||||
"""
|
||||
url = self.wx_conf['API_webwxgetvoice'] + \
|
||||
'?msgid=%s&skey=%s' % (msgid, self.skey)
|
||||
data = get(url, api='webwxgetvoice')
|
||||
return data
|
||||
|
||||
def webwxgetvideo(self, msgid):
|
||||
"""
|
||||
@brief get video in message
|
||||
@param msgid The id of message
|
||||
@return binary data of video
|
||||
"""
|
||||
url = self.wx_conf['API_webwxgetvideo'] + \
|
||||
'?msgid=%s&skey=%s' % (msgid, self.skey)
|
||||
data = get(url, api='webwxgetvideo')
|
||||
return data
|
||||
|
||||
def webwxgeticon(self, id):
|
||||
"""
|
||||
@brief get user small icon
|
||||
@param id String
|
||||
@return binary data of icon
|
||||
"""
|
||||
url = self.wx_conf['API_webwxgeticon'] + \
|
||||
'?username=%s&skey=%s' % (id, self.skey)
|
||||
data = get(url, api='webwxgeticon')
|
||||
return data
|
||||
|
||||
def webwxgetheadimg(self, id):
|
||||
"""
|
||||
@brief get user head image
|
||||
@param id String
|
||||
@return binary data of image
|
||||
"""
|
||||
url = self.wx_conf['API_webwxgetheadimg'] + \
|
||||
'?username=%s&skey=%s' % (id, self.skey)
|
||||
data = get(url, api='webwxgetheadimg')
|
||||
return data
|
||||
|
||||
def webwxsendmsg(self, word, to='filehelper'):
|
||||
"""
|
||||
@brief send text message
|
||||
@param word String
|
||||
@param to User id
|
||||
@return dic Dict
|
||||
"""
|
||||
url = self.wx_conf['API_webwxsendmsg'] + \
|
||||
'?pass_ticket=%s' % (self.pass_ticket)
|
||||
clientMsgId = str(int(time.time() * 1000)) + \
|
||||
str(random.random())[:5].replace('.', '')
|
||||
params = {
|
||||
'BaseRequest': self.base_request,
|
||||
'Msg': {
|
||||
"Type": 1,
|
||||
"Content": trans_coding(word),
|
||||
"FromUserName": self.User['UserName'],
|
||||
"ToUserName": to,
|
||||
"LocalID": clientMsgId,
|
||||
"ClientMsgId": clientMsgId
|
||||
}
|
||||
}
|
||||
dic = post(url, params)
|
||||
return dic
|
||||
|
||||
def webwxuploadmedia(self, file_path):
|
||||
"""
|
||||
@brief upload image
|
||||
@param file_path String
|
||||
@return Dict: json
|
||||
"""
|
||||
url = self.wx_conf['API_webwxuploadmedia'] + '?f=json'
|
||||
# 计数器
|
||||
self.media_count = self.media_count + 1
|
||||
fn = file_path
|
||||
# mime_type:
|
||||
# 'application/pdf'
|
||||
# 'image/jpeg'
|
||||
# 'image/png'
|
||||
# ...
|
||||
mime_type = mimetypes.guess_type(fn, strict=False)[0]
|
||||
if not mime_type:
|
||||
mime_type = 'text/plain'
|
||||
# 文档格式
|
||||
# 微信服务器目前应该支持3种类型:
|
||||
# pic 直接显示,包含图片,表情
|
||||
# video 不清楚
|
||||
# doc 显示为文件,包含PDF等
|
||||
media_type = 'pic' if mime_type.split('/')[0] == 'image' else 'doc'
|
||||
time_format = "%a %b %d %Y %T GMT%z (%Z)"
|
||||
last_modifie_date = time.strftime(time_format, time.localtime())
|
||||
file_size = os.path.getsize(fn)
|
||||
pass_ticket = self.pass_ticket
|
||||
client_media_id = str(int(time.time() * 1000)) + \
|
||||
str(random.random())[:5].replace('.', '')
|
||||
|
||||
webwx_data_ticket = ''
|
||||
for item in self.cookie:
|
||||
if item.name == 'webwx_data_ticket':
|
||||
webwx_data_ticket = item.value
|
||||
break
|
||||
if (webwx_data_ticket == ''):
|
||||
Log.error("No Cookie\n")
|
||||
return None
|
||||
|
||||
uploadmediarequest = json.dumps({
|
||||
"BaseRequest": self.base_request,
|
||||
"ClientMediaId": client_media_id,
|
||||
"TotalLen": file_size,
|
||||
"StartPos": 0,
|
||||
"DataLen": file_size,
|
||||
"MediaType": 4
|
||||
}, ensure_ascii=False).encode('utf8')
|
||||
|
||||
multipart_encoder = MultipartEncoder(
|
||||
fields={
|
||||
'id': 'WU_FILE_' + str(self.media_count),
|
||||
'name': fn,
|
||||
'type': mime_type,
|
||||
'lastModifieDate': last_modifie_date,
|
||||
'size': str(file_size),
|
||||
'mediatype': media_type,
|
||||
'uploadmediarequest': uploadmediarequest,
|
||||
'webwx_data_ticket': webwx_data_ticket,
|
||||
'pass_ticket': pass_ticket,
|
||||
'filename': (
|
||||
fn,
|
||||
open(fn, 'rb'),
|
||||
mime_type.split('/')[1]
|
||||
)
|
||||
},
|
||||
boundary=(
|
||||
'-----------------------------'
|
||||
'1575017231431605357584454111'
|
||||
)
|
||||
)
|
||||
|
||||
headers = {
|
||||
'Host': self.wx_filehost,
|
||||
'User-Agent': self.user_agent,
|
||||
'Accept': (
|
||||
'text/html,application/xhtml+xml,'
|
||||
'application/xml;q=0.9,*/*;q=0.8'
|
||||
),
|
||||
'Accept-Language': 'en-US,en;q=0.5',
|
||||
'Accept-Encoding': 'gzip, deflate',
|
||||
'Referer': 'https://' + self.wx_host,
|
||||
'Content-Type': multipart_encoder.content_type,
|
||||
'Origin': 'https://' + self.wx_host,
|
||||
'Connection': 'keep-alive',
|
||||
'Pragma': 'no-cache',
|
||||
'Cache-Control': 'no-cache',
|
||||
}
|
||||
|
||||
r = requests.post(url, data=multipart_encoder, headers=headers)
|
||||
if dic['BaseResponse']['Ret'] == 0:
|
||||
return dic
|
||||
return None
|
||||
|
||||
def webwxsendmsgimg(self, user_id, media_id):
|
||||
"""
|
||||
@brief send image
|
||||
@param user_id String
|
||||
@param media_id String
|
||||
@return Bool: whether operation succeed
|
||||
"""
|
||||
url = self.wx_conf['API_webwxsendmsgimg'] + \
|
||||
'?fun=async&f=json&pass_ticket=%s' % self.pass_ticket
|
||||
clientMsgId = str(int(time.time() * 1000)) + \
|
||||
str(random.random())[:5].replace('.', '')
|
||||
data_json = {
|
||||
"BaseRequest": self.base_request,
|
||||
"Msg": {
|
||||
"Type": 3,
|
||||
"MediaId": media_id,
|
||||
"FromUserName": self.User['UserName'],
|
||||
"ToUserName": user_id,
|
||||
"LocalID": clientMsgId,
|
||||
"ClientMsgId": clientMsgId
|
||||
}
|
||||
}
|
||||
r = post(url, data_json)
|
||||
return dic['BaseResponse']['Ret'] == 0
|
||||
|
||||
def webwxsendemoticon(self, user_id, media_id):
|
||||
"""
|
||||
@brief send image
|
||||
@param user_id String
|
||||
@param media_id String
|
||||
@return Bool: whether operation succeed
|
||||
"""
|
||||
url = self.wx_conf['API_webwxsendemoticon'] + \
|
||||
'?fun=sys&f=json&pass_ticket=%s' % self.pass_ticket
|
||||
clientMsgId = str(int(time.time() * 1000)) + \
|
||||
str(random.random())[:5].replace('.', '')
|
||||
data_json = {
|
||||
"BaseRequest": self.base_request,
|
||||
"Msg": {
|
||||
"Type": 47,
|
||||
"EmojiFlag": 2,
|
||||
"MediaId": media_id,
|
||||
"FromUserName": self.User['UserName'],
|
||||
"ToUserName": user_id,
|
||||
"LocalID": clientMsgId,
|
||||
"ClientMsgId": clientMsgId
|
||||
}
|
||||
}
|
||||
r = post(url, data_json)
|
||||
return dic['BaseResponse']['Ret'] == 0
|
||||
|
||||
def webwxsendappmsg(self, user_id, data):
|
||||
"""
|
||||
@brief send app msg
|
||||
@param user_id String
|
||||
@param data Dict
|
||||
@return Bool: whether operation succeed
|
||||
"""
|
||||
url = self.wx_conf['API_webwxsendappmsg'] + \
|
||||
'?fun=sys&f=json&pass_ticket=%s' % self.pass_ticket
|
||||
clientMsgId = str(int(time.time() * 1000)) + \
|
||||
str(random.random())[:5].replace('.', '')
|
||||
content = ''.join([
|
||||
"<appmsg appid='%s' sdkver=''>" % data['appid'], # 可使用其它AppID
|
||||
"<title>%s</title>" % data['title'],
|
||||
"<des></des>",
|
||||
"<action></action>",
|
||||
"<type>%d</type>" % data['type'],
|
||||
"<content></content>",
|
||||
"<url></url>",
|
||||
"<lowurl></lowurl>",
|
||||
"<appattach>",
|
||||
"<totallen>%d</totallen>" % data['totallen'],
|
||||
"<attachid>%s</attachid>" % data['attachid'],
|
||||
"<fileext>%s</fileext>" % data['fileext'],
|
||||
"</appattach>",
|
||||
"<extinfo></extinfo>",
|
||||
"</appmsg>",
|
||||
])
|
||||
data_json = {
|
||||
"BaseRequest": self.base_request,
|
||||
"Msg": {
|
||||
"Type": data['type'],
|
||||
"Content": content,
|
||||
"FromUserName": self.User['UserName'],
|
||||
"ToUserName": user_id,
|
||||
"LocalID": clientMsgId,
|
||||
"ClientMsgId": clientMsgId
|
||||
},
|
||||
"Scene": 0
|
||||
}
|
||||
r = post(url, data_json)
|
||||
return dic['BaseResponse']['Ret'] == 0
|
||||
|
||||
def webwxcreatechatroom(self, uid_arr):
|
||||
"""
|
||||
@brief create a chat group
|
||||
@param uid_arr [String]
|
||||
@return Bool: whether operation succeed
|
||||
"""
|
||||
url = self.wx_conf['API_webwxcreatechatroom'] + '?r=%s' % int(time.time())
|
||||
params = {
|
||||
'BaseRequest': self.base_request,
|
||||
'Topic': '',
|
||||
'MemberCount': len(uid_arr),
|
||||
'MemberList': [{'UserName': uid} for uid in uid_arr],
|
||||
}
|
||||
dic = post(url, params)
|
||||
return dic['BaseResponse']['Ret'] == 0
|
||||
|
||||
def webwxupdatechatroom(self, add_arr, del_arr, invite_arr):
|
||||
"""
|
||||
@brief add/delete/invite member in chat group
|
||||
@param add_arr [uid: String]
|
||||
@param del_arr [uid: String]
|
||||
@param invite_arr [uid: String]
|
||||
@return Bool: whether operation succeed
|
||||
"""
|
||||
url = self.wx_conf['API_webwxupdatechatroom'] + '?r=%s' % int(time.time())
|
||||
params = {
|
||||
'BaseRequest': self.base_request,
|
||||
'ChatRoomName': '',
|
||||
'NewTopic': '',
|
||||
'AddMemberList': add_arr,
|
||||
'DelMemberList': del_arr,
|
||||
'InviteMemberList': invite_arr,
|
||||
}
|
||||
dic = post(url, params)
|
||||
return dic['BaseResponse']['Ret'] == 0
|
||||
|
||||
def webwxrevokemsg(self, msgid, user_id, client_msgid):
|
||||
"""
|
||||
@brief revoke a message
|
||||
@param msgid String
|
||||
@param user_id String
|
||||
@param client_msgid String
|
||||
@return Bool: whether operation succeed
|
||||
"""
|
||||
url = self.wx_conf['API_webwxrevokemsg'] + '?r=%s' % int(time.time())
|
||||
params = {
|
||||
'BaseRequest': self.base_request,
|
||||
'SvrMsgId': msgid,
|
||||
'ToUserName': user_id,
|
||||
'ClientMsgId': client_msgid
|
||||
}
|
||||
dic = post(url, params)
|
||||
return dic['BaseResponse']['Ret'] == 0
|
||||
|
||||
def webwxpushloginurl(self, uin):
|
||||
"""
|
||||
@brief push a login confirm alert to mobile device
|
||||
@param uin String
|
||||
@return dic Dict
|
||||
"""
|
||||
url = self.wx_conf['API_webwxpushloginurl'] + '?uin=%s' % uin
|
||||
dic = eval(get(url))
|
||||
return dic
|
||||
|
||||
def association_login(self):
|
||||
"""
|
||||
@brief login without scan qrcode
|
||||
@return Bool: whether operation succeed
|
||||
"""
|
||||
if self.uin != '':
|
||||
dic = self.webwxpushloginurl(self.uin)
|
||||
if dic['ret'] == '0':
|
||||
self.uuid = dic['uuid']
|
||||
return True
|
||||
return False
|
||||
|
||||
def send_text(self, user_id, text):
|
||||
"""
|
||||
@brief send text
|
||||
@param user_id String
|
||||
@param text String
|
||||
@return Bool: whether operation succeed
|
||||
"""
|
||||
dic = self.webwxsendmsg(text, user_id)
|
||||
return dic['BaseResponse']['Ret'] == 0
|
||||
|
||||
def send_img(self, user_id, file_path):
|
||||
"""
|
||||
@brief send image
|
||||
@param user_id String
|
||||
@param file_path String
|
||||
@return Bool: whether operation succeed
|
||||
"""
|
||||
response = self.webwxuploadmedia(file_path)
|
||||
media_id = ""
|
||||
if response is not None:
|
||||
media_id = response['MediaId']
|
||||
return self.webwxsendmsgimg(user_id, media_id)
|
||||
|
||||
def send_emot(self, user_id, file_path):
|
||||
"""
|
||||
@brief send emotion
|
||||
@param user_id String
|
||||
@param file_path String
|
||||
@return Bool: whether operation succeed
|
||||
"""
|
||||
response = self.webwxuploadmedia(file_path)
|
||||
media_id = ""
|
||||
if response is not None:
|
||||
media_id = response['MediaId']
|
||||
return self.webwxsendemoticon(user_id, media_id)
|
||||
|
||||
def send_file(self, user_id, file_path):
|
||||
"""
|
||||
@brief send file
|
||||
@param user_id String
|
||||
@param file_path String
|
||||
@return Bool: whether operation succeed
|
||||
"""
|
||||
title = file_path.split('/')[-1]
|
||||
data = {
|
||||
'appid': Constant.API_WXAPPID,
|
||||
'title': title,
|
||||
'totallen': '',
|
||||
'attachid': '',
|
||||
'type': self.wx_conf['APPMSGTYPE_ATTACH'],
|
||||
'fileext': title.split('.')[-1],
|
||||
}
|
||||
|
||||
response = self.webwxuploadmedia(file_path)
|
||||
if response is not None:
|
||||
data['totallen'] = response['StartPos']
|
||||
data['attachid'] = response['MediaId']
|
||||
else:
|
||||
Log.error('File upload error')
|
||||
|
||||
return self.webwxsendappmsg(user_id, data)
|
||||
|
||||
def make_synckey(self, dic):
|
||||
self.synckey_dic = dic['SyncKey']
|
||||
|
||||
def foo(x):
|
||||
return str(x['Key']) + '_' + str(x['Val'])
|
||||
|
||||
# synckey for synccheck
|
||||
self.synckey = '|'.join(
|
||||
[foo(keyVal) for keyVal in self.synckey_dic['List']])
|
||||
|
||||
def revoke_msg(self, msgid, user_id, client_msgid):
|
||||
"""
|
||||
@brief revoke a message
|
||||
@param msgid String
|
||||
@param user_id String
|
||||
@param client_msgid String
|
||||
@return Bool: whether operation succeed
|
||||
"""
|
||||
return self.webwxrevokemsg(msgid, user_id, client_msgid)
|
||||
|
||||
# -----------------------------------------------------
|
||||
# The following is getting user & group infomation apis
|
||||
def get_user_by_id(self, user_id):
|
||||
"""
|
||||
@brief get all type of name by user id
|
||||
@param user_id The id of user
|
||||
@return Dict: {
|
||||
'UserName' # 微信动态ID
|
||||
'RemarkName' # 备注
|
||||
'NickName' # 微信昵称
|
||||
'ShowName' # Log显示用的
|
||||
}
|
||||
"""
|
||||
UnknownPeople = Constant.LOG_MSG_UNKNOWN_NAME + user_id
|
||||
name = {
|
||||
'UserName': user_id,
|
||||
'RemarkName': '',
|
||||
'NickName': '',
|
||||
'ShowName': '',
|
||||
}
|
||||
name['ShowName'] = UnknownPeople
|
||||
|
||||
# yourself
|
||||
if user_id == self.User['UserName']:
|
||||
name['RemarkName'] = self.User['RemarkName']
|
||||
name['NickName'] = self.User['NickName']
|
||||
name['ShowName'] = name['NickName']
|
||||
else:
|
||||
# 联系人
|
||||
for member in self.MemberList:
|
||||
if member['UserName'] == user_id:
|
||||
r, n = member['RemarkName'], member['NickName']
|
||||
name['RemarkName'] = r
|
||||
name['NickName'] = n
|
||||
name['ShowName'] = r if r else n
|
||||
break
|
||||
# 特殊帐号
|
||||
for member in self.SpecialUsersList:
|
||||
if member['UserName'] == user_id:
|
||||
name['RemarkName'] = user_id
|
||||
name['NickName'] = user_id
|
||||
name['ShowName'] = user_id
|
||||
break
|
||||
|
||||
return name
|
||||
|
||||
def get_group_user_by_id(self, user_id, group_id):
|
||||
"""
|
||||
@brief get group user by user id
|
||||
@param user_id The id of user
|
||||
@param group_id The id of group
|
||||
@return Dict: {
|
||||
'UserName' # 微信动态ID
|
||||
'NickName' # 微信昵称
|
||||
'DisplayName' # 群聊显示名称
|
||||
'ShowName' # Log显示用的
|
||||
'AttrStatus' # 群用户id
|
||||
}
|
||||
"""
|
||||
UnknownPeople = Constant.LOG_MSG_UNKNOWN_NAME + user_id
|
||||
name = {
|
||||
'UserName': user_id,
|
||||
'NickName': '',
|
||||
'DisplayName': '',
|
||||
'ShowName': '',
|
||||
'AttrStatus': '',
|
||||
}
|
||||
name['ShowName'] = UnknownPeople
|
||||
|
||||
# 群友
|
||||
if group_id in self.GroupMemeberList:
|
||||
for member in self.GroupMemeberList[group_id]:
|
||||
if member['UserName'] == user_id:
|
||||
n, d = member['NickName'], member['DisplayName']
|
||||
name['NickName'] = n
|
||||
name['DisplayName'] = d
|
||||
name['AttrStatus'] = member['AttrStatus']
|
||||
name['ShowName'] = d if d else n
|
||||
break
|
||||
|
||||
return name
|
||||
|
||||
def get_group_by_id(self, group_id):
|
||||
"""
|
||||
@brief get basic info by group id
|
||||
@param group_id The id of group
|
||||
@return Dict: {
|
||||
'UserName' # 微信动态ID
|
||||
'NickName' # 微信昵称
|
||||
'DisplayName' # 群聊显示名称
|
||||
'ShowName' # Log显示用的
|
||||
'OwnerUin' # 群主ID
|
||||
'MemberCount' # 群人数
|
||||
}
|
||||
"""
|
||||
UnknownGroup = Constant.LOG_MSG_UNKNOWN_GROUP_NAME + group_id
|
||||
group = {
|
||||
'UserName': group_id,
|
||||
'NickName': '',
|
||||
'DisplayName': '',
|
||||
'ShowName': '',
|
||||
'OwnerUin': '',
|
||||
'MemberCount': '',
|
||||
}
|
||||
|
||||
for member in self.GroupList:
|
||||
if member['UserName'] == group_id:
|
||||
group['NickName'] = member['NickName']
|
||||
group['DisplayName'] = member.get('DisplayName', '')
|
||||
group['ShowName'] = member.get('NickName', UnknownGroup)
|
||||
group['OwnerUin'] = member.get('OwnerUin', '')
|
||||
group['MemberCount'] = member['MemberCount']
|
||||
break
|
||||
|
||||
return group
|
||||
|
||||
def get_user_id(self, name):
|
||||
"""
|
||||
@brief Gets the user id.
|
||||
@param name The user nickname or remarkname
|
||||
@return The user id.
|
||||
"""
|
||||
for member in self.MemberList:
|
||||
if name == member['RemarkName'] or name == member['NickName']:
|
||||
return member['UserName']
|
||||
return None
|
||||
Reference in New Issue
Block a user