You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

231 lines
8.8KB

  1. # encoding:utf-8
  2. """
  3. wechat channel
  4. """
  5. import itchat
  6. import json
  7. from itchat.content import *
  8. from channel.channel import Channel
  9. from concurrent.futures import ThreadPoolExecutor
  10. from common.log import logger
  11. from common.tmp_dir import TmpDir
  12. from config import conf
  13. import requests
  14. import io
  15. import time
  16. thread_pool = ThreadPoolExecutor(max_workers=8)
  17. @itchat.msg_register(TEXT)
  18. def handler_single_msg(msg):
  19. WechatChannel().handle_text(msg)
  20. return None
  21. @itchat.msg_register(TEXT, isGroupChat=True)
  22. def handler_group_msg(msg):
  23. WechatChannel().handle_group(msg)
  24. return None
  25. @itchat.msg_register(VOICE)
  26. def handler_single_voice(msg):
  27. WechatChannel().handle_voice(msg)
  28. return None
  29. class WechatChannel(Channel):
  30. def __init__(self):
  31. pass
  32. def startup(self):
  33. # login by scan QRCode
  34. itchat.auto_login(enableCmdQR=2, hotReload=conf().get('hot_reload', False))
  35. # start message listener
  36. itchat.run()
  37. def handle_voice(self, msg):
  38. if conf().get('speech_recognition') != True :
  39. return
  40. logger.debug("[WX]receive voice msg: " + msg['FileName'])
  41. thread_pool.submit(self._do_handle_voice, msg)
  42. def _do_handle_voice(self, msg):
  43. from_user_id = msg['FromUserName']
  44. other_user_id = msg['User']['UserName']
  45. if from_user_id == other_user_id:
  46. file_name = TmpDir().path() + msg['FileName']
  47. msg.download(file_name)
  48. query = super().build_voice_to_text(file_name)
  49. if conf().get('voice_reply_voice'):
  50. self._do_send_voice(query, from_user_id)
  51. else:
  52. self._do_send_text(query, from_user_id)
  53. def handle_text(self, msg):
  54. logger.debug("[WX]receive text msg: " + json.dumps(msg, ensure_ascii=False))
  55. content = msg['Text']
  56. self._handle_single_msg(msg, content)
  57. def _handle_single_msg(self, msg, content):
  58. from_user_id = msg['FromUserName']
  59. to_user_id = msg['ToUserName'] # 接收人id
  60. other_user_id = msg['User']['UserName'] # 对手方id
  61. create_time = msg['CreateTime'] # 消息时间
  62. match_prefix = self.check_prefix(content, conf().get('single_chat_prefix'))
  63. if conf().get('hot_reload') == True and int(create_time) < int(time.time()) - 60: #跳过1分钟前的历史消息
  64. logger.debug("[WX]history message skipped")
  65. return
  66. if "」\n- - - - - - - - - - - - - - -" in content:
  67. logger.debug("[WX]reference query skipped")
  68. return
  69. if from_user_id == other_user_id and match_prefix is not None:
  70. # 好友向自己发送消息
  71. if match_prefix != '':
  72. str_list = content.split(match_prefix, 1)
  73. if len(str_list) == 2:
  74. content = str_list[1].strip()
  75. img_match_prefix = self.check_prefix(content, conf().get('image_create_prefix'))
  76. if img_match_prefix:
  77. content = content.split(img_match_prefix, 1)[1].strip()
  78. thread_pool.submit(self._do_send_img, content, from_user_id)
  79. else :
  80. thread_pool.submit(self._do_send_text, content, from_user_id)
  81. elif to_user_id == other_user_id and match_prefix:
  82. # 自己给好友发送消息
  83. str_list = content.split(match_prefix, 1)
  84. if len(str_list) == 2:
  85. content = str_list[1].strip()
  86. img_match_prefix = self.check_prefix(content, conf().get('image_create_prefix'))
  87. if img_match_prefix:
  88. content = content.split(img_match_prefix, 1)[1].strip()
  89. thread_pool.submit(self._do_send_img, content, to_user_id)
  90. else:
  91. thread_pool.submit(self._do_send_text, content, to_user_id)
  92. def handle_group(self, msg):
  93. logger.debug("[WX]receive group msg: " + json.dumps(msg, ensure_ascii=False))
  94. group_name = msg['User'].get('NickName', None)
  95. group_id = msg['User'].get('UserName', None)
  96. create_time = msg['CreateTime'] # 消息时间
  97. if conf().get('hot_reload') == True and int(create_time) < int(time.time()) - 60: #跳过1分钟前的历史消息
  98. logger.debug("[WX]history group message skipped")
  99. return
  100. if not group_name:
  101. return ""
  102. origin_content = msg['Content']
  103. content = msg['Content']
  104. content_list = content.split(' ', 1)
  105. context_special_list = content.split('\u2005', 1)
  106. if len(context_special_list) == 2:
  107. content = context_special_list[1]
  108. elif len(content_list) == 2:
  109. content = content_list[1]
  110. if "」\n- - - - - - - - - - - - - - -" in content:
  111. logger.debug("[WX]reference query skipped")
  112. return ""
  113. config = conf()
  114. match_prefix = (msg['IsAt'] and not config.get("group_at_off", False)) or self.check_prefix(origin_content, config.get('group_chat_prefix')) \
  115. or self.check_contain(origin_content, config.get('group_chat_keyword'))
  116. if ('ALL_GROUP' in config.get('group_name_white_list') or group_name in config.get('group_name_white_list') or self.check_contain(group_name, config.get('group_name_keyword_white_list'))) and match_prefix:
  117. img_match_prefix = self.check_prefix(content, conf().get('image_create_prefix'))
  118. if img_match_prefix:
  119. content = content.split(img_match_prefix, 1)[1].strip()
  120. thread_pool.submit(self._do_send_img, content, group_id)
  121. else:
  122. thread_pool.submit(self._do_send_group, content, msg)
  123. def send(self, msg, receiver):
  124. itchat.send(msg, toUserName=receiver)
  125. logger.info('[WX] sendMsg={}, receiver={}'.format(msg, receiver))
  126. def _do_send_voice(self, query, reply_user_id):
  127. try:
  128. if not query:
  129. return
  130. context = dict()
  131. context['from_user_id'] = reply_user_id
  132. reply_text = super().build_reply_content(query, context)
  133. if reply_text:
  134. replyFile = super().build_text_to_voice(reply_text)
  135. itchat.send_file(replyFile, toUserName=reply_user_id)
  136. logger.info('[WX] sendFile={}, receiver={}'.format(replyFile, reply_user_id))
  137. except Exception as e:
  138. logger.exception(e)
  139. def _do_send_text(self, query, reply_user_id):
  140. try:
  141. if not query:
  142. return
  143. context = dict()
  144. context['session_id'] = reply_user_id
  145. reply_text = super().build_reply_content(query, context)
  146. if reply_text:
  147. self.send(conf().get("single_chat_reply_prefix") + reply_text, reply_user_id)
  148. except Exception as e:
  149. logger.exception(e)
  150. def _do_send_img(self, query, reply_user_id):
  151. try:
  152. if not query:
  153. return
  154. context = dict()
  155. context['type'] = 'IMAGE_CREATE'
  156. img_url = super().build_reply_content(query, context)
  157. if not img_url:
  158. return
  159. # 图片下载
  160. pic_res = requests.get(img_url, stream=True)
  161. image_storage = io.BytesIO()
  162. for block in pic_res.iter_content(1024):
  163. image_storage.write(block)
  164. image_storage.seek(0)
  165. # 图片发送
  166. itchat.send_image(image_storage, reply_user_id)
  167. logger.info('[WX] sendImage, receiver={}'.format(reply_user_id))
  168. except Exception as e:
  169. logger.exception(e)
  170. def _do_send_group(self, query, msg):
  171. if not query:
  172. return
  173. context = dict()
  174. group_name = msg['User']['NickName']
  175. group_id = msg['User']['UserName']
  176. group_chat_in_one_session = conf().get('group_chat_in_one_session', [])
  177. if ('ALL_GROUP' in group_chat_in_one_session or \
  178. group_name in group_chat_in_one_session or \
  179. self.check_contain(group_name, group_chat_in_one_session)):
  180. context['session_id'] = group_id
  181. else:
  182. context['session_id'] = msg['ActualUserName']
  183. reply_text = super().build_reply_content(query, context)
  184. if reply_text:
  185. reply_text = '@' + msg['ActualNickName'] + ' ' + reply_text.strip()
  186. self.send(conf().get("group_chat_reply_prefix", "") + reply_text, group_id)
  187. def check_prefix(self, content, prefix_list):
  188. for prefix in prefix_list:
  189. if content.startswith(prefix):
  190. return prefix
  191. return None
  192. def check_contain(self, content, keyword_list):
  193. if not keyword_list:
  194. return None
  195. for ky in keyword_list:
  196. if content.find(ky) != -1:
  197. return True
  198. return None