選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

contact.py 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. import time
  2. import re
  3. import io
  4. import json
  5. import copy
  6. import logging
  7. from .. import config, utils
  8. from ..returnvalues import ReturnValue
  9. from ..storage import contact_change
  10. from ..utils import update_info_dict
  11. logger = logging.getLogger('itchat')
  12. def load_contact(core):
  13. core.update_chatroom = update_chatroom
  14. core.update_friend = update_friend
  15. core.get_contact = get_contact
  16. core.get_friends = get_friends
  17. core.get_chatrooms = get_chatrooms
  18. core.get_mps = get_mps
  19. core.set_alias = set_alias
  20. core.set_pinned = set_pinned
  21. core.accept_friend = accept_friend
  22. core.get_head_img = get_head_img
  23. core.create_chatroom = create_chatroom
  24. core.set_chatroom_name = set_chatroom_name
  25. core.delete_member_from_chatroom = delete_member_from_chatroom
  26. core.add_member_into_chatroom = add_member_into_chatroom
  27. def update_chatroom(self, userName, detailedMember=False):
  28. if not isinstance(userName, list):
  29. userName = [userName]
  30. url = '%s/webwxbatchgetcontact?type=ex&r=%s' % (
  31. self.loginInfo['url'], int(time.time()))
  32. headers = {
  33. 'ContentType': 'application/json; charset=UTF-8',
  34. 'User-Agent': config.USER_AGENT}
  35. data = {
  36. 'BaseRequest': self.loginInfo['BaseRequest'],
  37. 'Count': len(userName),
  38. 'List': [{
  39. 'UserName': u,
  40. 'ChatRoomId': '', } for u in userName], }
  41. chatroomList = json.loads(self.s.post(url, data=json.dumps(data), headers=headers
  42. ).content.decode('utf8', 'replace')).get('ContactList')
  43. if not chatroomList:
  44. return ReturnValue({'BaseResponse': {
  45. 'ErrMsg': 'No chatroom found',
  46. 'Ret': -1001, }})
  47. if detailedMember:
  48. def get_detailed_member_info(encryChatroomId, memberList):
  49. url = '%s/webwxbatchgetcontact?type=ex&r=%s' % (
  50. self.loginInfo['url'], int(time.time()))
  51. headers = {
  52. 'ContentType': 'application/json; charset=UTF-8',
  53. 'User-Agent': config.USER_AGENT, }
  54. data = {
  55. 'BaseRequest': self.loginInfo['BaseRequest'],
  56. 'Count': len(memberList),
  57. 'List': [{
  58. 'UserName': member['UserName'],
  59. 'EncryChatRoomId': encryChatroomId}
  60. for member in memberList], }
  61. return json.loads(self.s.post(url, data=json.dumps(data), headers=headers
  62. ).content.decode('utf8', 'replace'))['ContactList']
  63. MAX_GET_NUMBER = 50
  64. for chatroom in chatroomList:
  65. totalMemberList = []
  66. for i in range(int(len(chatroom['MemberList']) / MAX_GET_NUMBER + 1)):
  67. memberList = chatroom['MemberList'][i *
  68. MAX_GET_NUMBER: (i+1)*MAX_GET_NUMBER]
  69. totalMemberList += get_detailed_member_info(
  70. chatroom['EncryChatRoomId'], memberList)
  71. chatroom['MemberList'] = totalMemberList
  72. update_local_chatrooms(self, chatroomList)
  73. r = [self.storageClass.search_chatrooms(userName=c['UserName'])
  74. for c in chatroomList]
  75. return r if 1 < len(r) else r[0]
  76. def update_friend(self, userName):
  77. if not isinstance(userName, list):
  78. userName = [userName]
  79. url = '%s/webwxbatchgetcontact?type=ex&r=%s' % (
  80. self.loginInfo['url'], int(time.time()))
  81. headers = {
  82. 'ContentType': 'application/json; charset=UTF-8',
  83. 'User-Agent': config.USER_AGENT}
  84. data = {
  85. 'BaseRequest': self.loginInfo['BaseRequest'],
  86. 'Count': len(userName),
  87. 'List': [{
  88. 'UserName': u,
  89. 'EncryChatRoomId': '', } for u in userName], }
  90. friendList = json.loads(self.s.post(url, data=json.dumps(data), headers=headers
  91. ).content.decode('utf8', 'replace')).get('ContactList')
  92. update_local_friends(self, friendList)
  93. r = [self.storageClass.search_friends(userName=f['UserName'])
  94. for f in friendList]
  95. return r if len(r) != 1 else r[0]
  96. @contact_change
  97. def update_local_chatrooms(core, l):
  98. '''
  99. get a list of chatrooms for updating local chatrooms
  100. return a list of given chatrooms with updated info
  101. '''
  102. for chatroom in l:
  103. # format new chatrooms
  104. utils.emoji_formatter(chatroom, 'NickName')
  105. for member in chatroom['MemberList']:
  106. if 'NickName' in member:
  107. utils.emoji_formatter(member, 'NickName')
  108. if 'DisplayName' in member:
  109. utils.emoji_formatter(member, 'DisplayName')
  110. if 'RemarkName' in member:
  111. utils.emoji_formatter(member, 'RemarkName')
  112. # update it to old chatrooms
  113. oldChatroom = utils.search_dict_list(
  114. core.chatroomList, 'UserName', chatroom['UserName'])
  115. if oldChatroom:
  116. update_info_dict(oldChatroom, chatroom)
  117. # - update other values
  118. memberList = chatroom.get('MemberList', [])
  119. oldMemberList = oldChatroom['MemberList']
  120. if memberList:
  121. for member in memberList:
  122. oldMember = utils.search_dict_list(
  123. oldMemberList, 'UserName', member['UserName'])
  124. if oldMember:
  125. update_info_dict(oldMember, member)
  126. else:
  127. oldMemberList.append(member)
  128. else:
  129. core.chatroomList.append(chatroom)
  130. oldChatroom = utils.search_dict_list(
  131. core.chatroomList, 'UserName', chatroom['UserName'])
  132. # delete useless members
  133. if len(chatroom['MemberList']) != len(oldChatroom['MemberList']) and \
  134. chatroom['MemberList']:
  135. existsUserNames = [member['UserName']
  136. for member in chatroom['MemberList']]
  137. delList = []
  138. for i, member in enumerate(oldChatroom['MemberList']):
  139. if member['UserName'] not in existsUserNames:
  140. delList.append(i)
  141. delList.sort(reverse=True)
  142. for i in delList:
  143. del oldChatroom['MemberList'][i]
  144. # - update OwnerUin
  145. if oldChatroom.get('ChatRoomOwner') and oldChatroom.get('MemberList'):
  146. owner = utils.search_dict_list(oldChatroom['MemberList'],
  147. 'UserName', oldChatroom['ChatRoomOwner'])
  148. oldChatroom['OwnerUin'] = (owner or {}).get('Uin', 0)
  149. # - update IsAdmin
  150. if 'OwnerUin' in oldChatroom and oldChatroom['OwnerUin'] != 0:
  151. oldChatroom['IsAdmin'] = \
  152. oldChatroom['OwnerUin'] == int(core.loginInfo['wxuin'])
  153. else:
  154. oldChatroom['IsAdmin'] = None
  155. # - update Self
  156. newSelf = utils.search_dict_list(oldChatroom['MemberList'],
  157. 'UserName', core.storageClass.userName)
  158. oldChatroom['Self'] = newSelf or copy.deepcopy(core.loginInfo['User'])
  159. return {
  160. 'Type': 'System',
  161. 'Text': [chatroom['UserName'] for chatroom in l],
  162. 'SystemInfo': 'chatrooms',
  163. 'FromUserName': core.storageClass.userName,
  164. 'ToUserName': core.storageClass.userName, }
  165. @contact_change
  166. def update_local_friends(core, l):
  167. '''
  168. get a list of friends or mps for updating local contact
  169. '''
  170. fullList = core.memberList + core.mpList
  171. for friend in l:
  172. if 'NickName' in friend:
  173. utils.emoji_formatter(friend, 'NickName')
  174. if 'DisplayName' in friend:
  175. utils.emoji_formatter(friend, 'DisplayName')
  176. if 'RemarkName' in friend:
  177. utils.emoji_formatter(friend, 'RemarkName')
  178. oldInfoDict = utils.search_dict_list(
  179. fullList, 'UserName', friend['UserName'])
  180. if oldInfoDict is None:
  181. oldInfoDict = copy.deepcopy(friend)
  182. if oldInfoDict['VerifyFlag'] & 8 == 0:
  183. core.memberList.append(oldInfoDict)
  184. else:
  185. core.mpList.append(oldInfoDict)
  186. else:
  187. update_info_dict(oldInfoDict, friend)
  188. @contact_change
  189. def update_local_uin(core, msg):
  190. '''
  191. content contains uins and StatusNotifyUserName contains username
  192. they are in same order, so what I do is to pair them together
  193. I caught an exception in this method while not knowing why
  194. but don't worry, it won't cause any problem
  195. '''
  196. uins = re.search('<username>([^<]*?)<', msg['Content'])
  197. usernameChangedList = []
  198. r = {
  199. 'Type': 'System',
  200. 'Text': usernameChangedList,
  201. 'SystemInfo': 'uins', }
  202. if uins:
  203. uins = uins.group(1).split(',')
  204. usernames = msg['StatusNotifyUserName'].split(',')
  205. if 0 < len(uins) == len(usernames):
  206. for uin, username in zip(uins, usernames):
  207. if not '@' in username:
  208. continue
  209. fullContact = core.memberList + core.chatroomList + core.mpList
  210. userDicts = utils.search_dict_list(fullContact,
  211. 'UserName', username)
  212. if userDicts:
  213. if userDicts.get('Uin', 0) == 0:
  214. userDicts['Uin'] = uin
  215. usernameChangedList.append(username)
  216. logger.debug('Uin fetched: %s, %s' % (username, uin))
  217. else:
  218. if userDicts['Uin'] != uin:
  219. logger.debug('Uin changed: %s, %s' % (
  220. userDicts['Uin'], uin))
  221. else:
  222. if '@@' in username:
  223. core.storageClass.updateLock.release()
  224. update_chatroom(core, username)
  225. core.storageClass.updateLock.acquire()
  226. newChatroomDict = utils.search_dict_list(
  227. core.chatroomList, 'UserName', username)
  228. if newChatroomDict is None:
  229. newChatroomDict = utils.struct_friend_info({
  230. 'UserName': username,
  231. 'Uin': uin,
  232. 'Self': copy.deepcopy(core.loginInfo['User'])})
  233. core.chatroomList.append(newChatroomDict)
  234. else:
  235. newChatroomDict['Uin'] = uin
  236. elif '@' in username:
  237. core.storageClass.updateLock.release()
  238. update_friend(core, username)
  239. core.storageClass.updateLock.acquire()
  240. newFriendDict = utils.search_dict_list(
  241. core.memberList, 'UserName', username)
  242. if newFriendDict is None:
  243. newFriendDict = utils.struct_friend_info({
  244. 'UserName': username,
  245. 'Uin': uin, })
  246. core.memberList.append(newFriendDict)
  247. else:
  248. newFriendDict['Uin'] = uin
  249. usernameChangedList.append(username)
  250. logger.debug('Uin fetched: %s, %s' % (username, uin))
  251. else:
  252. logger.debug('Wrong length of uins & usernames: %s, %s' % (
  253. len(uins), len(usernames)))
  254. else:
  255. logger.debug('No uins in 51 message')
  256. logger.debug(msg['Content'])
  257. return r
  258. def get_contact(self, update=False):
  259. if not update:
  260. return utils.contact_deep_copy(self, self.chatroomList)
  261. def _get_contact(seq=0):
  262. url = '%s/webwxgetcontact?r=%s&seq=%s&skey=%s' % (self.loginInfo['url'],
  263. int(time.time()), seq, self.loginInfo['skey'])
  264. headers = {
  265. 'ContentType': 'application/json; charset=UTF-8',
  266. 'User-Agent': config.USER_AGENT, }
  267. try:
  268. r = self.s.get(url, headers=headers)
  269. except:
  270. logger.info(
  271. 'Failed to fetch contact, that may because of the amount of your chatrooms')
  272. for chatroom in self.get_chatrooms():
  273. self.update_chatroom(chatroom['UserName'], detailedMember=True)
  274. return 0, []
  275. j = json.loads(r.content.decode('utf-8', 'replace'))
  276. return j.get('Seq', 0), j.get('MemberList')
  277. seq, memberList = 0, []
  278. while 1:
  279. seq, batchMemberList = _get_contact(seq)
  280. memberList.extend(batchMemberList)
  281. if seq == 0:
  282. break
  283. chatroomList, otherList = [], []
  284. for m in memberList:
  285. if m['Sex'] != 0:
  286. otherList.append(m)
  287. elif '@@' in m['UserName']:
  288. chatroomList.append(m)
  289. elif '@' in m['UserName']:
  290. # mp will be dealt in update_local_friends as well
  291. otherList.append(m)
  292. if chatroomList:
  293. update_local_chatrooms(self, chatroomList)
  294. if otherList:
  295. update_local_friends(self, otherList)
  296. return utils.contact_deep_copy(self, chatroomList)
  297. def get_friends(self, update=False):
  298. if update:
  299. self.get_contact(update=True)
  300. return utils.contact_deep_copy(self, self.memberList)
  301. def get_chatrooms(self, update=False, contactOnly=False):
  302. if contactOnly:
  303. return self.get_contact(update=True)
  304. else:
  305. if update:
  306. self.get_contact(True)
  307. return utils.contact_deep_copy(self, self.chatroomList)
  308. def get_mps(self, update=False):
  309. if update:
  310. self.get_contact(update=True)
  311. return utils.contact_deep_copy(self, self.mpList)
  312. def set_alias(self, userName, alias):
  313. oldFriendInfo = utils.search_dict_list(
  314. self.memberList, 'UserName', userName)
  315. if oldFriendInfo is None:
  316. return ReturnValue({'BaseResponse': {
  317. 'Ret': -1001, }})
  318. url = '%s/webwxoplog?lang=%s&pass_ticket=%s' % (
  319. self.loginInfo['url'], 'zh_CN', self.loginInfo['pass_ticket'])
  320. data = {
  321. 'UserName': userName,
  322. 'CmdId': 2,
  323. 'RemarkName': alias,
  324. 'BaseRequest': self.loginInfo['BaseRequest'], }
  325. headers = {'User-Agent': config.USER_AGENT}
  326. r = self.s.post(url, json.dumps(data, ensure_ascii=False).encode('utf8'),
  327. headers=headers)
  328. r = ReturnValue(rawResponse=r)
  329. if r:
  330. oldFriendInfo['RemarkName'] = alias
  331. return r
  332. def set_pinned(self, userName, isPinned=True):
  333. url = '%s/webwxoplog?pass_ticket=%s' % (
  334. self.loginInfo['url'], self.loginInfo['pass_ticket'])
  335. data = {
  336. 'UserName': userName,
  337. 'CmdId': 3,
  338. 'OP': int(isPinned),
  339. 'BaseRequest': self.loginInfo['BaseRequest'], }
  340. headers = {'User-Agent': config.USER_AGENT}
  341. r = self.s.post(url, json=data, headers=headers)
  342. return ReturnValue(rawResponse=r)
  343. def accept_friend(self, userName, v4='', autoUpdate=True):
  344. url = f"{self.loginInfo['url']}/webwxverifyuser?r={int(time.time())}&pass_ticket={self.loginInfo['pass_ticket']}"
  345. data = {
  346. 'BaseRequest': self.loginInfo['BaseRequest'],
  347. 'Opcode': 3, # 3
  348. 'VerifyUserListSize': 1,
  349. 'VerifyUserList': [{
  350. 'Value': userName,
  351. 'VerifyUserTicket': v4, }],
  352. 'VerifyContent': '',
  353. 'SceneListCount': 1,
  354. 'SceneList': [33],
  355. 'skey': self.loginInfo['skey'], }
  356. headers = {
  357. 'ContentType': 'application/json; charset=UTF-8',
  358. 'User-Agent': config.USER_AGENT}
  359. r = self.s.post(url, headers=headers,
  360. data=json.dumps(data, ensure_ascii=False).encode('utf8', 'replace'))
  361. if autoUpdate:
  362. self.update_friend(userName)
  363. return ReturnValue(rawResponse=r)
  364. def get_head_img(self, userName=None, chatroomUserName=None, picDir=None):
  365. ''' get head image
  366. * if you want to get chatroom header: only set chatroomUserName
  367. * if you want to get friend header: only set userName
  368. * if you want to get chatroom member header: set both
  369. '''
  370. params = {
  371. 'userName': userName or chatroomUserName or self.storageClass.userName,
  372. 'skey': self.loginInfo['skey'],
  373. 'type': 'big', }
  374. url = '%s/webwxgeticon' % self.loginInfo['url']
  375. if chatroomUserName is None:
  376. infoDict = self.storageClass.search_friends(userName=userName)
  377. if infoDict is None:
  378. return ReturnValue({'BaseResponse': {
  379. 'ErrMsg': 'No friend found',
  380. 'Ret': -1001, }})
  381. else:
  382. if userName is None:
  383. url = '%s/webwxgetheadimg' % self.loginInfo['url']
  384. else:
  385. chatroom = self.storageClass.search_chatrooms(
  386. userName=chatroomUserName)
  387. if chatroomUserName is None:
  388. return ReturnValue({'BaseResponse': {
  389. 'ErrMsg': 'No chatroom found',
  390. 'Ret': -1001, }})
  391. if 'EncryChatRoomId' in chatroom:
  392. params['chatroomid'] = chatroom['EncryChatRoomId']
  393. params['chatroomid'] = params.get(
  394. 'chatroomid') or chatroom['UserName']
  395. headers = {'User-Agent': config.USER_AGENT}
  396. r = self.s.get(url, params=params, stream=True, headers=headers)
  397. tempStorage = io.BytesIO()
  398. for block in r.iter_content(1024):
  399. tempStorage.write(block)
  400. if picDir is None:
  401. return tempStorage.getvalue()
  402. with open(picDir, 'wb') as f:
  403. f.write(tempStorage.getvalue())
  404. tempStorage.seek(0)
  405. return ReturnValue({'BaseResponse': {
  406. 'ErrMsg': 'Successfully downloaded',
  407. 'Ret': 0, },
  408. 'PostFix': utils.get_image_postfix(tempStorage.read(20)), })
  409. def create_chatroom(self, memberList, topic=''):
  410. url = '%s/webwxcreatechatroom?pass_ticket=%s&r=%s' % (
  411. self.loginInfo['url'], self.loginInfo['pass_ticket'], int(time.time()))
  412. data = {
  413. 'BaseRequest': self.loginInfo['BaseRequest'],
  414. 'MemberCount': len(memberList.split(',')),
  415. 'MemberList': [{'UserName': member} for member in memberList.split(',')],
  416. 'Topic': topic, }
  417. headers = {
  418. 'content-type': 'application/json; charset=UTF-8',
  419. 'User-Agent': config.USER_AGENT}
  420. r = self.s.post(url, headers=headers,
  421. data=json.dumps(data, ensure_ascii=False).encode('utf8', 'ignore'))
  422. return ReturnValue(rawResponse=r)
  423. def set_chatroom_name(self, chatroomUserName, name):
  424. url = '%s/webwxupdatechatroom?fun=modtopic&pass_ticket=%s' % (
  425. self.loginInfo['url'], self.loginInfo['pass_ticket'])
  426. data = {
  427. 'BaseRequest': self.loginInfo['BaseRequest'],
  428. 'ChatRoomName': chatroomUserName,
  429. 'NewTopic': name, }
  430. headers = {
  431. 'content-type': 'application/json; charset=UTF-8',
  432. 'User-Agent': config.USER_AGENT}
  433. r = self.s.post(url, headers=headers,
  434. data=json.dumps(data, ensure_ascii=False).encode('utf8', 'ignore'))
  435. return ReturnValue(rawResponse=r)
  436. def delete_member_from_chatroom(self, chatroomUserName, memberList):
  437. url = '%s/webwxupdatechatroom?fun=delmember&pass_ticket=%s' % (
  438. self.loginInfo['url'], self.loginInfo['pass_ticket'])
  439. data = {
  440. 'BaseRequest': self.loginInfo['BaseRequest'],
  441. 'ChatRoomName': chatroomUserName,
  442. 'DelMemberList': ','.join([member['UserName'] for member in memberList]), }
  443. headers = {
  444. 'content-type': 'application/json; charset=UTF-8',
  445. 'User-Agent': config.USER_AGENT}
  446. r = self.s.post(url, data=json.dumps(data), headers=headers)
  447. return ReturnValue(rawResponse=r)
  448. def add_member_into_chatroom(self, chatroomUserName, memberList,
  449. useInvitation=False):
  450. ''' add or invite member into chatroom
  451. * there are two ways to get members into chatroom: invite or directly add
  452. * but for chatrooms with more than 40 users, you can only use invite
  453. * but don't worry we will auto-force userInvitation for you when necessary
  454. '''
  455. if not useInvitation:
  456. chatroom = self.storageClass.search_chatrooms(
  457. userName=chatroomUserName)
  458. if not chatroom:
  459. chatroom = self.update_chatroom(chatroomUserName)
  460. if len(chatroom['MemberList']) > self.loginInfo['InviteStartCount']:
  461. useInvitation = True
  462. if useInvitation:
  463. fun, memberKeyName = 'invitemember', 'InviteMemberList'
  464. else:
  465. fun, memberKeyName = 'addmember', 'AddMemberList'
  466. url = '%s/webwxupdatechatroom?fun=%s&pass_ticket=%s' % (
  467. self.loginInfo['url'], fun, self.loginInfo['pass_ticket'])
  468. params = {
  469. 'BaseRequest': self.loginInfo['BaseRequest'],
  470. 'ChatRoomName': chatroomUserName,
  471. memberKeyName: memberList, }
  472. headers = {
  473. 'content-type': 'application/json; charset=UTF-8',
  474. 'User-Agent': config.USER_AGENT}
  475. r = self.s.post(url, data=json.dumps(params), headers=headers)
  476. return ReturnValue(rawResponse=r)