wxbot.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. #!/usr/bin/env python
  2. # coding: utf-8
  3. import pyqrcode
  4. import requests
  5. import json
  6. import xml.dom.minidom
  7. import urllib
  8. import time
  9. import re
  10. import random
  11. from requests.exceptions import *
  12. class WXBot:
  13. """WXBot, a framework to process WeChat messages"""
  14. def __init__(self):
  15. self.DEBUG = False
  16. self.uuid = ''
  17. self.base_uri = ''
  18. self.redirect_uri = ''
  19. self.uin = ''
  20. self.sid = ''
  21. self.skey = ''
  22. self.pass_ticket = ''
  23. self.device_id = 'e' + repr(random.random())[2:17]
  24. self.base_request = {}
  25. self.sync_key_str = ''
  26. self.sync_key = []
  27. self.user = {}
  28. self.account_info = {}
  29. self.member_list = [] # all kind of accounts: contacts, public accounts, groups, special accounts
  30. self.contact_list = [] # contact list
  31. self.public_list = [] # public account list
  32. self.group_list = [] # group chat list
  33. self.special_list = [] # special list account
  34. self.group_members = {} # members of all groups
  35. self.sync_host = ''
  36. self.session = requests.Session()
  37. self.session.headers.update({'User-Agent': 'Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5'})
  38. self.conf = {'qr': 'png'}
  39. def get_contact(self):
  40. """Get information of all contacts of current account."""
  41. url = self.base_uri + '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' \
  42. % (self.pass_ticket, self.skey, int(time.time()))
  43. r = self.session.post(url, data='{}')
  44. r.encoding = 'utf-8'
  45. if self.DEBUG:
  46. with open('contacts.json', 'w') as f:
  47. f.write(r.text.encode('utf-8'))
  48. dic = json.loads(r.text)
  49. self.member_list = dic['MemberList']
  50. special_users = ['newsapp', 'fmessage', 'filehelper', 'weibo', 'qqmail',
  51. 'fmessage', 'tmessage', 'qmessage', 'qqsync', 'floatbottle',
  52. 'lbsapp', 'shakeapp', 'medianote', 'qqfriend', 'readerapp',
  53. 'blogapp', 'facebookapp', 'masssendapp', 'meishiapp',
  54. 'feedsapp', 'voip', 'blogappweixin', 'weixin', 'brandsessionholder',
  55. 'weixinreminder', 'wxid_novlwrv3lqwv11', 'gh_22b87fa7cb3c',
  56. 'officialaccounts', 'notification_messages', 'wxid_novlwrv3lqwv11',
  57. 'gh_22b87fa7cb3c', 'wxitil', 'userexperience_alarm', 'notification_messages']
  58. self.contact_list = []
  59. self.public_list = []
  60. self.special_list = []
  61. self.group_list = []
  62. for contact in self.member_list:
  63. if contact['VerifyFlag'] & 8 != 0: # public account
  64. self.public_list.append(contact)
  65. self.account_info[contact['UserName']] = {'type': 'public', 'info': contact}
  66. elif contact['UserName'] in special_users: # special account
  67. self.special_list.append(contact)
  68. self.account_info[contact['UserName']] = {'type': 'special', 'info': contact}
  69. elif contact['UserName'].find('@@') != -1: # group
  70. self.group_list.append(contact)
  71. self.account_info[contact['UserName']] = {'type': 'group', 'info': contact}
  72. elif contact['UserName'] == self.user['UserName']: # self
  73. self.account_info[contact['UserName']] = {'type': 'self', 'info': contact}
  74. pass
  75. else:
  76. self.contact_list.append(contact)
  77. self.group_members = self.batch_get_group_members()
  78. for group in self.group_members:
  79. for member in self.group_members[group]:
  80. if member['UserName'] not in self.account_info:
  81. self.account_info[member['UserName']] = {'type': 'group_member', 'info': member, 'group': group}
  82. if self.DEBUG:
  83. with open('contact_list.json', 'w') as f:
  84. f.write(json.dumps(self.contact_list))
  85. with open('special_list.json', 'w') as f:
  86. f.write(json.dumps(self.special_list))
  87. with open('group_list.json', 'w') as f:
  88. f.write(json.dumps(self.group_list))
  89. with open('public_list.json', 'w') as f:
  90. f.write(json.dumps(self.public_list))
  91. with open('member_list.json', 'w') as f:
  92. f.write(json.dumps(self.member_list))
  93. with open('group_users.json', 'w') as f:
  94. f.write(json.dumps(self.group_members))
  95. with open('account_info.json', 'w') as f:
  96. f.write(json.dumps(self.account_info))
  97. return True
  98. def batch_get_group_members(self):
  99. """Get information of accounts in all groups at once."""
  100. url = self.base_uri + '/webwxbatchgetcontact?type=ex&r=%s&pass_ticket=%s' % (int(time.time()), self.pass_ticket)
  101. params = {
  102. 'BaseRequest': self.base_request,
  103. "Count": len(self.group_list),
  104. "List": [{"UserName": group['UserName'], "EncryChatRoomId": ""} for group in self.group_list]
  105. }
  106. r = self.session.post(url, data=json.dumps(params))
  107. r.encoding = 'utf-8'
  108. dic = json.loads(r.text)
  109. group_members = {}
  110. for group in dic['ContactList']:
  111. gid = group['UserName']
  112. members = group['MemberList']
  113. group_members[gid] = members
  114. return group_members
  115. def get_account_info(self, uid):
  116. if uid in self.account_info:
  117. return self.account_info[uid]
  118. else:
  119. return None
  120. def get_account_name(self, uid):
  121. info = self.get_account_info(uid)
  122. if info is None:
  123. return 'unknown'
  124. info = info['info']
  125. name = {}
  126. if 'RemarkName' in info and info['RemarkName']:
  127. name['remark_name'] = info['RemarkName']
  128. if 'NickName' in info and info['NickName']:
  129. name['nickname'] = info['NickName']
  130. if 'DisplayName' in info and info['DisplayName']:
  131. name['display_name'] = info['DisplayName']
  132. return name
  133. @staticmethod
  134. def get_prefer_name(name):
  135. if 'remark_name' in name:
  136. return name['remark_name']
  137. if 'display_name' in name:
  138. return name['display_name']
  139. if 'nickname' in name:
  140. return name['nickname']
  141. return 'unknown'
  142. def get_user_type(self, wx_user_id):
  143. """
  144. Get the relationship of a account and current user.
  145. :param wx_user_id:
  146. :return: The type of the account.
  147. """
  148. for account in self.contact_list:
  149. if wx_user_id == account['UserName']:
  150. return 'contact'
  151. for account in self.public_list:
  152. if wx_user_id == account['UserName']:
  153. return 'public'
  154. for account in self.special_list:
  155. if wx_user_id == account['UserName']:
  156. return 'special'
  157. for account in self.group_list:
  158. if wx_user_id == account['UserName']:
  159. return 'group'
  160. for group in self.group_members:
  161. for member in self.group_members[group]:
  162. if member['UserName'] == wx_user_id:
  163. return 'group_member'
  164. return 'unknown'
  165. def is_contact(self, uid):
  166. for account in self.contact_list:
  167. if uid == account['UserName']:
  168. return True
  169. return False
  170. def is_public(self, uid):
  171. for account in self.public_list:
  172. if uid == account['UserName']:
  173. return True
  174. return False
  175. def is_special(self, uid):
  176. for account in self.special_list:
  177. if uid == account['UserName']:
  178. return True
  179. return False
  180. def handle_msg_all(self, msg):
  181. """
  182. The function to process all WeChat messages, please override this function.
  183. msg:
  184. msg_id -> id of the received WeChat message
  185. msg_type_id -> the type of the message
  186. user -> the account that the message if sent from
  187. content -> content of the message
  188. :param msg: The received message.
  189. :return: None
  190. """
  191. pass
  192. def extract_msg_content(self, msg_type_id, msg):
  193. """
  194. content_type_id:
  195. 0 -> Text
  196. 1 -> Location
  197. 3 -> Image
  198. 4 -> Voice
  199. 5 -> Recommend
  200. 6 -> Animation
  201. 7 -> Share
  202. 8 -> Video
  203. 9 -> VideoCall
  204. 10 -> Redraw
  205. 11 -> Empty
  206. 99 -> Unknown
  207. :param msg_type_id: The type of the received message.
  208. :param msg: The received message.
  209. :return: The extracted content of the message.
  210. """
  211. mtype = msg['MsgType']
  212. content = msg['Content'].replace('&lt;', '<').replace('&gt;', '>')
  213. msg_id = msg['MsgId']
  214. msg_content = {}
  215. if msg_type_id == 0:
  216. return {'type': 11, 'data': ''}
  217. elif msg_type_id == 2: # File Helper
  218. return {'type': 0, 'data': content.replace('<br/>', '\n')}
  219. elif msg_type_id == 3: # Group
  220. sp = content.find('<br/>')
  221. uid = content[:sp]
  222. content = content[sp:]
  223. content = content.replace('<br/>', '')
  224. uid = uid[:-1]
  225. msg_content['user'] = {'id': uid, 'name': self.get_prefer_name(self.get_account_name(uid))}
  226. if self.DEBUG:
  227. print msg_content['user']['name']
  228. else: # Self, Contact, Special, Public, Unknown
  229. pass
  230. msg_prefix = (msg_content['user']['name'] + ':') if 'user' in msg_content else ''
  231. if mtype == 1:
  232. if content.find('http://weixin.qq.com/cgi-bin/redirectforward?args=') != -1:
  233. r = self.session.get(content)
  234. r.encoding = 'gbk'
  235. data = r.text
  236. pos = self.search_content('title', data, 'xml')
  237. msg_content['type'] = 1
  238. msg_content['data'] = pos
  239. msg_content['detail'] = data
  240. if self.DEBUG:
  241. print ' %s[Location] I am at %s ' % (msg_prefix, pos)
  242. else:
  243. msg_content['type'] = 0
  244. msg_content['data'] = content.replace(u'\u2005', '')
  245. if self.DEBUG:
  246. print ' %s[Text] %s' % (msg_prefix, msg_content['data'])
  247. elif mtype == 3:
  248. msg_content['type'] = 3
  249. msg_content['data'] = self.get_msg_img_url(msg_id)
  250. if self.DEBUG:
  251. image = self.get_msg_img(msg_id)
  252. print ' %s[Image] %s' % (msg_prefix, image)
  253. elif mtype == 34:
  254. msg_content['type'] = 4
  255. msg_content['data'] = self.get_voice_url(msg_id)
  256. if self.DEBUG:
  257. voice = self.get_voice(msg_id)
  258. print ' %s[Voice] %s' % (msg_prefix, voice)
  259. elif mtype == 42:
  260. msg_content['type'] = 5
  261. info = msg['RecommendInfo']
  262. msg_content['data'] = {'nickname': info['NickName'],
  263. 'alias': info['Alias'],
  264. 'province': info['Province'],
  265. 'city': info['City'],
  266. 'gender': ['unknown', 'male', 'female'][info['Sex']]}
  267. if self.DEBUG:
  268. print ' %s[Recommend]' % msg_prefix
  269. print ' -----------------------------'
  270. print ' | NickName: %s' % info['NickName']
  271. print ' | Alias: %s' % info['Alias']
  272. print ' | Local: %s %s' % (info['Province'], info['City'])
  273. print ' | Gender: %s' % ['unknown', 'male', 'female'][info['Sex']]
  274. print ' -----------------------------'
  275. elif mtype == 47:
  276. msg_content['type'] = 6
  277. msg_content['data'] = self.search_content('cdnurl', content)
  278. if self.DEBUG:
  279. print ' %s[Animation] %s' % (msg_prefix, msg_content['data'])
  280. elif mtype == 49:
  281. msg_content['type'] = 7
  282. app_msg_type = ''
  283. if msg['AppMsgType'] == 3:
  284. app_msg_type = 'music'
  285. elif msg['AppMsgType'] == 5:
  286. app_msg_type = 'link'
  287. elif msg['AppMsgType'] == 7:
  288. app_msg_type = 'weibo'
  289. else:
  290. app_msg_type = 'unknown'
  291. msg_content['data'] = {'type': app_msg_type,
  292. 'title': msg['FileName'],
  293. 'desc': self.search_content('des', content, 'xml'),
  294. 'url': msg['Url'],
  295. 'from': self.search_content('appname', content, 'xml')}
  296. if self.DEBUG:
  297. print ' %s[Share] %s' % (msg_prefix, app_msg_type)
  298. print ' --------------------------'
  299. print ' | title: %s' % msg['FileName']
  300. print ' | desc: %s' % self.search_content('des', content, 'xml')
  301. print ' | link: %s' % msg['Url']
  302. print ' | from: %s' % self.search_content('appname', content, 'xml')
  303. print ' --------------------------'
  304. elif mtype == 62:
  305. msg_content['type'] = 8
  306. msg_content['data'] = content
  307. if self.DEBUG:
  308. print ' %s[Video] Please check on mobiles' % msg_prefix
  309. elif mtype == 53:
  310. msg_content['type'] = 9
  311. msg_content['data'] = content
  312. if self.DEBUG:
  313. print ' %s[Video Call]' % msg_prefix
  314. elif mtype == 10002:
  315. msg_content['type'] = 10
  316. msg_content['data'] = content
  317. if self.DEBUG:
  318. print ' %s[Redraw]' % msg_prefix
  319. else:
  320. msg_content['type'] = 99
  321. msg_content['data'] = content
  322. if self.DEBUG:
  323. print ' %s[Unknown]' % msg_prefix
  324. return msg_content
  325. def handle_msg(self, r):
  326. """
  327. The inner function that processes raw WeChat messages.
  328. msg_type_id:
  329. 0 -> Init
  330. 1 -> Self
  331. 2 -> FileHelper
  332. 3 -> Group
  333. 4 -> Contact
  334. 5 -> Public
  335. 6 -> Special
  336. 99 -> Unknown
  337. :param r: The raw data of the messages.
  338. :return: None
  339. """
  340. for msg in r['AddMsgList']:
  341. msg_type_id = 99
  342. user = {'id': msg['FromUserName']}
  343. if msg['MsgType'] == 51: # init message
  344. msg_type_id = 0
  345. elif msg['FromUserName'] == self.user['UserName']: # Self
  346. msg_type_id = 1
  347. user['name'] = 'self'
  348. elif msg['ToUserName'] == 'filehelper': # File Helper
  349. msg_type_id = 2
  350. user['name'] = 'file_helper'
  351. elif msg['FromUserName'][:2] == '@@': # Group
  352. msg_type_id = 3
  353. user['name'] = self.get_prefer_name(self.get_account_name(user['id']))
  354. elif self.is_contact(msg['FromUserName']): # Contact
  355. msg_type_id = 4
  356. user['name'] = self.get_prefer_name(self.get_account_name(user['id']))
  357. elif self.is_public(msg['FromUserName']): # Public
  358. msg_type_id = 5
  359. user['name'] = self.get_prefer_name(self.get_account_name(user['id']))
  360. elif self.is_special(msg['FromUserName']): # Special
  361. msg_type_id = 6
  362. user['name'] = self.get_prefer_name(self.get_account_name(user['id']))
  363. if self.DEBUG and msg_type_id != 0:
  364. print '[MSG] %s:' % user['name']
  365. content = self.extract_msg_content(msg_type_id, msg)
  366. message = {'msg_type_id': msg_type_id,
  367. 'msg_id': msg['MsgId'],
  368. 'content': content,
  369. 'user': user}
  370. self.handle_msg_all(message)
  371. def schedule(self):
  372. """
  373. The function to do schedule works.
  374. This function will be called a lot of times.
  375. Please override this if needed.
  376. :return: None
  377. """
  378. pass
  379. def proc_msg(self):
  380. self.test_sync_check()
  381. while True:
  382. check_time = time.time()
  383. [retcode, selector] = self.sync_check()
  384. if retcode == '1100': # logout from mobile
  385. break
  386. elif retcode == '1101': # login web WeChat from other devide
  387. break
  388. elif retcode == '0':
  389. if selector == '2': # new message
  390. r = self.sync()
  391. if r is not None:
  392. self.handle_msg(r)
  393. elif selector == '7': # Play WeChat on mobile
  394. r = self.sync()
  395. if r is not None:
  396. self.handle_msg(r)
  397. elif selector == '0': # nothing
  398. pass
  399. else:
  400. pass
  401. self.schedule()
  402. check_time = time.time() - check_time
  403. if check_time < 0.5:
  404. time.sleep(0.5 - check_time)
  405. def send_msg_by_uid(self, word, dst='filehelper'):
  406. url = self.base_uri + '/webwxsendmsg?pass_ticket=%s' % self.pass_ticket
  407. msg_id = str(int(time.time() * 1000)) + str(random.random())[:5].replace('.', '')
  408. if type(word) == 'str':
  409. word = word.decode('utf-8')
  410. params = {
  411. 'BaseRequest': self.base_request,
  412. 'Msg': {
  413. "Type": 1,
  414. "Content": word,
  415. "FromUserName": self.user['UserName'],
  416. "ToUserName": dst,
  417. "LocalID": msg_id,
  418. "ClientMsgId": msg_id
  419. }
  420. }
  421. headers = {'content-type': 'application/json; charset=UTF-8'}
  422. data = json.dumps(params, ensure_ascii=False).encode('utf8')
  423. try:
  424. r = self.session.post(url, data=data, headers=headers)
  425. except (ConnectionError, ReadTimeout):
  426. return False
  427. dic = r.json()
  428. return dic['BaseResponse']['Ret'] == 0
  429. def send_msg(self, name, word, isfile=False):
  430. uid = self.get_user_id(name)
  431. if uid:
  432. if isfile:
  433. with open(word, 'r') as f:
  434. result = True
  435. for line in f.readlines():
  436. line = line.replace('\n', '')
  437. print '-> ' + name + ': ' + line
  438. if self.send_msg_by_uid(line, uid):
  439. pass
  440. else:
  441. result = False
  442. time.sleep(1)
  443. return result
  444. else:
  445. if self.send_msg_by_uid(word, uid):
  446. return True
  447. else:
  448. return False
  449. else:
  450. if self.DEBUG:
  451. print '[ERROR] This user does not exist .'
  452. return True
  453. @staticmethod
  454. def search_content(key, content, fmat='attr'):
  455. if fmat == 'attr':
  456. pm = re.search(key + '\s?=\s?"([^"<]+)"', content)
  457. if pm:
  458. return pm.group(1)
  459. elif fmat == 'xml':
  460. pm = re.search('<{0}>([^<]+)</{0}>'.format(key), content)
  461. if pm:
  462. return pm.group(1)
  463. return 'unknown'
  464. def run(self):
  465. self.get_uuid()
  466. self.gen_qr_code('qr.png')
  467. print '[INFO] Please use WeCaht to scan the QR code .'
  468. self.wait4login(1)
  469. print '[INFO] Please confirm to login .'
  470. self.wait4login(0)
  471. if self.login():
  472. print '[INFO] Web WeChat login succeed .'
  473. else:
  474. print '[ERROR] Web WeChat login failed .'
  475. return
  476. if self.init():
  477. print '[INFO] Web WeChat init succeed .'
  478. else:
  479. print '[INFO] Web WeChat init failed'
  480. return
  481. self.status_notify()
  482. self.get_contact()
  483. print '[INFO] Get %d contacts' % len(self.contact_list)
  484. print '[INFO] Start to process messages .'
  485. self.proc_msg()
  486. def get_uuid(self):
  487. url = 'https://login.weixin.qq.com/jslogin'
  488. params = {
  489. 'appid': 'wx782c26e4c19acffb',
  490. 'fun': 'new',
  491. 'lang': 'zh_CN',
  492. '_': int(time.time()) * 1000 + random.randint(1, 999),
  493. }
  494. r = self.session.get(url, params=params)
  495. r.encoding = 'utf-8'
  496. data = r.text
  497. regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)"'
  498. pm = re.search(regx, data)
  499. if pm:
  500. code = pm.group(1)
  501. self.uuid = pm.group(2)
  502. return code == '200'
  503. return False
  504. def gen_qr_code(self, qr_file_path):
  505. string = 'https://login.weixin.qq.com/l/' + self.uuid
  506. qr = pyqrcode.create(string)
  507. if self.conf['qr'] == 'png':
  508. qr.png(qr_file_path)
  509. elif self.conf['qr'] == 'tty':
  510. print(qr.terminal(quiet_zone=1))
  511. def wait4login(self, tip):
  512. time.sleep(tip)
  513. url = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s' \
  514. % (tip, self.uuid, int(time.time()))
  515. r = self.session.get(url)
  516. r.encoding = 'utf-8'
  517. data = r.text
  518. param = re.search(r'window.code=(\d+);', data)
  519. code = param.group(1)
  520. if code == '201':
  521. return True
  522. elif code == '200':
  523. param = re.search(r'window.redirect_uri="(\S+?)";', data)
  524. redirect_uri = param.group(1) + '&fun=new'
  525. self.redirect_uri = redirect_uri
  526. self.base_uri = redirect_uri[:redirect_uri.rfind('/')]
  527. return True
  528. elif code == '408':
  529. print '[ERROR] WeChat login timeout .'
  530. else:
  531. print '[ERROR] WeChat login exception .'
  532. return False
  533. def login(self):
  534. r = self.session.get(self.redirect_uri)
  535. r.encoding = 'utf-8'
  536. data = r.text
  537. doc = xml.dom.minidom.parseString(data)
  538. root = doc.documentElement
  539. for node in root.childNodes:
  540. if node.nodeName == 'skey':
  541. self.skey = node.childNodes[0].data
  542. elif node.nodeName == 'wxsid':
  543. self.sid = node.childNodes[0].data
  544. elif node.nodeName == 'wxuin':
  545. self.uin = node.childNodes[0].data
  546. elif node.nodeName == 'pass_ticket':
  547. self.pass_ticket = node.childNodes[0].data
  548. if '' in (self.skey, self.sid, self.uin, self.pass_ticket):
  549. return False
  550. self.base_request = {
  551. 'Uin': self.uin,
  552. 'Sid': self.sid,
  553. 'Skey': self.skey,
  554. 'DeviceID': self.device_id,
  555. }
  556. return True
  557. def init(self):
  558. url = self.base_uri + '/webwxinit?r=%i&lang=en_US&pass_ticket=%s' % (int(time.time()), self.pass_ticket)
  559. params = {
  560. 'BaseRequest': self.base_request
  561. }
  562. r = self.session.post(url, data=json.dumps(params))
  563. r.encoding = 'utf-8'
  564. dic = json.loads(r.text)
  565. self.sync_key = dic['SyncKey']
  566. self.user = dic['User']
  567. self.sync_key_str = '|'.join([str(keyVal['Key']) + '_' + str(keyVal['Val'])
  568. for keyVal in self.sync_key['List']])
  569. return dic['BaseResponse']['Ret'] == 0
  570. def status_notify(self):
  571. url = self.base_uri + '/webwxstatusnotify?lang=zh_CN&pass_ticket=%s' % self.pass_ticket
  572. self.base_request['Uin'] = int(self.base_request['Uin'])
  573. params = {
  574. 'BaseRequest': self.base_request,
  575. "Code": 3,
  576. "FromUserName": self.user['UserName'],
  577. "ToUserName": self.user['UserName'],
  578. "ClientMsgId": int(time.time())
  579. }
  580. r = self.session.post(url, data=json.dumps(params))
  581. r.encoding = 'utf-8'
  582. dic = json.loads(r.text)
  583. return dic['BaseResponse']['Ret'] == 0
  584. def test_sync_check(self):
  585. for host in ['webpush', 'webpush2']:
  586. self.sync_host = host
  587. retcode = self.sync_check()[0]
  588. if retcode == '0':
  589. return True
  590. return False
  591. def sync_check(self):
  592. params = {
  593. 'r': int(time.time()),
  594. 'sid': self.sid,
  595. 'uin': self.uin,
  596. 'skey': self.skey,
  597. 'deviceid': self.device_id,
  598. 'synckey': self.sync_key_str,
  599. '_': int(time.time()),
  600. }
  601. url = 'https://' + self.sync_host + '.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?' + urllib.urlencode(params)
  602. try:
  603. r = self.session.get(url)
  604. except (ConnectionError, ReadTimeout):
  605. return [-1, -1]
  606. r.encoding = 'utf-8'
  607. data = r.text
  608. pm = re.search(r'window.synccheck=\{retcode:"(\d+)",selector:"(\d+)"\}', data)
  609. retcode = pm.group(1)
  610. selector = pm.group(2)
  611. return [retcode, selector]
  612. def sync(self):
  613. url = self.base_uri + '/webwxsync?sid=%s&skey=%s&lang=en_US&pass_ticket=%s' \
  614. % (self.sid, self.skey, self.pass_ticket)
  615. params = {
  616. 'BaseRequest': self.base_request,
  617. 'SyncKey': self.sync_key,
  618. 'rr': ~int(time.time())
  619. }
  620. try:
  621. r = self.session.post(url, data=json.dumps(params))
  622. except (ConnectionError, ReadTimeout):
  623. return None
  624. r.encoding = 'utf-8'
  625. dic = json.loads(r.text)
  626. if dic['BaseResponse']['Ret'] == 0:
  627. self.sync_key = dic['SyncKey']
  628. self.sync_key_str = '|'.join([str(keyVal['Key']) + '_' + str(keyVal['Val'])
  629. for keyVal in self.sync_key['List']])
  630. return dic
  631. def get_icon(self, uid):
  632. url = self.base_uri + '/webwxgeticon?username=%s&skey=%s' % (uid, self.skey)
  633. r = self.session.get(url)
  634. data = r.content
  635. fn = 'img_' + uid + '.jpg'
  636. with open(fn, 'wb') as f:
  637. f.write(data)
  638. return fn
  639. def get_head_img(self, uid):
  640. url = self.base_uri + '/webwxgetheadimg?username=%s&skey=%s' % (uid, self.skey)
  641. r = self.session.get(url)
  642. data = r.content
  643. fn = 'img_' + uid + '.jpg'
  644. with open(fn, 'wb') as f:
  645. f.write(data)
  646. return fn
  647. def get_msg_img_url(self, msgid):
  648. return self.base_uri + '/webwxgetmsgimg?MsgID=%s&skey=%s' % (msgid, self.skey)
  649. def get_msg_img(self, msgid):
  650. url = self.base_uri + '/webwxgetmsgimg?MsgID=%s&skey=%s' % (msgid, self.skey)
  651. r = self.session.get(url)
  652. data = r.content
  653. fn = 'img_' + msgid + '.jpg'
  654. with open(fn, 'wb') as f:
  655. f.write(data)
  656. return fn
  657. def get_voice_url(self, msgid):
  658. return self.base_uri + '/webwxgetvoice?msgid=%s&skey=%s' % (msgid, self.skey)
  659. def get_voice(self, msgid):
  660. url = self.base_uri + '/webwxgetvoice?msgid=%s&skey=%s' % (msgid, self.skey)
  661. r = self.session.get(url)
  662. data = r.content
  663. fn = 'voice_' + msgid + '.mp3'
  664. with open(fn, 'wb') as f:
  665. f.write(data)
  666. return fn