Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

489 lines
20KB

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