PageRenderTime 62ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/client/wxbot.py

https://bitbucket.org/SES-xuelan/dingdang4magicmirror
Python | 1506 lines | 1501 code | 3 blank | 2 comment | 0 complexity | bc9e0f122f6ba283fb13e1fcd55393db MD5 | raw file

Large files files are truncated, but you can click here to view the full file

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

Large files files are truncated, but you can click here to view the full file