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.

722 line
31KB

  1. from flask_restful import Resource, reqparse
  2. from flask import jsonify,request
  3. from bridge.context import ContextType
  4. import requests,json,re
  5. from wechat import gewe_chat
  6. from voice.ali.ali_voice import AliVoice
  7. from common import utils,redis_helper,memory,kafka_helper
  8. from common.log import logger
  9. import openai
  10. import xml.etree.ElementTree as ET
  11. import os
  12. from voice import audio_convert
  13. class MessagesResource(Resource):
  14. def __init__(self):
  15. self.parser = reqparse.RequestParser()
  16. def post(self):
  17. msg = request.get_json()
  18. logger.info(f"收到微信回调消息: {msg}")
  19. type_name =msg.get("TypeName")
  20. app_id = msg.get("Appid")
  21. # token_id = "f828cb3c-1039-489f-b9ae-7494d1778a15"
  22. token_id=get_token_id_by_app_id(app_id)
  23. if token_id=="":
  24. logger.warning('找不到登录信息,不处理')
  25. return jsonify({"message": "收到微信回调消息"})
  26. if type_name=='AddMsg':
  27. wxid = msg.get("Wxid")
  28. msg_data = msg.get("Data")
  29. msg_type = msg_data.get("MsgType",None)
  30. from_wxid = msg_data["FromUserName"]["string"]
  31. to_wxid = msg_data["ToUserName"]["string"]
  32. msg_push_content=msg_data.get("PushContent") #群发
  33. handlers = {
  34. 1: handle_text,
  35. 3: handle_image,
  36. 34: handle_voice,
  37. 49: handle_xml,
  38. 37: handle_add_friend_notice,
  39. 10002: handle_10002_msg
  40. }
  41. # (扫码进群情况)判断受否是群聊,并添加到通信录,
  42. if check_chatroom(from_wxid) or check_chatroom(to_wxid):
  43. logger.info('群信息')
  44. chatroom_id=from_wxid
  45. ret,msg,data=gewe_chat.wxchat.save_contract_list(token_id,app_id,chatroom_id,3)
  46. logger.info(f'保存到通讯录 chatroom_id {chatroom_id} {msg}')
  47. gewe_chat.wxchat.update_groups_info_to_cache(token_id,app_id,wxid,chatroom_id)
  48. handlers[1]=handle_text_group
  49. handlers[3]=handle_image_group
  50. handlers[34]=handle_voice_group
  51. handler = handlers.get(msg_type)
  52. if handler:
  53. return handler(token_id,app_id, wxid,msg_data,from_wxid, to_wxid)
  54. else:
  55. logger.warning(f"微信回调消息类型 {msg_type} 未处理")
  56. elif type_name=='ModContacts':
  57. '''
  58. 好友通过验证及好友资料变更的通知消息
  59. '''
  60. msg_data = msg.get("Data")
  61. handle_mod_contacts(token_id,app_id,msg_data)
  62. elif type_name=="DelContacts":
  63. '''
  64. 删除好友通知/退出群聊
  65. '''
  66. msg_data = msg.get("Data")
  67. username=msg_data["UserName"]["string"]
  68. if check_chatroom(username):
  69. logger.info('退出群聊')
  70. wxid = msg.get("Wxid")
  71. chatroom_id=username
  72. redis_helper.redis_helper.delete_hash_field(f'__AI_OPS_WX__:GROUPS_INFO:{wxid}',chatroom_id)
  73. logger.info(f'清除 chatroom_id{chatroom_id} 数据')
  74. else:
  75. logger.info('删除好友通知')
  76. elif type_name=="Offline":
  77. '''
  78. 已经离线
  79. '''
  80. wxid = msg.get("Wxid")
  81. logger.warning(f'微信ID {wxid}在设备{app_id}已经离线')
  82. k,r=get_login_info_by_app_id(app_id)
  83. print(k)
  84. redis_helper.redis_helper.update_hash_field(k,'status',0)
  85. else:
  86. logger.warning(f"未知消息类型")
  87. return jsonify({"message": "收到微信回调消息"})
  88. def handle_text(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  89. '''
  90. 私聊文本消息
  91. '''
  92. msg_content=msg_data["Content"]["string"]
  93. if wxid == from_wxid: #手动发送消息
  94. logger.info("Active message sending detected")
  95. gewe_chat.wxchat.save_contacts_brief_to_cache(token_id,app_id,wxid,[to_wxid])
  96. callback_to_user=msg_data["ToUserName"]["string"]
  97. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  98. input_message=utils.dialogue_message(from_wxid,to_wxid,input_wx_content_dialogue_message)
  99. kafka_helper.kafka_client.produce_message(input_message)
  100. logger.info("发送对话 %s",input_message)
  101. else:
  102. callback_to_user=msg_data["FromUserName"]["string"]
  103. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
  104. prompt={"role": "user", "content": [{
  105. "type": "text",
  106. "text": msg_content
  107. }]}
  108. messages_to_send=get_messages_from_cache(hash_key, prompt)
  109. # 收到的对话
  110. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  111. input_message=utils.dialogue_message(callback_to_user,wxid,input_wx_content_dialogue_message)
  112. kafka_helper.kafka_client.produce_message(input_message)
  113. logger.info("发送对话 %s",input_message)
  114. cache_data = memory.USER_INTERACTIVE_CACHE.get(wxid)
  115. if cache_data and cache_data.get('interactive') :
  116. o=get_first_char_if_digit(msg_content)
  117. if o is not None:
  118. userSelectOptions=cache_data.get('options')
  119. if o < len(userSelectOptions):
  120. o=o-1
  121. msg_content=userSelectOptions[o].get("value")
  122. messages_to_send=[{"role": "user", "content": msg_content}]
  123. else:
  124. messages_to_send=[{"role": "user", "content": msg_content}]
  125. else:
  126. messages_to_send=[{"role": "user", "content": msg_content}]
  127. res=fast_gpt_api(messages_to_send,f'{wxid}-{callback_to_user}')
  128. reply_content=res["choices"][0]["message"]["content"]
  129. description = ''
  130. userSelectOptions = []
  131. if isinstance(reply_content, list) and any(item.get("type") == "interactive" for item in reply_content):
  132. for item in reply_content:
  133. if item["type"] == "interactive" and item["interactive"]["type"] == "userSelect":
  134. params = item["interactive"]["params"]
  135. description = params.get("description")
  136. userSelectOptions = params.get("userSelectOptions", [])
  137. values_string = "\n".join(option["value"] for option in userSelectOptions)
  138. if description is not None:
  139. memory.USER_INTERACTIVE_CACHE[wxid] = {
  140. "interactive":True,
  141. "options": userSelectOptions,
  142. }
  143. reply_content=description + '------------------------------\n'+values_string
  144. elif isinstance(reply_content, list) and any(item.get("type") == "text" for item in reply_content):
  145. memory.USER_INTERACTIVE_CACHE[wxid] = {
  146. "interactive":False
  147. }
  148. text=''
  149. for item in reply_content:
  150. if item["type"] == "text":
  151. text=item["text"]["content"]
  152. if text=='':
  153. # 去除上次上一轮对话再次请求
  154. cache_messages_str=redis_helper.redis_helper.get_hash_field(hash_key,"data")
  155. cache_messages = json.loads(cache_messages_str) if cache_messages_str else []
  156. if len(cache_messages) >= 3:
  157. cache_messages = cache_messages[:-3]
  158. redis_helper.redis_helper.update_hash_field(hash_key,"data",json.dumps(cache_messages,ensure_ascii=False))
  159. messages_to_send=get_messages_from_cache(hash_key, prompt)
  160. res=fast_gpt_api(messages_to_send,f'{wxid}-{callback_to_user}')
  161. reply_content=res["choices"][0]["message"]["content"]
  162. else:
  163. reply_content=text
  164. else:
  165. memory.USER_INTERACTIVE_CACHE[wxid] = {
  166. "interactive":False
  167. }
  168. reply_content=res["choices"][0]["message"]["content"]
  169. gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,reply_content)
  170. get_messages_from_cache(hash_key, {"role": "assistant", "content": reply_content})
  171. # 回复的对话
  172. input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
  173. input_message=utils.dialogue_message(wxid,callback_to_user,input_wx_content_dialogue_message,True)
  174. kafka_helper.kafka_client.produce_message(input_message)
  175. logger.info("发送对话 %s",input_message)
  176. def handle_text_group(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  177. '''
  178. 群聊文本消息
  179. '''
  180. msg_content=msg_data["Content"]["string"]
  181. msg_push_content=msg_data.get("PushContent")
  182. k,login_info=get_login_info_by_app_id(app_id)
  183. nickname=login_info.get("nickName")
  184. if wxid == from_wxid: #手动发送消息
  185. logger.info("Active message sending detected")
  186. gewe_chat.wxchat.save_contacts_brief_to_cache(token_id,app_id,wxid,[to_wxid])
  187. callback_to_user=msg_data["ToUserName"]["string"]
  188. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  189. input_message=utils.dialogue_message(from_wxid,to_wxid,input_wx_content_dialogue_message)
  190. kafka_helper.kafka_client.produce_message(input_message)
  191. logger.info("发送对话 %s",input_message)
  192. else:
  193. c = gewe_chat.wxchat.get_wxchat_config(wxid)
  194. chatroom_id_white_list = c.get("chatroomIdWhiteList", [])
  195. if not chatroom_id_white_list:
  196. logger.info('白名单为空或未定义,不处理')
  197. return
  198. if from_wxid not in chatroom_id_white_list:
  199. logger.info('群ID不在白名单中,不处理')
  200. return
  201. if '在群聊中@了你' in msg_push_content or '@'+nickname in msg_push_content:
  202. callback_to_user=msg_data["FromUserName"]["string"]
  203. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
  204. prompt={"role": "user", "content": [{
  205. "type": "text",
  206. "text": msg_content
  207. }]}
  208. messages_to_send=get_messages_from_cache(hash_key, prompt)
  209. # 收到的对话
  210. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  211. input_message=utils.dialogue_message(callback_to_user,wxid,input_wx_content_dialogue_message)
  212. kafka_helper.kafka_client.produce_message(input_message)
  213. logger.info("发送对话 %s",input_message)
  214. cache_data = memory.USER_INTERACTIVE_CACHE.get(wxid)
  215. if cache_data and cache_data.get('interactive') :
  216. o=get_first_char_if_digit(msg_content)
  217. if o is not None:
  218. userSelectOptions=cache_data.get('options')
  219. if o < len(userSelectOptions):
  220. o=o-1
  221. msg_content=userSelectOptions[o].get("value")
  222. messages_to_send=[{"role": "user", "content": msg_content}]
  223. else:
  224. messages_to_send=[{"role": "user", "content": msg_content}]
  225. else:
  226. messages_to_send=[{"role": "user", "content": msg_content}]
  227. res=fast_gpt_api(messages_to_send,f'{wxid}-{callback_to_user}')
  228. reply_content=res["choices"][0]["message"]["content"]
  229. description = ''
  230. userSelectOptions = []
  231. if isinstance(reply_content, list) and any(item.get("type") == "interactive" for item in reply_content):
  232. for item in reply_content:
  233. if item["type"] == "interactive" and item["interactive"]["type"] == "userSelect":
  234. params = item["interactive"]["params"]
  235. description = params.get("description")
  236. userSelectOptions = params.get("userSelectOptions", [])
  237. values_string = "\n".join(option["value"] for option in userSelectOptions)
  238. if description is not None:
  239. memory.USER_INTERACTIVE_CACHE[wxid] = {
  240. "interactive":True,
  241. "options": userSelectOptions,
  242. }
  243. reply_content=description + '------------------------------\n'+values_string
  244. elif isinstance(reply_content, list) and any(item.get("type") == "text" for item in reply_content):
  245. memory.USER_INTERACTIVE_CACHE[wxid] = {
  246. "interactive":False
  247. }
  248. text=''
  249. for item in reply_content:
  250. if item["type"] == "text":
  251. text=item["text"]["content"]
  252. if text=='':
  253. # 去除上次上一轮对话再次请求
  254. cache_messages_str=redis_helper.redis_helper.get_hash_field(hash_key,"data")
  255. cache_messages = json.loads(cache_messages_str) if cache_messages_str else []
  256. if len(cache_messages) >= 3:
  257. cache_messages = cache_messages[:-3]
  258. redis_helper.redis_helper.update_hash_field(hash_key,"data",json.dumps(cache_messages,ensure_ascii=False))
  259. messages_to_send=get_messages_from_cache(hash_key, prompt)
  260. res=fast_gpt_api(messages_to_send,f'{wxid}-{callback_to_user}')
  261. reply_content=res["choices"][0]["message"]["content"]
  262. else:
  263. reply_content=text
  264. else:
  265. memory.USER_INTERACTIVE_CACHE[wxid] = {
  266. "interactive":False
  267. }
  268. reply_content=res["choices"][0]["message"]["content"]
  269. reply_content='@'+extract_nickname(msg_push_content) + reply_content
  270. gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,reply_content)
  271. get_messages_from_cache(hash_key, {"role": "assistant", "content": reply_content})
  272. # 回复的对话
  273. input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
  274. input_message=utils.dialogue_message(wxid,callback_to_user,input_wx_content_dialogue_message,True)
  275. kafka_helper.kafka_client.produce_message(input_message)
  276. logger.info("发送对话 %s",input_message)
  277. else:
  278. logger.info('群聊公开消息')
  279. return
  280. def handle_image(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  281. '''
  282. 图片消息
  283. '''
  284. msg_content=msg_data["Content"]["string"]
  285. callback_to_user=from_wxid
  286. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
  287. wx_img_url=gewe_chat.wxchat.download_image_msg(token_id,app_id,msg_content)
  288. oss_access_key_id="LTAI5tRTG6pLhTpKACJYoPR5"
  289. oss_access_key_secret="E7dMzeeMxq4VQvLg7Tq7uKf3XWpYfN"
  290. oss_endpoint="http://oss-cn-shanghai.aliyuncs.com"
  291. oss_bucket_name="cow-agent"
  292. oss_prefix="cow"
  293. img_url=utils.upload_oss(oss_access_key_id, oss_access_key_secret, oss_endpoint, oss_bucket_name, wx_img_url, oss_prefix)
  294. prompt={
  295. "role": "user",
  296. "content": [{
  297. "type": "image_url",
  298. "image_url": {"url": img_url}
  299. }]
  300. }
  301. get_messages_from_cache(hash_key, prompt)
  302. gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,'已经上传了图片,有什么可以为您服务')
  303. logger.info(f"上传图片 URL: {img_url}")
  304. wx_content_dialogue_message=[{"type": "image_url", "image_url": {"url": img_url}}]
  305. input_message=utils.dialogue_message(wxid,callback_to_user,wx_content_dialogue_message)
  306. kafka_helper.kafka_client.produce_message(input_message)
  307. logger.info("发送对话 %s",input_message)
  308. def handle_image_group(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  309. logger.info('群聊图片消息')
  310. def handle_voice(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  311. '''
  312. 语音消息
  313. '''
  314. callback_to_user=from_wxid
  315. msg_content=msg_data["Content"]["string"]
  316. msg_id=msg_data["MsgId"]
  317. file_url=gewe_chat.wxchat.download_audio_msg(token_id,app_id,msg_id,msg_content)
  318. react_silk_path=utils.save_to_local_from_url(file_url)
  319. react_wav_path = os.path.splitext(react_silk_path)[0] + ".wav"
  320. audio_convert.any_to_wav(react_silk_path,react_wav_path)
  321. react_voice_text=AliVoice().voiceToText(react_wav_path)
  322. os.remove(react_silk_path)
  323. os.remove(react_wav_path)
  324. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
  325. messages=get_messages_from_cache(hash_key, {"role": "user", "content": react_voice_text})
  326. ai_res=fast_gpt_api(messages,f'{wxid}-{callback_to_user}')
  327. ai_res_content=ai_res["choices"][0]["message"]["content"]
  328. contains_url=contains_url(ai_res_content)
  329. if not contains_url:
  330. voice_during,voice_url=utils.wx_voice(ai_res_content)
  331. ret,ret_msg,res=gewe_chat.wxchat.post_voice(token_id,app_id,callback_to_user,voice_url,voice_during)
  332. if ret==200:
  333. logger.info((f'{wxid} 向 {callback_to_user} 发送语音文本【{ai_res_content}】{ret_msg}'))
  334. else:
  335. logger.warning((f'{wxid} 向 {callback_to_user} 发送语音文本【{ai_res_content}】{ret_msg}'))
  336. ret,ret_msg,res=gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,ai_res_content)
  337. logger.info((f'{wxid} 向 {callback_to_user} 发送文本【{ai_res_content}】{ret_msg}'))
  338. else:
  339. logger.info(f"回复内容包含网址,不发送语音,回复文字内容:{ai_res_content}")
  340. ret,ret_msg,res=gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,ai_res_content)
  341. get_messages_from_cache(hash_key, {"role": "assistant", "content": ai_res_content})
  342. # 构造对话消息并发送到 Kafka
  343. input_wx_content_dialogue_message = [{"type": "text", "text": ai_res_content}]
  344. input_message = utils.dialogue_message(wxid, callback_to_user, input_wx_content_dialogue_message,True)
  345. kafka_helper.kafka_client.produce_message(input_message)
  346. logger.info("发送对话 %s", input_message)
  347. def handle_voice_group(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  348. logger.info('语音消息')
  349. def handle_xml(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  350. '''
  351. 处理xml
  352. '''
  353. msg_content_xml=msg_data["Content"]["string"]
  354. root = ET.fromstring(msg_content_xml)
  355. type_value = root.find(".//appmsg/type").text
  356. handlers = {
  357. 57: handle_xml_reference,
  358. }
  359. handler = handlers.get(type_value)
  360. if handler:
  361. return handler(token_id,app_id, wxid,msg_data,from_wxid, to_wxid)
  362. elif "邀请你加入了群聊" in msg_content_xml: # 邀请加入群聊
  363. logger.warning(f"xml消息 {type_value} 邀请你加入了群聊.todo")
  364. else:
  365. print(f"xml消息 {type_value} 未解析")
  366. def handle_xml_reference(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  367. '''
  368. 判断此类消息的逻辑:$.Data.MsgType=49 并且 解析$.Data.Content.string中的xml msg.appmsg.type=57
  369. '''
  370. callback_to_user=from_wxid
  371. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
  372. msg_content= msg_data["PushContent"]
  373. prompt={"role": "user", "content": [{
  374. "type": "text",
  375. "text": msg_content
  376. }]}
  377. # 收到的对话
  378. messages_to_send=get_messages_from_cache(hash_key, prompt)
  379. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  380. input_message=utils.dialogue_message(callback_to_user,wxid,input_wx_content_dialogue_message)
  381. kafka_helper.kafka_client.produce_message(input_message)
  382. logger.info("发送对话 %s",input_message)
  383. # 回复的对话
  384. res=fast_gpt_api(messages_to_send,f'{wxid}-{callback_to_user}')
  385. reply_content=res["choices"][0]["message"]["content"]
  386. input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
  387. input_message=utils.dialogue_message(wxid,callback_to_user,input_wx_content_dialogue_message,True)
  388. kafka_helper.kafka_client.produce_message(input_message)
  389. logger.info("发送对话 %s",input_message)
  390. get_messages_from_cache(hash_key, {"role": "assistant", "content": reply_content})
  391. gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,reply_content)
  392. def handle_add_friend_notice(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  393. '''
  394. 好友添加请求通知
  395. '''
  396. logger.info('好友添加请求通知')
  397. msg_content_xml=msg_data["Content"]["string"]
  398. root = ET.fromstring(msg_content_xml)
  399. msg_content = root.attrib.get('content', None)
  400. v3= root.attrib.get('encryptusername', None)
  401. v4= root.attrib.get('ticket', None)
  402. scene=root.attrib.get('scene', None)
  403. to_contact_wxid=root.attrib.get('fromusername', None)
  404. wxid=msg_data["ToUserName"]["string"]
  405. # 自动同意好友
  406. # print(v3)
  407. # print(v4)
  408. # print(scene)
  409. # print(msg_content)
  410. # 操作类型,2添加好友 3同意好友 4拒绝好友
  411. #option=2
  412. option=3
  413. reply_add_contact_contact="亲,我是你的美丽顾问"
  414. ret,ret_msg=gewe_chat.wxchat.add_contacts(token_id,app_id,scene,option,v3,v4,reply_add_contact_contact)
  415. if ret==200:
  416. logger.info('自动添加好友成功')
  417. # 好友发送的文字
  418. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{to_contact_wxid}'
  419. prompt={"role": "user", "content": [{"type": "text","text": msg_content}]}
  420. messages_to_send=get_messages_from_cache(hash_key, prompt)
  421. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  422. input_message=utils.dialogue_message(to_contact_wxid,wxid,input_wx_content_dialogue_message)
  423. kafka_helper.kafka_client.produce_message(input_message)
  424. logger.info("发送对话 %s",input_message)
  425. callback_to_user=to_contact_wxid
  426. res=fast_gpt_api(messages_to_send,f'{wxid}-{callback_to_user}')
  427. reply_content=res["choices"][0]["message"]["content"]
  428. #保存好友信息
  429. gewe_chat.wxchat.save_contacts_brief_to_cache(token_id,app_id, wxid,[to_contact_wxid])
  430. # 保存到缓存
  431. get_messages_from_cache(hash_key, {"role": "assistant", "content": reply_content})
  432. # 发送信息
  433. gewe_chat.wxchat.post_text(token_id,app_id, to_contact_wxid,reply_content)
  434. # 发送到kafka
  435. input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
  436. input_message=utils.dialogue_message(wxid,to_contact_wxid,input_wx_content_dialogue_message,True)
  437. kafka_helper.kafka_client.produce_message(input_message)
  438. logger.info("发送对话 %s",input_message)
  439. else:
  440. logger.warning("添加好友失败")
  441. def handle_10002_msg(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  442. '''
  443. 群聊邀请
  444. 撤回消息
  445. 拍一拍消息
  446. 地理位置
  447. 踢出群聊通知
  448. 解散群聊通知
  449. 发布群公告
  450. '''
  451. msg_content_xml=msg_data["Content"]["string"]
  452. # 群聊邀请
  453. if '邀请你加入了群聊' in msg_content_xml and check_chatroom(msg_data["FromUserName"]["string"]):
  454. chatroom_id=msg_data["FromUserName"]["string"]
  455. ret,msg,data=gewe_chat.wxchat.save_contract_list(token_id,app_id,chatroom_id,3)
  456. logger.info(f'群聊邀请,保存到通讯录 chatroom_id {chatroom_id} {msg}')
  457. gewe_chat.wxchat.update_groups_info_to_cache(token_id,app_id,wxid,chatroom_id)
  458. if '移出了群聊' in msg_content_xml and 'sysmsgtemplate' in msg_content_xml :
  459. chatroom_id=msg_data["FromUserName"]["string"]
  460. ret,msg,data=gewe_chat.wxchat.save_contract_list(token_id,app_id,chatroom_id,2)
  461. logger.info(f'踢出群聊,移除从通讯录 chatroom_id {chatroom_id} {msg}')
  462. redis_helper.redis_helper.delete_hash_field(f'__AI_OPS_WX__:GROUPS_INFO:{wxid}',chatroom_id)
  463. logger.info(f'清除 chatroom_id{chatroom_id} 数据')
  464. if '已解散该群聊' in msg_content_xml and 'sysmsgtemplate' in msg_content_xml :
  465. chatroom_id=msg_data["FromUserName"]["string"]
  466. ret,msg,data=gewe_chat.wxchat.save_contract_list(token_id,app_id,chatroom_id,2)
  467. logger.info(f'解散群聊,移除从通讯录 chatroom_id {chatroom_id} {msg}')
  468. redis_helper.redis_helper.delete_hash_field(f'__AI_OPS_WX__:GROUPS_INFO:{wxid}',chatroom_id)
  469. logger.info(f'清除 chatroom_id{chatroom_id} 数据')
  470. print('撤回消息,拍一拍消息,地理位置')
  471. def handle_mod_contacts(token_id,app_id,msg_data):
  472. '''
  473. 好友通过验证及好友资料变更的通知消息
  474. '''
  475. logger.info('好友通过验证及好友资料变更的通知消息')
  476. if not check_chatroom(msg_data["UserName"]["string"]):
  477. contact_wxid = msg_data["UserName"]["string"]
  478. keys=redis_helper.redis_helper.client.keys('__AI_OPS_WX__:LOGININFO:*')
  479. wxid=""
  480. for k in keys:
  481. key=k.decode('utf-8')
  482. hash_key=f"__AI_OPS_WX__:LOGININFO:{key}"
  483. cache_app_id=redis_helper.redis_helper.get_hash_field(hash_key,"appId")
  484. if app_id==cache_app_id:
  485. wxid=redis_helper.redis_helper.get_hash_field(hash_key,"wxid")
  486. break
  487. if wxid!="":
  488. hash_key=f"__AI_OPS_WX__:CONTACTS_BRIEF:{wxid}"
  489. cache_str=redis_helper.redis_helper.get_hash_field(hash_key,"data")
  490. cache = json.loads(cache_str) if cache_str else []
  491. for c in cache:
  492. if c["userName"]==contact_wxid:
  493. c["nickName"] = msg_data["NickName"]
  494. c["snsBgImg"] = msg_data["SnsBgimgId"]
  495. c["smallHeadImgUrl"] = msg_data["SmallHeadImgUrl"]
  496. c["signature"]= msg_data["Signature"]
  497. # 更新缓存,如果有修改过的话
  498. if cache:
  499. updated_cache_str = json.dumps(cache)
  500. redis_helper.redis_helper.set_hash_field(hash_key, "data", updated_cache_str)
  501. else:
  502. logger.info('群聊好友通过验证及好友资料变更的通知消息')
  503. def get_messages_from_cache(hash_key,object:object)->object:
  504. messages=redis_helper.redis_helper.get_hash(hash_key)
  505. wxid=hash_key.split(':')[-1]
  506. if not messages:
  507. messages=[{"role": "system", "content": ""}]
  508. messages.append(object)
  509. redis_helper.redis_helper.set_hash(hash_key,{"data":json.dumps(messages,ensure_ascii=False)},3600)
  510. else:
  511. messages_str=redis_helper.redis_helper.get_hash_field(hash_key,"data")
  512. messages = json.loads(messages_str) if messages_str else []
  513. #判断是否含有图片
  514. last_message = messages[-1]
  515. content = last_message.get("content", [])
  516. if isinstance(content, list) and content:
  517. last_content_type = content[-1].get("type")
  518. if last_content_type == 'image_url':
  519. content.append(object['content'][0])
  520. messages[-1]['content']=content
  521. else:
  522. messages.append(object)
  523. else:
  524. messages.append(object)
  525. redis_helper.redis_helper.set_hash(hash_key,{"data":json.dumps(messages,ensure_ascii=False)},3600)
  526. return messages
  527. def fast_gpt_api(messages:list,session_id:str):
  528. #api_key="sk-tdi7u0zuLsR0JpPMGBeFZxymOpL0zoFVafX8EEEvEakIDAGQ22NyQ6w"
  529. api_key="sk-uJDBdKmJVb2cmfldGOvlIY6Qx0AzqWMPD3lS1IzgQYzHNOXv9SKNI"
  530. api_url = "http://106.15.182.218:3000/api/v1/chat/completions"
  531. headers = {
  532. "Content-Type": "application/json",
  533. "Authorization": f"Bearer {api_key}"
  534. }
  535. data={
  536. "model": "",
  537. "messages":messages,
  538. "chatId": session_id,
  539. "detail": True
  540. }
  541. print(json.dumps(data,ensure_ascii=False))
  542. logger.info("[CHATGPT] 请求={}".format(json.dumps(data, ensure_ascii=False)))
  543. response = requests.post(url=api_url, headers=headers, data=json.dumps(data), timeout=600)
  544. response.raise_for_status()
  545. response_data = response.json()
  546. logger.info("[CHATGPT] 响应={}".format(json.dumps(response_data, separators=(',', ':'),ensure_ascii=False)))
  547. print(response_data)
  548. return response_data
  549. def get_token_id_by_app_id(app_id: str) -> str:
  550. # 使用 SCAN 避免一次性返回所有的匹配键,逐步扫描
  551. cursor = 0
  552. while True:
  553. cursor, login_keys = redis_helper.redis_helper.client.scan(cursor, match='__AI_OPS_WX__:LOGININFO:*')
  554. # 批量获取所有键的 hash 数据
  555. for k in login_keys:
  556. r = redis_helper.redis_helper.get_hash(k)
  557. if r.get("appId") == app_id:
  558. return r.get("tokenId", "")
  559. # 如果游标为 0,则表示扫描完成
  560. if cursor == 0:
  561. break
  562. return ""
  563. def get_login_info_by_app_id(app_id: str) ->dict:
  564. # 使用 SCAN 避免一次性返回所有的匹配键,逐步扫描
  565. cursor = 0
  566. while True:
  567. cursor, login_keys = redis_helper.redis_helper.client.scan(cursor, match='__AI_OPS_WX__:LOGININFO:*')
  568. # 批量获取所有键的 hash 数据
  569. for k in login_keys:
  570. r = redis_helper.redis_helper.get_hash(k)
  571. if r.get("appId") == app_id:
  572. return k,r
  573. # 如果游标为 0,则表示扫描完成
  574. if cursor == 0:
  575. break
  576. return ""
  577. def contains_url(text):
  578. # 定义检测网址的正则表达式
  579. url_pattern = re.compile(
  580. r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
  581. )
  582. # 检查字符串是否包含网址
  583. return bool(url_pattern.search(text))
  584. def get_first_char_if_digit(s):
  585. if s and s[0].isdigit(): # 判断字符串是否非空且首字符为数字
  586. return int(s[0]) # 返回数字形式
  587. return None # 如果不是数字则返回 None
  588. def remove_at_mention_regex(text):
  589. # 使用正则表达式去掉“在群聊中@了你”
  590. return re.sub(r"在群聊中@了你", "", text)
  591. def extract_nickname(text)->str:
  592. if "在群聊中@了你" in text:
  593. # 如果包含 "在群聊中@了你",提取其前面的名字
  594. match = re.search(r"^(.*?)在群聊中@了你", text)
  595. if match:
  596. return match.group(1).strip()
  597. elif ": @" in text:
  598. # 如果包含 ": @",提取其前面的名字
  599. return text.split(": @")[0].strip()
  600. return ''
  601. def check_chatroom(userName):
  602. pattern = r'^\d+@chatroom$'
  603. if re.match(pattern, userName):
  604. return True
  605. return False