wxbot.py 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359
  1. #!/usr/bin/env python
  2. # coding: utf-8
  3. import os
  4. import sys
  5. import traceback
  6. import webbrowser
  7. import pyqrcode
  8. import requests
  9. import mimetypes
  10. import json
  11. import xml.dom.minidom
  12. import urllib
  13. import time
  14. import re
  15. import random
  16. from traceback import format_exc
  17. from requests.exceptions import ConnectionError, ReadTimeout
  18. from Queue import Queue
  19. import HTMLParser
  20. import threading
  21. import pickle
  22. UNKONWN = 'unkonwn'
  23. SUCCESS = '200'
  24. SCANED = '201'
  25. TIMEOUT = '408'
  26. def show_image(file_path):
  27. """
  28. 跨平台显示图片文件
  29. :param file_path: 图片文件路径
  30. """
  31. if sys.version_info >= (3, 3):
  32. from shlex import quote
  33. else:
  34. from pipes import quote
  35. if sys.platform == "darwin":
  36. command = "open -a /Applications/Preview.app %s&" % quote(file_path)
  37. os.system(command)
  38. else:
  39. webbrowser.open(os.path.join(os.getcwd(),'temp',file_path))
  40. class SafeSession(requests.Session):
  41. def request(self, method, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None,
  42. timeout=None, allow_redirects=True, proxies=None, hooks=None, stream=None, verify=None, cert=None,
  43. json=None):
  44. for i in range(3):
  45. try:
  46. return super(SafeSession, self).request(method, url, params, data, headers, cookies, files, auth,
  47. timeout,
  48. allow_redirects, proxies, hooks, stream, verify, cert, json)
  49. except Exception as e:
  50. print e.message, traceback.format_exc()
  51. continue
  52. class WXBot:
  53. """WXBot功能类"""
  54. def __init__(self):
  55. self.DEBUG = False
  56. self.SCHEDULE_INTV = 5
  57. self.uuid = ''
  58. self.base_uri = ''
  59. self.redirect_uri = ''
  60. self.uin = ''
  61. self.sid = ''
  62. self.skey = ''
  63. self.pass_ticket = ''
  64. self.device_id = 'e' + repr(random.random())[2:17]
  65. self.base_request = {}
  66. self.sync_key_str = ''
  67. self.sync_key = []
  68. self.sync_host = ''
  69. #文件缓存目录
  70. self.temp_pwd = os.path.join(os.getcwd(), 'temp')
  71. if os.path.exists(self.temp_pwd) == False:
  72. os.makedirs(self.temp_pwd)
  73. self.session = SafeSession()
  74. self.session.headers.update({'User-Agent': 'Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5'})
  75. self.conf = {'qr': 'png'}
  76. self.my_account = {} # 当前账户
  77. # 所有相关账号: 联系人, 公众号, 群组, 特殊账号
  78. self.member_list = []
  79. # 所有群组的成员, {'group_id1': [member1, member2, ...], ...}
  80. self.group_members = {}
  81. # 所有账户, {'group_member':{'id':{'type':'group_member', 'info':{}}, ...}, 'normal_member':{'id':{}, ...}}
  82. self.account_info = {'group_member': {}, 'normal_member': {}}
  83. self.contact_list = [] # 联系人列表
  84. self.public_list = [] # 公众账号列表
  85. self.group_list = [] # 群聊列表
  86. self.special_list = [] # 特殊账号列表
  87. self.encry_chat_room_id_list = [] # 存储群聊的EncryChatRoomId,获取群内成员头像时需要用到
  88. self.file_index = 0 # 文件上传序号
  89. self.msg_queue = Queue() # 消息处理队列,handle_msg_all是从此队列拿消息的
  90. self.msg_thread = None
  91. self.schedule_thread = None
  92. self.inner_proc_thread = None
  93. @staticmethod
  94. def to_unicode(string, encoding='utf-8'):
  95. """
  96. 将字符串转换为Unicode
  97. :param string: 待转换字符串
  98. :param encoding: 字符串解码方式
  99. :return: 转换后的Unicode字符串
  100. """
  101. if isinstance(string, str):
  102. return string.decode(encoding)
  103. elif isinstance(string, unicode):
  104. return string
  105. else:
  106. raise Exception('Unknown Type')
  107. def get_contact(self):
  108. """获取当前账户的所有相关账号(包括联系人、公众号、群聊、特殊账号)"""
  109. url = self.base_uri + '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' \
  110. % (self.pass_ticket, self.skey, int(time.time()))
  111. r = self.session.post(url, data='{}')
  112. r.encoding = 'utf-8'
  113. if self.DEBUG:
  114. with open(os.path.join(self.temp_pwd,'contacts.json'), 'w') as f:
  115. f.write(r.text.encode('utf-8'))
  116. dic = json.loads(r.text)
  117. self.member_list = dic['MemberList']
  118. special_users = ['newsapp', 'fmessage', 'filehelper', 'weibo', 'qqmail',
  119. 'fmessage', 'tmessage', 'qmessage', 'qqsync', 'floatbottle',
  120. 'lbsapp', 'shakeapp', 'medianote', 'qqfriend', 'readerapp',
  121. 'blogapp', 'facebookapp', 'masssendapp', 'meishiapp',
  122. 'feedsapp', 'voip', 'blogappweixin', 'weixin', 'brandsessionholder',
  123. 'weixinreminder', 'wxid_novlwrv3lqwv11', 'gh_22b87fa7cb3c',
  124. 'officialaccounts', 'notification_messages', 'wxid_novlwrv3lqwv11',
  125. 'gh_22b87fa7cb3c', 'wxitil', 'userexperience_alarm', 'notification_messages']
  126. self.contact_list = []
  127. self.public_list = []
  128. self.special_list = []
  129. self.group_list = []
  130. for contact in self.member_list:
  131. if contact['VerifyFlag'] & 8 != 0: # 公众号
  132. self.public_list.append(contact)
  133. self.account_info['normal_member'][contact['UserName']] = {'type': 'public', 'info': contact}
  134. elif contact['UserName'] in special_users: # 特殊账户
  135. self.special_list.append(contact)
  136. self.account_info['normal_member'][contact['UserName']] = {'type': 'special', 'info': contact}
  137. elif contact['UserName'].find('@@') != -1: # 群聊
  138. self.group_list.append(contact)
  139. self.account_info['normal_member'][contact['UserName']] = {'type': 'group', 'info': contact}
  140. elif contact['UserName'] == self.my_account['UserName']: # 自己
  141. self.account_info['normal_member'][contact['UserName']] = {'type': 'self', 'info': contact}
  142. else:
  143. self.contact_list.append(contact)
  144. self.account_info['normal_member'][contact['UserName']] = {'type': 'contact', 'info': contact}
  145. self.batch_get_group_members()
  146. for group in self.group_members:
  147. for member in self.group_members[group]:
  148. if member['UserName'] not in self.account_info:
  149. self.account_info['group_member'][member['UserName']] = \
  150. {'type': 'group_member', 'info': member, 'group': group}
  151. return True
  152. def batch_get_group_members(self):
  153. """批量获取所有群聊成员信息"""
  154. url = self.base_uri + '/webwxbatchgetcontact?type=ex&r=%s&pass_ticket=%s' % (int(time.time()), self.pass_ticket)
  155. params = {
  156. 'BaseRequest': self.base_request,
  157. "Count": len(self.group_list),
  158. "List": [{"UserName": group['UserName'], "EncryChatRoomId": ""} for group in self.group_list]
  159. }
  160. r = self.session.post(url, data=json.dumps(params))
  161. r.encoding = 'utf-8'
  162. dic = json.loads(r.text)
  163. group_members = {}
  164. encry_chat_room_id = {}
  165. for group in dic['ContactList']:
  166. gid = group['UserName']
  167. members = group['MemberList']
  168. group_members[gid] = members
  169. encry_chat_room_id[gid] = group['EncryChatRoomId']
  170. self.group_members = group_members
  171. self.encry_chat_room_id_list = encry_chat_room_id
  172. def get_group_member_name(self, gid, uid):
  173. """
  174. 获取群聊中指定成员的名称信息
  175. :param gid: 群id
  176. :param uid: 群聊成员id
  177. :return: 名称信息,类似 {"display_name": "test_user", "nickname": "test", "remark_name": "for_test" }
  178. """
  179. if gid not in self.group_members:
  180. return None
  181. group = self.group_members[gid]
  182. for member in group:
  183. if member['UserName'] == uid:
  184. names = {}
  185. if 'RemarkName' in member and member['RemarkName']:
  186. names['remark_name'] = member['RemarkName']
  187. if 'NickName' in member and member['NickName']:
  188. names['nickname'] = member['NickName']
  189. if 'DisplayName' in member and member['DisplayName']:
  190. names['display_name'] = member['DisplayName']
  191. return names
  192. return None
  193. def get_contact_info(self, uid):
  194. return self.account_info['normal_member'].get(uid)
  195. def get_group_member_info(self, uid):
  196. return self.account_info['group_member'].get(uid)
  197. def get_contact_name(self, uid):
  198. info = self.get_contact_info(uid)
  199. if info is None:
  200. return None
  201. info = info['info']
  202. name = {}
  203. if 'RemarkName' in info and info['RemarkName']:
  204. name['remark_name'] = info['RemarkName']
  205. if 'NickName' in info and info['NickName']:
  206. name['nickname'] = info['NickName']
  207. if 'DisplayName' in info and info['DisplayName']:
  208. name['display_name'] = info['DisplayName']
  209. if len(name) == 0:
  210. return None
  211. else:
  212. return name
  213. @staticmethod
  214. def get_contact_prefer_name(name):
  215. if name is None:
  216. return None
  217. if 'remark_name' in name:
  218. return name['remark_name']
  219. if 'nickname' in name:
  220. return name['nickname']
  221. if 'display_name' in name:
  222. return name['display_name']
  223. return None
  224. @staticmethod
  225. def get_group_member_prefer_name(name):
  226. if name is None:
  227. return None
  228. if 'remark_name' in name:
  229. return name['remark_name']
  230. if 'display_name' in name:
  231. return name['display_name']
  232. if 'nickname' in name:
  233. return name['nickname']
  234. return None
  235. def get_user_type(self, wx_user_id):
  236. """
  237. 获取特定账号与自己的关系
  238. :param wx_user_id: 账号id:
  239. :return: 与当前账号的关系
  240. """
  241. for account in self.contact_list:
  242. if wx_user_id == account['UserName']:
  243. return 'contact'
  244. for account in self.public_list:
  245. if wx_user_id == account['UserName']:
  246. return 'public'
  247. for account in self.special_list:
  248. if wx_user_id == account['UserName']:
  249. return 'special'
  250. for account in self.group_list:
  251. if wx_user_id == account['UserName']:
  252. return 'group'
  253. for group in self.group_members:
  254. for member in self.group_members[group]:
  255. if member['UserName'] == wx_user_id:
  256. return 'group_member'
  257. return 'unknown'
  258. def is_contact(self, uid):
  259. for account in self.contact_list:
  260. if uid == account['UserName']:
  261. return True
  262. return False
  263. def is_public(self, uid):
  264. for account in self.public_list:
  265. if uid == account['UserName']:
  266. return True
  267. return False
  268. def is_special(self, uid):
  269. for account in self.special_list:
  270. if uid == account['UserName']:
  271. return True
  272. return False
  273. def handle_msg_all(self, msg):
  274. """
  275. 处理所有消息,请子类化后覆盖此函数
  276. msg:
  277. msg_id -> 消息id
  278. msg_type_id -> 消息类型id
  279. user -> 发送消息的账号id
  280. content -> 消息内容
  281. :param msg: 收到的消息
  282. """
  283. pass
  284. @staticmethod
  285. def proc_at_info(msg):
  286. if not msg:
  287. return '', []
  288. segs = msg.split(u'\u2005')
  289. str_msg_all = ''
  290. str_msg = ''
  291. infos = []
  292. if len(segs) > 1:
  293. for i in range(0, len(segs) - 1):
  294. segs[i] += u'\u2005'
  295. pm = re.search(u'@.*\u2005', segs[i]).group()
  296. if pm:
  297. name = pm[1:-1]
  298. string = segs[i].replace(pm, '')
  299. str_msg_all += string + '@' + name + ' '
  300. str_msg += string
  301. if string:
  302. infos.append({'type': 'str', 'value': string})
  303. infos.append({'type': 'at', 'value': name})
  304. else:
  305. infos.append({'type': 'str', 'value': segs[i]})
  306. str_msg_all += segs[i]
  307. str_msg += segs[i]
  308. str_msg_all += segs[-1]
  309. str_msg += segs[-1]
  310. infos.append({'type': 'str', 'value': segs[-1]})
  311. else:
  312. infos.append({'type': 'str', 'value': segs[-1]})
  313. str_msg_all = msg
  314. str_msg = msg
  315. return str_msg_all.replace(u'\u2005', ''), str_msg.replace(u'\u2005', ''), infos
  316. def extract_msg_content(self, msg_type_id, msg):
  317. """
  318. content_type_id:
  319. 0 -> Text
  320. 1 -> Location
  321. 3 -> Image
  322. 4 -> Voice
  323. 5 -> Recommend
  324. 6 -> Animation
  325. 7 -> Share
  326. 8 -> Video
  327. 9 -> VideoCall
  328. 10 -> Redraw
  329. 11 -> Empty
  330. 99 -> Unknown
  331. :param msg_type_id: 消息类型id
  332. :param msg: 消息结构体
  333. :return: 解析的消息
  334. """
  335. mtype = msg['MsgType']
  336. content = HTMLParser.HTMLParser().unescape(msg['Content'])
  337. msg_id = msg['MsgId']
  338. msg_content = {}
  339. if msg_type_id == 0:
  340. return {'type': 11, 'data': ''}
  341. elif msg_type_id == 2: # File Helper
  342. return {'type': 0, 'data': content.replace('<br/>', '\n')}
  343. elif msg_type_id == 3: # 群聊
  344. sp = content.find('<br/>')
  345. uid = content[:sp]
  346. content = content[sp:]
  347. content = content.replace('<br/>', '')
  348. uid = uid[:-1]
  349. name = self.get_contact_prefer_name(self.get_contact_name(uid))
  350. if not name:
  351. name = self.get_group_member_prefer_name(self.get_group_member_name(msg['FromUserName'], uid))
  352. if not name:
  353. name = 'unknown'
  354. msg_content['user'] = {'id': uid, 'name': name}
  355. else: # Self, Contact, Special, Public, Unknown
  356. pass
  357. msg_prefix = (msg_content['user']['name'] + ':') if 'user' in msg_content else ''
  358. if mtype == 1:
  359. if content.find('http://weixin.qq.com/cgi-bin/redirectforward?args=') != -1:
  360. r = self.session.get(content)
  361. r.encoding = 'gbk'
  362. data = r.text
  363. pos = self.search_content('title', data, 'xml')
  364. msg_content['type'] = 1
  365. msg_content['data'] = pos
  366. msg_content['detail'] = data
  367. if self.DEBUG:
  368. print ' %s[Location] %s ' % (msg_prefix, pos)
  369. else:
  370. msg_content['type'] = 0
  371. if msg_type_id == 3 or (msg_type_id == 1 and msg['ToUserName'][:2] == '@@'): # Group text message
  372. msg_infos = self.proc_at_info(content)
  373. str_msg_all = msg_infos[0]
  374. str_msg = msg_infos[1]
  375. detail = msg_infos[2]
  376. msg_content['data'] = str_msg_all
  377. msg_content['detail'] = detail
  378. msg_content['desc'] = str_msg
  379. else:
  380. msg_content['data'] = content
  381. if self.DEBUG:
  382. try:
  383. print ' %s[Text] %s' % (msg_prefix, msg_content['data'])
  384. except UnicodeEncodeError:
  385. print ' %s[Text] (illegal text).' % msg_prefix
  386. elif mtype == 3:
  387. msg_content['type'] = 3
  388. msg_content['data'] = self.get_msg_img_url(msg_id)
  389. msg_content['img'] = self.session.get(msg_content['data']).content.encode('hex')
  390. if self.DEBUG:
  391. image = self.get_msg_img(msg_id)
  392. print ' %s[Image] %s' % (msg_prefix, image)
  393. elif mtype == 34:
  394. msg_content['type'] = 4
  395. msg_content['data'] = self.get_voice_url(msg_id)
  396. msg_content['voice'] = self.session.get(msg_content['data']).content.encode('hex')
  397. if self.DEBUG:
  398. voice = self.get_voice(msg_id)
  399. print ' %s[Voice] %s' % (msg_prefix, voice)
  400. elif mtype == 37:
  401. msg_content['type'] = 37
  402. msg_content['data'] = msg['RecommendInfo']
  403. if self.DEBUG:
  404. print ' %s[useradd] %s' % (msg_prefix,msg['RecommendInfo']['NickName'])
  405. elif mtype == 42:
  406. msg_content['type'] = 5
  407. info = msg['RecommendInfo']
  408. msg_content['data'] = {'nickname': info['NickName'],
  409. 'alias': info['Alias'],
  410. 'province': info['Province'],
  411. 'city': info['City'],
  412. 'gender': ['unknown', 'male', 'female'][info['Sex']]}
  413. if self.DEBUG:
  414. print ' %s[Recommend]' % msg_prefix
  415. print ' -----------------------------'
  416. print ' | NickName: %s' % info['NickName']
  417. print ' | Alias: %s' % info['Alias']
  418. print ' | Local: %s %s' % (info['Province'], info['City'])
  419. print ' | Gender: %s' % ['unknown', 'male', 'female'][info['Sex']]
  420. print ' -----------------------------'
  421. elif mtype == 47:
  422. msg_content['type'] = 6
  423. msg_content['data'] = self.search_content('cdnurl', content)
  424. if self.DEBUG:
  425. print ' %s[Animation] %s' % (msg_prefix, msg_content['data'])
  426. elif mtype == 49:
  427. msg_content['type'] = 7
  428. if msg['AppMsgType'] == 3:
  429. app_msg_type = 'music'
  430. elif msg['AppMsgType'] == 5:
  431. app_msg_type = 'link'
  432. elif msg['AppMsgType'] == 7:
  433. app_msg_type = 'weibo'
  434. else:
  435. app_msg_type = 'unknown'
  436. msg_content['data'] = {'type': app_msg_type,
  437. 'title': msg['FileName'],
  438. 'desc': self.search_content('des', content, 'xml'),
  439. 'url': msg['Url'],
  440. 'from': self.search_content('appname', content, 'xml'),
  441. 'content': msg.get('Content') # 有的公众号会发一次性3 4条链接一个大图,如果只url那只能获取第一条,content里面有所有的链接
  442. }
  443. if self.DEBUG:
  444. print ' %s[Share] %s' % (msg_prefix, app_msg_type)
  445. print ' --------------------------'
  446. print ' | title: %s' % msg['FileName']
  447. print ' | desc: %s' % self.search_content('des', content, 'xml')
  448. print ' | link: %s' % msg['Url']
  449. print ' | from: %s' % self.search_content('appname', content, 'xml')
  450. print ' | content: %s' % (msg.get('content')[:20] if msg.get('content') else "unknown")
  451. print ' --------------------------'
  452. elif mtype == 62:
  453. msg_content['type'] = 8
  454. msg_content['data'] = content
  455. if self.DEBUG:
  456. print ' %s[Video] Please check on mobiles' % msg_prefix
  457. elif mtype == 53:
  458. msg_content['type'] = 9
  459. msg_content['data'] = content
  460. if self.DEBUG:
  461. print ' %s[Video Call]' % msg_prefix
  462. elif mtype == 10002:
  463. msg_content['type'] = 10
  464. msg_content['data'] = content
  465. if self.DEBUG:
  466. print ' %s[Redraw]' % msg_prefix
  467. elif mtype == 10000: # unknown, maybe red packet, or group invite
  468. msg_content['type'] = 12
  469. msg_content['data'] = msg['Content']
  470. if self.DEBUG:
  471. print ' [Unknown]'
  472. else:
  473. msg_content['type'] = 99
  474. msg_content['data'] = content
  475. if self.DEBUG:
  476. print ' %s[Unknown]' % msg_prefix
  477. return msg_content
  478. def handle_msg(self, r):
  479. """
  480. 处理原始微信消息的内部函数
  481. msg_type_id:
  482. 0 -> Init
  483. 1 -> Self
  484. 2 -> FileHelper
  485. 3 -> Group
  486. 4 -> Contact
  487. 5 -> Public
  488. 6 -> Special
  489. 99 -> Unknown
  490. :param r: 原始微信消息
  491. """
  492. for msg in r['AddMsgList']:
  493. user = {'id': msg['FromUserName'], 'name': 'unknown'}
  494. if msg['MsgType'] == 51: # init message
  495. msg_type_id = 0
  496. user['name'] = 'system'
  497. elif msg['MsgType'] == 37: # friend request
  498. msg_type_id = 37
  499. pass
  500. # content = msg['Content']
  501. # username = content[content.index('fromusername='): content.index('encryptusername')]
  502. # username = username[username.index('"') + 1: username.rindex('"')]
  503. # print u'[Friend Request]'
  504. # print u' Nickname:' + msg['RecommendInfo']['NickName']
  505. # print u' 附加消息:'+msg['RecommendInfo']['Content']
  506. # # print u'Ticket:'+msg['RecommendInfo']['Ticket'] # Ticket添加好友时要用
  507. # print u' 微信号:'+username #未设置微信号的 腾讯会自动生成一段微信ID 但是无法通过搜索 搜索到此人
  508. elif msg['FromUserName'] == self.my_account['UserName']: # Self
  509. msg_type_id = 1
  510. user['name'] = 'self'
  511. elif msg['ToUserName'] == 'filehelper': # File Helper
  512. msg_type_id = 2
  513. user['name'] = 'file_helper'
  514. elif msg['FromUserName'][:2] == '@@': # Group
  515. msg_type_id = 3
  516. user['name'] = self.get_contact_prefer_name(self.get_contact_name(user['id']))
  517. elif self.is_contact(msg['FromUserName']): # Contact
  518. msg_type_id = 4
  519. user['name'] = self.get_contact_prefer_name(self.get_contact_name(user['id']))
  520. elif self.is_public(msg['FromUserName']): # Public
  521. msg_type_id = 5
  522. user['name'] = self.get_contact_prefer_name(self.get_contact_name(user['id']))
  523. elif self.is_special(msg['FromUserName']): # Special
  524. msg_type_id = 6
  525. user['name'] = self.get_contact_prefer_name(self.get_contact_name(user['id']))
  526. else:
  527. msg_type_id = 99
  528. user['name'] = 'unknown'
  529. if not user['name']:
  530. user['name'] = 'unknown'
  531. user['name'] = HTMLParser.HTMLParser().unescape(user['name'])
  532. if self.DEBUG and msg_type_id != 0:
  533. print u'[MSG] %s:' % user['name']
  534. content = self.extract_msg_content(msg_type_id, msg)
  535. message = {'msg_type_id': msg_type_id,
  536. 'msg_id': msg['MsgId'],
  537. 'content': content,
  538. 'to_user_id': msg['ToUserName'],
  539. 'user': user}
  540. self.msg_queue.put(message)
  541. def schedule(self):
  542. """
  543. 做任务型事情的函数,如果需要,可以在子类中覆盖此函数
  544. 此函数在处理消息的间隙被调用,请不要长时间阻塞此函数
  545. """
  546. pass
  547. def proc_msg(self):
  548. while True:
  549. check_time = time.time()
  550. try:
  551. [retcode, selector] = self.sync_check()
  552. # print '[DEBUG] sync_check:', retcode, selector
  553. if retcode == '1100': # 从微信客户端上登出
  554. break
  555. elif retcode == '1101': # 从其它设备上登了网页微信
  556. break
  557. elif retcode == '0':
  558. if selector == '2': # 有新消息
  559. r = self.sync()
  560. if r is not None:
  561. self.handle_msg(r)
  562. elif selector == '3': # 未知
  563. r = self.sync()
  564. if r is not None:
  565. self.handle_msg(r)
  566. elif selector == '4': # 通讯录更新
  567. r = self.sync()
  568. if r is not None:
  569. self.get_contact()
  570. elif selector == '6': # 可能是红包
  571. r = self.sync()
  572. if r is not None:
  573. self.handle_msg(r)
  574. elif selector == '7': # 在手机上操作了微信
  575. r = self.sync()
  576. if r is not None:
  577. self.handle_msg(r)
  578. elif selector == '0': # 无事件
  579. pass
  580. else:
  581. print '[DEBUG] sync_check:', retcode, selector
  582. r = self.sync()
  583. if r is not None:
  584. self.handle_msg(r)
  585. else:
  586. print '[DEBUG] sync_check:', retcode, selector
  587. except:
  588. print '[ERROR] Except in proc_msg'
  589. print format_exc()
  590. check_time = time.time() - check_time
  591. if check_time < 0.8:
  592. time.sleep(1 - check_time)
  593. def msg_thread_proc(self):
  594. print '[INFO] Msg thread start'
  595. while True:
  596. if not self.msg_queue.empty():
  597. msg = self.msg_queue.get()
  598. self.handle_msg_all(msg)
  599. else:
  600. time.sleep(0.1)
  601. def schedule_thread_proc(self):
  602. print '[INFO] Schedule thread start'
  603. while True:
  604. check_time = time.time()
  605. self.schedule()
  606. check_time = time.time() - check_time
  607. if check_time < self.SCHEDULE_INTV:
  608. time.sleep(self.SCHEDULE_INTV - check_time)
  609. def login_and_init_with_restore(self):
  610. return self.restore_login_result()
  611. def login_and_init_with_qr(self):
  612. self.get_uuid()
  613. self.gen_qr_code(os.path.join(self.temp_pwd, 'wxqr.png'))
  614. print '[INFO] Please use WeChat to scan the QR code .'
  615. result = self.wait4login()
  616. if result != SUCCESS:
  617. print '[ERROR] Web WeChat login failed. failed code=%s' % (result,)
  618. return False
  619. if self.login():
  620. print '[INFO] Web WeChat login succeed .'
  621. else:
  622. print '[ERROR] Web WeChat login failed .'
  623. return False
  624. if self.init():
  625. print '[INFO] Web WeChat init succeed .'
  626. else:
  627. print '[INFO] Web WeChat init failed'
  628. return False
  629. self.status_notify()
  630. self.get_contact()
  631. self.test_sync_check()
  632. self.save_login_result()
  633. return True
  634. def run_inner(self):
  635. print '[INFO] Get %d contacts' % len(self.contact_list)
  636. print '[INFO] Start to process messages .'
  637. self.proc_msg()
  638. def run(self):
  639. if not self.login_and_init_with_restore():
  640. print '[INFO] Restore login failed !'
  641. if not self.login_and_init_with_qr():
  642. print '[ERROR] Login and init failed !'
  643. return
  644. else:
  645. print '[INFO Restore login succeed .'
  646. self.msg_thread = threading.Thread(target=self.msg_thread_proc)
  647. self.msg_thread.setDaemon(True)
  648. self.msg_thread.start()
  649. self.schedule_thread = threading.Thread(target=self.schedule_thread_proc)
  650. self.schedule_thread.setDaemon(True)
  651. self.schedule_thread.start()
  652. self.inner_proc_thread = threading.Thread(target=self.run_inner)
  653. self.inner_proc_thread.setDaemon(True)
  654. self.inner_proc_thread.start()
  655. self.inner_proc_thread.join()
  656. while True:
  657. if self.login_and_init_with_restore():
  658. self.inner_proc_thread = threading.Thread(target=self.run_inner)
  659. self.inner_proc_thread.setDaemon(True)
  660. self.inner_proc_thread.start()
  661. self.inner_proc_thread.join()
  662. else:
  663. print '[ERROR] Try to restore from file failed !'
  664. return
  665. def apply_useradd_requests(self,RecommendInfo):
  666. url = self.base_uri + '/webwxverifyuser?r='+str(int(time.time()))+'&lang=zh_CN'
  667. params = {
  668. "BaseRequest": self.base_request,
  669. "Opcode": 3,
  670. "VerifyUserListSize": 1,
  671. "VerifyUserList": [
  672. {
  673. "Value": RecommendInfo['UserName'],
  674. "VerifyUserTicket": RecommendInfo['Ticket'] }
  675. ],
  676. "VerifyContent": "",
  677. "SceneListCount": 1,
  678. "SceneList": [
  679. 33
  680. ],
  681. "skey": self.skey
  682. }
  683. headers = {'content-type': 'application/json; charset=UTF-8'}
  684. data = json.dumps(params, ensure_ascii=False).encode('utf8')
  685. try:
  686. r = self.session.post(url, data=data, headers=headers)
  687. except (ConnectionError, ReadTimeout):
  688. return False
  689. dic = r.json()
  690. return dic['BaseResponse']['Ret'] == 0
  691. def add_groupuser_to_friend_by_uid(self, uid, VerifyContent):
  692. """
  693. 主动向群内人员打招呼,提交添加好友请求
  694. uid-群内人员得uid VerifyContent-好友招呼内容
  695. 慎用此接口!封号后果自负!慎用此接口!封号后果自负!慎用此接口!封号后果自负!
  696. """
  697. if self.is_contact(uid):
  698. return True
  699. url = self.base_uri + '/webwxverifyuser?r='+str(int(time.time()))+'&lang=zh_CN'
  700. params ={
  701. "BaseRequest": self.base_request,
  702. "Opcode": 2,
  703. "VerifyUserListSize": 1,
  704. "VerifyUserList": [
  705. {
  706. "Value": uid,
  707. "VerifyUserTicket": ""
  708. }
  709. ],
  710. "VerifyContent": VerifyContent,
  711. "SceneListCount": 1,
  712. "SceneList": [
  713. 33
  714. ],
  715. "skey": self.skey
  716. }
  717. headers = {'content-type': 'application/json; charset=UTF-8'}
  718. data = json.dumps(params, ensure_ascii=False).encode('utf8')
  719. try:
  720. r = self.session.post(url, data=data, headers=headers)
  721. except (ConnectionError, ReadTimeout):
  722. return False
  723. dic = r.json()
  724. return dic['BaseResponse']['Ret'] == 0
  725. def add_friend_to_group(self,uid,group_name):
  726. """
  727. 将好友加入到群聊中
  728. """
  729. gid = ''
  730. #通过群名获取群id,群没保存到通讯录中的话无法添加哦
  731. for group in self.group_list:
  732. if group['NickName'] == group_name:
  733. gid = group['UserName']
  734. if gid == '':
  735. return False
  736. #通过群id判断uid是否在群中
  737. for user in self.group_members[gid]:
  738. if user['UserName'] == uid:
  739. #已经在群里面了,不用加了
  740. return True
  741. url = self.base_uri + '/webwxupdatechatroom?fun=addmember&pass_ticket=%s' % self.pass_ticket
  742. params ={
  743. "AddMemberList": uid,
  744. "ChatRoomName": gid,
  745. "BaseRequest": self.base_request
  746. }
  747. headers = {'content-type': 'application/json; charset=UTF-8'}
  748. data = json.dumps(params, ensure_ascii=False).encode('utf8')
  749. try:
  750. r = self.session.post(url, data=data, headers=headers)
  751. except (ConnectionError, ReadTimeout):
  752. return False
  753. dic = r.json()
  754. return dic['BaseResponse']['Ret'] == 0
  755. def delete_user_from_group(self,uname,gid):
  756. """
  757. 将群用户从群中剔除,只有群管理员有权限
  758. """
  759. uid = ""
  760. for user in self.group_members[gid]:
  761. if user['NickName'] == uname:
  762. uid = user['UserName']
  763. if uid == "":
  764. return False
  765. url = self.base_uri + '/webwxupdatechatroom?fun=delmember&pass_ticket=%s' % self.pass_ticket
  766. params ={
  767. "DelMemberList": uid,
  768. "ChatRoomName": gid,
  769. "BaseRequest": self.base_request
  770. }
  771. headers = {'content-type': 'application/json; charset=UTF-8'}
  772. data = json.dumps(params, ensure_ascii=False).encode('utf8')
  773. try:
  774. r = self.session.post(url, data=data, headers=headers)
  775. except (ConnectionError, ReadTimeout):
  776. return False
  777. dic = r.json()
  778. return dic['BaseResponse']['Ret'] == 0
  779. def send_msg_by_uid(self, word, dst='filehelper'):
  780. url = self.base_uri + '/webwxsendmsg?pass_ticket=%s' % self.pass_ticket
  781. msg_id = str(int(time.time() * 1000)) + str(random.random())[:5].replace('.', '')
  782. word = self.to_unicode(word)
  783. params = {
  784. 'BaseRequest': self.base_request,
  785. 'Msg': {
  786. "Type": 1,
  787. "Content": word,
  788. "FromUserName": self.my_account['UserName'],
  789. "ToUserName": dst,
  790. "LocalID": msg_id,
  791. "ClientMsgId": msg_id
  792. }
  793. }
  794. headers = {'content-type': 'application/json; charset=UTF-8'}
  795. data = json.dumps(params, ensure_ascii=False).encode('utf8')
  796. try:
  797. r = self.session.post(url, data=data, headers=headers)
  798. except (ConnectionError, ReadTimeout):
  799. return False
  800. dic = r.json()
  801. return dic['BaseResponse']['Ret'] == 0
  802. def upload_media(self, fpath, is_img=False):
  803. if not os.path.exists(fpath):
  804. print '[ERROR] File not exists.'
  805. return None
  806. url_1 = 'https://file.wx2.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json'
  807. url_2 = 'https://file2.wx2.qq.com/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json'
  808. flen = str(os.path.getsize(fpath))
  809. ftype = mimetypes.guess_type(fpath)[0] or 'application/octet-stream'
  810. files = {
  811. 'id': (None, 'WU_FILE_%s' % str(self.file_index)),
  812. 'name': (None, os.path.basename(fpath)),
  813. 'type': (None, ftype),
  814. 'lastModifiedDate': (None, time.strftime('%m/%d/%Y, %H:%M:%S GMT+0800 (CST)')),
  815. 'size': (None, flen),
  816. 'mediatype': (None, 'pic' if is_img else 'doc'),
  817. 'uploadmediarequest': (None, json.dumps({
  818. 'BaseRequest': self.base_request,
  819. 'ClientMediaId': int(time.time()),
  820. 'TotalLen': flen,
  821. 'StartPos': 0,
  822. 'DataLen': flen,
  823. 'MediaType': 4,
  824. })),
  825. 'webwx_data_ticket': (None, self.session.cookies['webwx_data_ticket']),
  826. 'pass_ticket': (None, self.pass_ticket),
  827. 'filename': (os.path.basename(fpath), open(fpath, 'rb'),ftype.split('/')[1]),
  828. }
  829. self.file_index += 1
  830. try:
  831. r = self.session.post(url_1, files=files)
  832. if json.loads(r.text)['BaseResponse']['Ret'] != 0:
  833. # 当file返回值不为0时则为上传失败,尝试第二服务器上传
  834. r = self.session.post(url_2, files=files)
  835. if json.loads(r.text)['BaseResponse']['Ret'] != 0:
  836. print '[ERROR] Upload media failure.'
  837. return None
  838. mid = json.loads(r.text)['MediaId']
  839. return mid
  840. except Exception,e:
  841. return None
  842. def send_file_msg_by_uid(self, fpath, uid):
  843. mid = self.upload_media(fpath)
  844. if mid is None or not mid:
  845. return False
  846. url = self.base_uri + '/webwxsendappmsg?fun=async&f=json&pass_ticket=' + self.pass_ticket
  847. msg_id = str(int(time.time() * 1000)) + str(random.random())[:5].replace('.', '')
  848. data = {
  849. 'BaseRequest': self.base_request,
  850. 'Msg': {
  851. 'Type': 6,
  852. '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'),
  853. 'FromUserName': self.my_account['UserName'],
  854. 'ToUserName': uid,
  855. 'LocalID': msg_id,
  856. 'ClientMsgId': msg_id, }, }
  857. try:
  858. r = self.session.post(url, data=json.dumps(data))
  859. res = json.loads(r.text)
  860. if res['BaseResponse']['Ret'] == 0:
  861. return True
  862. else:
  863. return False
  864. except Exception,e:
  865. return False
  866. def send_img_msg_by_uid(self, fpath, uid):
  867. mid = self.upload_media(fpath, is_img=True)
  868. if mid is None:
  869. return False
  870. url = self.base_uri + '/webwxsendmsgimg?fun=async&f=json'
  871. data = {
  872. 'BaseRequest': self.base_request,
  873. 'Msg': {
  874. 'Type': 3,
  875. 'MediaId': mid,
  876. 'FromUserName': self.my_account['UserName'],
  877. 'ToUserName': uid,
  878. 'LocalID': str(time.time() * 1e7),
  879. 'ClientMsgId': str(time.time() * 1e7), }, }
  880. if fpath[-4:] == '.gif':
  881. url = self.base_uri + '/webwxsendemoticon?fun=sys'
  882. data['Msg']['Type'] = 47
  883. data['Msg']['EmojiFlag'] = 2
  884. try:
  885. r = self.session.post(url, data=json.dumps(data))
  886. res = json.loads(r.text)
  887. if res['BaseResponse']['Ret'] == 0:
  888. return True
  889. else:
  890. return False
  891. except Exception,e:
  892. return False
  893. def get_user_id(self, name):
  894. if name == '':
  895. return None
  896. name = self.to_unicode(name)
  897. for contact in self.contact_list:
  898. if 'RemarkName' in contact and contact['RemarkName'] == name:
  899. return contact['UserName']
  900. elif 'NickName' in contact and contact['NickName'] == name:
  901. return contact['UserName']
  902. elif 'DisplayName' in contact and contact['DisplayName'] == name:
  903. return contact['UserName']
  904. for group in self.group_list:
  905. if 'RemarkName' in group and group['RemarkName'] == name:
  906. return group['UserName']
  907. if 'NickName' in group and group['NickName'] == name:
  908. return group['UserName']
  909. if 'DisplayName' in group and group['DisplayName'] == name:
  910. return group['UserName']
  911. return ''
  912. def send_msg(self, name, word, isfile=False):
  913. uid = self.get_user_id(name)
  914. if uid is not None:
  915. if isfile:
  916. with open(word, 'r') as f:
  917. result = True
  918. for line in f.readlines():
  919. line = line.replace('\n', '')
  920. print '-> ' + name + ': ' + line
  921. if self.send_msg_by_uid(line, uid):
  922. pass
  923. else:
  924. result = False
  925. time.sleep(1)
  926. return result
  927. else:
  928. word = self.to_unicode(word)
  929. if self.send_msg_by_uid(word, uid):
  930. return True
  931. else:
  932. return False
  933. else:
  934. if self.DEBUG:
  935. print '[ERROR] This user does not exist .'
  936. return True
  937. @staticmethod
  938. def search_content(key, content, fmat='attr'):
  939. if fmat == 'attr':
  940. pm = re.search(key + '\s?=\s?"([^"<]+)"', content)
  941. if pm:
  942. return pm.group(1)
  943. elif fmat == 'xml':
  944. pm = re.search('<{0}>([^<]+)</{0}>'.format(key), content)
  945. if pm:
  946. return pm.group(1)
  947. return 'unknown'
  948. def get_uuid(self):
  949. url = 'https://login.weixin.qq.com/jslogin'
  950. params = {
  951. 'appid': 'wx782c26e4c19acffb',
  952. 'fun': 'new',
  953. 'lang': 'zh_CN',
  954. '_': int(time.time()) * 1000 + random.randint(1, 999),
  955. }
  956. r = self.session.get(url, params=params)
  957. r.encoding = 'utf-8'
  958. data = r.text
  959. regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)"'
  960. pm = re.search(regx, data)
  961. if pm:
  962. code = pm.group(1)
  963. self.uuid = pm.group(2)
  964. return code == '200'
  965. return False
  966. def gen_qr_code(self, qr_file_path):
  967. string = 'https://login.weixin.qq.com/l/' + self.uuid
  968. qr = pyqrcode.create(string)
  969. if self.conf['qr'] == 'png':
  970. qr.png(qr_file_path, scale=8)
  971. show_image(qr_file_path)
  972. # img = Image.open(qr_file_path)
  973. # img.show()
  974. elif self.conf['qr'] == 'tty':
  975. print(qr.terminal(quiet_zone=1))
  976. def do_request(self, url):
  977. r = self.session.get(url)
  978. r.encoding = 'utf-8'
  979. data = r.text
  980. param = re.search(r'window.code=(\d+);', data)
  981. code = param.group(1)
  982. return code, data
  983. def wait4login(self):
  984. """
  985. http comet:
  986. tip=1, 等待用户扫描二维码,
  987. 201: scaned
  988. 408: timeout
  989. tip=0, 等待用户确认登录,
  990. 200: confirmed
  991. """
  992. LOGIN_TEMPLATE = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s'
  993. tip = 1
  994. try_later_secs = 1
  995. MAX_RETRY_TIMES = 10
  996. code = UNKONWN
  997. retry_time = MAX_RETRY_TIMES
  998. while retry_time > 0:
  999. url = LOGIN_TEMPLATE % (tip, self.uuid, int(time.time()))
  1000. code, data = self.do_request(url)
  1001. if code == SCANED:
  1002. print '[INFO] Please confirm to login .'
  1003. tip = 0
  1004. elif code == SUCCESS: # 确认登录成功
  1005. param = re.search(r'window.redirect_uri="(\S+?)";', data)
  1006. redirect_uri = param.group(1) + '&fun=new'
  1007. self.redirect_uri = redirect_uri
  1008. self.base_uri = redirect_uri[:redirect_uri.rfind('/')]
  1009. return code
  1010. elif code == TIMEOUT:
  1011. print '[ERROR] WeChat login timeout. retry in %s secs later...' % (try_later_secs,)
  1012. tip = 1 # 重置
  1013. retry_time -= 1
  1014. time.sleep(try_later_secs)
  1015. else:
  1016. print ('[ERROR] WeChat login exception return_code=%s. retry in %s secs later...' %
  1017. (code, try_later_secs))
  1018. tip = 1
  1019. retry_time -= 1
  1020. time.sleep(try_later_secs)
  1021. return code
  1022. def save_dict_to_file(self, dic, fname):
  1023. with open(os.path.join(self.temp_pwd, fname), 'w') as f:
  1024. f.write(json.dumps(dic))
  1025. def restore_dict_from_file(self, fname):
  1026. with open(os.path.join(self.temp_pwd, fname), 'r') as f:
  1027. fstr = f.read()
  1028. return json.loads(fstr)
  1029. def save_login_result(self):
  1030. result = {}
  1031. result['skey'] = self.skey
  1032. result['sid'] = self.sid
  1033. result['uin'] = self.uin
  1034. result['pass_ticket'] = self.pass_ticket
  1035. result['base_request'] = self.base_request
  1036. result['redirect_uri'] = self.redirect_uri
  1037. result['base_uri'] = self.base_uri
  1038. result['sync_key'] = self.sync_key
  1039. result['sync_key_str'] = self.sync_key_str
  1040. result['my_account'] = self.my_account
  1041. result['sync_host'] = self.sync_host
  1042. with open(os.path.join(self.temp_pwd, "login_result.json"), 'w') as f:
  1043. f.write(json.dumps(result))
  1044. self.save_dict_to_file(self.contact_list, 'contact_list.json')
  1045. self.save_dict_to_file(self.special_list, 'special_list.json')
  1046. self.save_dict_to_file(self.group_list, 'group_list.json')
  1047. self.save_dict_to_file(self.public_list, 'public_list.json')
  1048. self.save_dict_to_file(self.member_list, 'member_list.json')
  1049. self.save_dict_to_file(self.group_members, 'group_members.json')
  1050. self.save_dict_to_file(self.account_info, 'account_info.json')
  1051. self.save_dict_to_file(self.encry_chat_room_id_list, 'encry_chat_room_id_list.json')
  1052. pickle.dump(self.session, open(os.path.join(self.temp_pwd, "session.json"), "w"))
  1053. def restore_login_result(self):
  1054. try:
  1055. with open(os.path.join(self.temp_pwd, "login_result.json"), 'r') as f:
  1056. login_str = f.read()
  1057. result = json.loads(login_str)
  1058. self.skey = result['skey']
  1059. self.sid = result['sid']
  1060. self.uin = result['uin']
  1061. self.pass_ticket = result['pass_ticket']
  1062. self.base_request = result['base_request']
  1063. self.redirect_uri = result['redirect_uri']
  1064. self.base_uri = result['base_uri']
  1065. self.sync_key = result['sync_key']
  1066. self.sync_key_str = result['sync_key_str']
  1067. self.my_account = result['my_account']
  1068. self.sync_host = result['sync_host']
  1069. self.contact_list = self.restore_dict_from_file('contact_list.json')
  1070. self.special_list = self.restore_dict_from_file('special_list.json')
  1071. self.group_list = self.restore_dict_from_file('group_list.json')
  1072. self.public_list = self.restore_dict_from_file('public_list.json')
  1073. self.member_list = self.restore_dict_from_file('member_list.json')
  1074. self.group_members = self.restore_dict_from_file('group_members.json')
  1075. self.account_info = self.restore_dict_from_file('account_info.json')
  1076. self.encry_chat_room_id_list = self.restore_dict_from_file('encry_chat_room_id_list.json')
  1077. self.session = pickle.load(open(os.path.join(self.temp_pwd, "session.json"), "r"))
  1078. return True
  1079. except Exception, e:
  1080. print format_exc()
  1081. def login(self):
  1082. if len(self.redirect_uri) < 4:
  1083. print '[ERROR] Login failed due to network problem, please try again.'
  1084. return False
  1085. r = self.session.get(self.redirect_uri)
  1086. r.encoding = 'utf-8'
  1087. data = r.text
  1088. doc = xml.dom.minidom.parseString(data)
  1089. root = doc.documentElement
  1090. for node in root.childNodes:
  1091. if node.nodeName == 'skey':
  1092. self.skey = node.childNodes[0].data
  1093. elif node.nodeName == 'wxsid':
  1094. self.sid = node.childNodes[0].data
  1095. elif node.nodeName == 'wxuin':
  1096. self.uin = node.childNodes[0].data
  1097. elif node.nodeName == 'pass_ticket':
  1098. self.pass_ticket = node.childNodes[0].data
  1099. if '' in (self.skey, self.sid, self.uin, self.pass_ticket):
  1100. return False
  1101. self.base_request = {
  1102. 'Uin': self.uin,
  1103. 'Sid': self.sid,
  1104. 'Skey': self.skey,
  1105. 'DeviceID': self.device_id,
  1106. }
  1107. return True
  1108. def init(self):
  1109. url = self.base_uri + '/webwxinit?r=%i&lang=en_US&pass_ticket=%s' % (int(time.time()), self.pass_ticket)
  1110. params = {
  1111. 'BaseRequest': self.base_request
  1112. }
  1113. r = self.session.post(url, data=json.dumps(params))
  1114. r.encoding = 'utf-8'
  1115. dic = json.loads(r.text)
  1116. self.sync_key = dic['SyncKey']
  1117. self.my_account = dic['User']
  1118. self.sync_key_str = '|'.join([str(keyVal['Key']) + '_' + str(keyVal['Val'])
  1119. for keyVal in self.sync_key['List']])
  1120. return dic['BaseResponse']['Ret'] == 0
  1121. def status_notify(self):
  1122. url = self.base_uri + '/webwxstatusnotify?lang=zh_CN&pass_ticket=%s' % self.pass_ticket
  1123. self.base_request['Uin'] = int(self.base_request['Uin'])
  1124. params = {
  1125. 'BaseRequest': self.base_request,
  1126. "Code": 3,
  1127. "FromUserName": self.my_account['UserName'],
  1128. "ToUserName": self.my_account['UserName'],
  1129. "ClientMsgId": int(time.time())
  1130. }
  1131. r = self.session.post(url, data=json.dumps(params))
  1132. r.encoding = 'utf-8'
  1133. dic = json.loads(r.text)
  1134. return dic['BaseResponse']['Ret'] == 0
  1135. def test_sync_check(self):
  1136. for host in ['webpush.wx', 'webpush2.wx', 'webpush.weixin', 'webpush2.weixin2', ]:
  1137. self.sync_host = host
  1138. retcode = self.sync_check()[0]
  1139. if retcode == '0':
  1140. return True
  1141. return False
  1142. def sync_check(self):
  1143. params = {
  1144. 'r': int(time.time()),
  1145. 'sid': self.sid,
  1146. 'uin': self.uin,
  1147. 'skey': self.skey,
  1148. 'deviceid': self.device_id,
  1149. 'synckey': self.sync_key_str,
  1150. '_': int(time.time()),
  1151. }
  1152. url = 'https://' + self.sync_host + '.qq.com/cgi-bin/mmwebwx-bin/synccheck?' + urllib.urlencode(params)
  1153. try:
  1154. r = self.session.get(url, timeout=60)
  1155. r.encoding = 'utf-8'
  1156. data = r.text
  1157. pm = re.search(r'window.synccheck=\{retcode:"(\d+)",selector:"(\d+)"\}', data)
  1158. retcode = pm.group(1)
  1159. selector = pm.group(2)
  1160. return [retcode, selector]
  1161. except:
  1162. return [-1, -1]
  1163. def sync(self):
  1164. url = self.base_uri + '/webwxsync?sid=%s&skey=%s&lang=en_US&pass_ticket=%s' \
  1165. % (self.sid, self.skey, self.pass_ticket)
  1166. params = {
  1167. 'BaseRequest': self.base_request,
  1168. 'SyncKey': self.sync_key,
  1169. 'rr': ~int(time.time())
  1170. }
  1171. try:
  1172. r = self.session.post(url, data=json.dumps(params), timeout=60)
  1173. r.encoding = 'utf-8'
  1174. dic = json.loads(r.text)
  1175. if dic['BaseResponse']['Ret'] == 0:
  1176. self.sync_key = dic['SyncKey']
  1177. self.sync_key_str = '|'.join([str(keyVal['Key']) + '_' + str(keyVal['Val'])
  1178. for keyVal in self.sync_key['List']])
  1179. return dic
  1180. except:
  1181. return None
  1182. def get_icon(self, uid, gid=None):
  1183. """
  1184. 获取联系人或者群聊成员头像
  1185. :param uid: 联系人id
  1186. :param gid: 群id,如果为非None获取群中成员头像,如果为None则获取联系人头像
  1187. """
  1188. if gid is None:
  1189. url = self.base_uri + '/webwxgeticon?username=%s&skey=%s' % (uid, self.skey)
  1190. else:
  1191. url = self.base_uri + '/webwxgeticon?username=%s&skey=%s&chatroomid=%s' % (
  1192. uid, self.skey, self.encry_chat_room_id_list[gid])
  1193. r = self.session.get(url)
  1194. data = r.content
  1195. fn = 'icon_' + uid + '.jpg'
  1196. with open(os.path.join(self.temp_pwd,fn), 'wb') as f:
  1197. f.write(data)
  1198. return fn
  1199. def get_head_img(self, uid):
  1200. """
  1201. 获取群头像
  1202. :param uid: 群uid
  1203. """
  1204. url = self.base_uri + '/webwxgetheadimg?username=%s&skey=%s' % (uid, self.skey)
  1205. r = self.session.get(url)
  1206. data = r.content
  1207. fn = 'head_' + uid + '.jpg'
  1208. with open(os.path.join(self.temp_pwd,fn), 'wb') as f:
  1209. f.write(data)
  1210. return fn
  1211. def get_msg_img_url(self, msgid):
  1212. return self.base_uri + '/webwxgetmsgimg?MsgID=%s&skey=%s' % (msgid, self.skey)
  1213. def get_msg_img(self, msgid):
  1214. """
  1215. 获取图片消息,下载图片到本地
  1216. :param msgid: 消息id
  1217. :return: 保存的本地图片文件路径
  1218. """
  1219. url = self.base_uri + '/webwxgetmsgimg?MsgID=%s&skey=%s' % (msgid, self.skey)
  1220. r = self.session.get(url)
  1221. data = r.content
  1222. fn = 'img_' + msgid + '.jpg'
  1223. with open(os.path.join(self.temp_pwd,fn), 'wb') as f:
  1224. f.write(data)
  1225. return fn
  1226. def get_voice_url(self, msgid):
  1227. return self.base_uri + '/webwxgetvoice?msgid=%s&skey=%s' % (msgid, self.skey)
  1228. def get_voice(self, msgid):
  1229. """
  1230. 获取语音消息,下载语音到本地
  1231. :param msgid: 语音消息id
  1232. :return: 保存的本地语音文件路径
  1233. """
  1234. url = self.base_uri + '/webwxgetvoice?msgid=%s&skey=%s' % (msgid, self.skey)
  1235. r = self.session.get(url)
  1236. data = r.content
  1237. fn = 'voice_' + msgid + '.mp3'
  1238. with open(os.path.join(self.temp_pwd,fn), 'wb') as f:
  1239. f.write(data)
  1240. return fn
  1241. def set_remark_name(self, uid, name): # 设置联系人的备注名
  1242. url = self.base_uri + '/webwxoplog?lang=zh_CN&pass_ticket=%s' % self.pass_ticket
  1243. remark_name = self.to_unicode(name)
  1244. params = {
  1245. 'BaseRequest': self.base_request,
  1246. 'CmdId': 2,
  1247. 'RemarkName': remark_name,
  1248. 'UserName': uid
  1249. }
  1250. try:
  1251. r = self.session.post(url, data=json.dumps(params), timeout=60)
  1252. r.encoding = 'utf-8'
  1253. dic = json.loads(r.text)
  1254. return dic['BaseResponse']['ErrMsg']
  1255. except:
  1256. return None