No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

906 líneas
42KB

  1. from voice.ali.ali_voice import AliVoice
  2. from common.log import logger
  3. import xml.etree.ElementTree as ET
  4. import os,json,asyncio,aiohttp
  5. from voice import audio_convert
  6. from fastapi import APIRouter,Request
  7. from pydantic import BaseModel
  8. from fastapi import APIRouter, Depends
  9. from typing import Dict, Any
  10. from model.models import AgentConfig,OperationType
  11. from common.utils import *
  12. from common.memory import *
  13. timeout_duration = 2.0
  14. messages_router = APIRouter()
  15. WX_BACKLIST=['fmessage', 'medianote','weixin','weixingongzhong','tmessage']
  16. @messages_router.post("/messages",response_model=None)
  17. async def get_chatroominfo(request: Request, body: Dict[str, Any]):
  18. msg = body
  19. logger.info(f"收到微信回调消息: {json.dumps(msg, separators=(',', ':'),ensure_ascii=False)}")
  20. type_name =msg.get("TypeName")
  21. app_id = msg.get("Appid")
  22. k, loginfo = await request.app.state.gewe_service.get_login_info_by_app_id_async(app_id)
  23. if not k:
  24. logger.warning('找不到登录信息,不处理')
  25. return {"message": f"收到微信回调消息: {type_name}"}
  26. token_id=loginfo.get('tokenId','')
  27. wxid = msg.get("Wxid",'')
  28. if type_name == 'AddMsg':
  29. await handle_self_cmd_async(request,wxid,msg)
  30. msg_data = msg.get("Data")
  31. from_wxid = msg_data["FromUserName"]["string"]
  32. config=await request.app.state.redis_service.get_hash(f"__AI_OPS_WX__:WXCHAT_CONFIG")
  33. wxids=config.keys()
  34. WX_BACKLIST.extend(wxids)
  35. if from_wxid in WX_BACKLIST:
  36. logger.warning(f'微信ID {wxid} 在黑名单,不处理')
  37. return {"message": "收到微信回调消息"}
  38. await handle_messages_async(request,token_id,msg)
  39. return {"message": "收到微信回调消息"}
  40. async def handle_self_cmd_async(request: Request,wxid,msg):
  41. '''
  42. 个人微信命令处理
  43. 如果是个人微信的指令,wxid == from_wxid
  44. commands = {
  45. '启用托管': True,
  46. '关闭托管': False
  47. }
  48. '''
  49. msg_data=msg.get("Data")
  50. from_wxid=msg_data["FromUserName"]["string"]
  51. msg_content=msg_data["Content"]["string"]
  52. if wxid == from_wxid:
  53. commands = {
  54. '启用托管': True,
  55. '关闭托管': False
  56. }
  57. if msg_content in commands:
  58. c_dict = await request.app.state.gewe_service.get_wxchat_config_from_cache_async(wxid)
  59. if c_dict:
  60. agent_config=AgentConfig.model_validate(c_dict)
  61. agent_config.agentEnabled=commands[msg_content]
  62. await request.app.state.gewe_service.wxchat.save_wxchat_config_async(wxid, agent_config.model_dump())
  63. logger.info(f'{wxid} {"启动" if commands[msg_content] else "关闭"}托管')
  64. async def handle_messages_async(request: Request,token_id,msg):
  65. #msg_data=msg.get("Data")
  66. type_name =msg.get("TypeName")
  67. # app_id = msg.get("Appid")
  68. # from_wxid=msg_data["FromUserName"]["string"]
  69. # msg_content=msg_data["Content"]["string"]
  70. wxid = msg.get("Wxid",'')
  71. match type_name:
  72. case 'AddMsg':
  73. await handle_add_messages_async(request,token_id,msg,wxid)
  74. case 'ModContacts':
  75. await handle_mod_contacts_async(request,token_id,msg,wxid)
  76. case 'DelContacts':
  77. await handle_del_contacts_async(request,token_id,msg,wxid)
  78. case 'Offline':
  79. await handle_offline_async(request,token_id,msg,wxid)
  80. case _:
  81. logger.warning(f'未知消息类型:{type_name}')
  82. async def gpt_client_async(request,messages: list, wixd: str, friend_wxid: str):
  83. c = await request.app.state.gewe_service.get_wxchat_config_from_cache_async(wixd)
  84. api_key = c.get('agentTokenId', "sk-jr69ONIehfGKe9JFphuNk4DU5Y5wooHKHhQv7oSnFzVbwCnW65fXO9kvH")
  85. print(f'流程key:{api_key}\n')
  86. #api_key="sk-jr69ONIehfGKe9JFphuNk4DU5Y5wooHKHhQv7oSnFzVbwCnW65fXO9kvH" #测试
  87. #api_key="sk-uJDBdKmJVb2cmfldGOvlIY6Qx0AzqWMPD3lS1IzgQYzHNOXv9SKNI" #开发2
  88. api_url = "http://106.15.182.218:3000/api/v1/chat/completions"
  89. headers = {
  90. "Content-Type": "application/json",
  91. "Authorization": f"Bearer {api_key}"
  92. }
  93. session_id = f'{wixd}-{friend_wxid}'
  94. data = {
  95. "model": "",
  96. "messages": messages,
  97. "chatId": session_id,
  98. "detail": True
  99. }
  100. logger.info("[CHATGPT] 请求={}".format(json.dumps(data, ensure_ascii=False)))
  101. async with aiohttp.ClientSession() as session:
  102. try:
  103. async with session.post(url=api_url, headers=headers, data=json.dumps(data), timeout=600) as response:
  104. response.raise_for_status()
  105. response_data = await response.json()
  106. logger.info("[CHATGPT] 响应={}".format(json.dumps(response_data, separators=(',', ':'), ensure_ascii=False)))
  107. return response_data
  108. except aiohttp.ClientError as e:
  109. logger.error(f"请求失败: {e}")
  110. raise
  111. async def handle_add_messages_async(request: Request,token_id,msg,wxid):
  112. wx_config =await request.app.state.gewe_service.get_wxchat_config_from_cache_async(wxid)
  113. if not bool(wx_config.get("agentEnabled",False)):
  114. logger.info(f'微信ID {wxid} 未托管,不处理')
  115. return
  116. app_id = msg.get("Appid")
  117. msg_data = msg.get("Data")
  118. msg_type = msg_data.get("MsgType",None)
  119. from_wxid = msg_data["FromUserName"]["string"]
  120. to_wxid = msg_data["ToUserName"]["string"]
  121. msg_push_content=msg_data.get("PushContent")
  122. handlers = {
  123. 1: handle_text_async,
  124. 3: handle_image_async,
  125. 34: handle_voice_async,
  126. 42: handle_name_card_async,
  127. 49: handle_xml_async,
  128. 37: handle_add_friend_notice_async,
  129. 10002: handle_10002_msg
  130. }
  131. # (扫码进群情况)判断受否是群聊,并添加到通信录
  132. if check_chatroom(from_wxid) or check_chatroom(to_wxid):
  133. logger.info('群信息')
  134. chatroom_id=from_wxid
  135. ret,msg,data=await request.app.state.gewe_service.save_contract_list_async(token_id,app_id,chatroom_id,3)
  136. logger.info(f'保存到通讯录 chatroom_id {chatroom_id} {msg}')
  137. await request.app.state.gewe_service.update_group_info_to_cache_async(token_id,app_id,wxid,chatroom_id)
  138. await request.app.state.gewe_service.update_group_members_to_cache_async(token_id,app_id,wxid,chatroom_id)
  139. handlers[1]=handle_text_group_async
  140. handlers[3]=handle_image_group_async
  141. handlers[34]=handle_voice_group_async
  142. handler = handlers.get(msg_type)
  143. if handler:
  144. return await handler(request,token_id,app_id, wxid,msg_data,from_wxid, to_wxid)
  145. else:
  146. logger.warning(f"微信回调消息类型 {msg_type} 未处理")
  147. async def handle_mod_contacts_async(request: Request,token_id,msg,wxid):
  148. '''
  149. 好友通过验证及好友资料变更的通知消息
  150. '''
  151. wxid = msg.get("Wxid")
  152. msg_data = msg.get("Data")
  153. app_id = msg.get("Appid")
  154. #
  155. #handle_mod_contacts(token_id,app_id,wxid,msg_data)
  156. #
  157. loop = asyncio.get_event_loop()
  158. future = asyncio.run_coroutine_threadsafe(
  159. handle_mod_contacts_worker_async(request,token_id,app_id,wxid,msg_data),
  160. loop
  161. )
  162. contact_wxid =msg_data.get("UserName",{}).get("string","") #msg_data["UserName"]["string"]
  163. nickname= msg_data.get("NickName",{}).get("string","")#msg_data["NickName"]["string"]
  164. city=msg_data.get("City","")
  165. signature=msg_data.get("Signature","")
  166. province=msg_data.get("Province","")
  167. bigHeadImgUrl=msg_data.get("SnsUserInfo",{}).get("SnsBgimgId","") #msg_data["SnsUserInfo"]["SnsBgimgId"]
  168. country=msg_data.get("Country","")
  169. sex=msg_data.get("Sex",None)
  170. pyInitial= msg_data.get("PyInitial",{}).get("string","")#msg_data["PyInitial"]["string"]
  171. quanPin=msg_data.get("QuanPin",{}).get("string","") #msg_data["QuanPin"]["string"]
  172. remark=msg_data.get("Remark",{}).get("string","")
  173. remarkPyInitial=msg_data.get("RemarkPyInitial",{}).get("string","")
  174. remarkQuanPin=msg_data.get("RemarkQuanPin",{}).get("string","")
  175. smallHeadImgUrl=msg_data.get("smallHeadImgUrl","")
  176. # data=gewe_chat.wxchat.get_brief_info(token_id,app_id,[contact_wxid])
  177. # contact=data[0]
  178. # alias=contact.get("alias")
  179. #推动到kafka
  180. contact_data = {
  181. "alias": None,
  182. "bigHeadImgUrl": bigHeadImgUrl,
  183. "cardImgUrl": None,
  184. "city": city,
  185. "country": country,
  186. "description": None,
  187. "labelList": None,
  188. "nickName": nickname,
  189. "phoneNumList": None,
  190. "province": province,
  191. "pyInitial": pyInitial,
  192. "quanPin": quanPin,
  193. "remark": remark,
  194. "remarkPyInitial": remarkPyInitial,
  195. "remarkQuanPin": remarkQuanPin,
  196. "sex": sex,
  197. "signature": signature,
  198. "smallHeadImgUrl": smallHeadImgUrl,
  199. "snsBgImg": None,
  200. "userName": contact_wxid
  201. }
  202. input_message=wx_mod_contact_message(wxid,contact_data)
  203. await request.app.state.kafka_service.send_message_async(input_message)
  204. async def handle_del_contacts_async(request: Request,token_id,msg,wxid):
  205. '''
  206. 删除好友通知/退出群聊
  207. '''
  208. msg_data = msg.get("Data")
  209. username=msg_data["UserName"]["string"]
  210. if check_chatroom(username):
  211. logger.info('退出群聊')
  212. wxid = msg.get("Wxid")
  213. chatroom_id=username
  214. await request.app.state.redis_service.delete_hash_field(f'__AI_OPS_WX__:GROUPS_INFO:{wxid}',chatroom_id)
  215. logger.info(f'清除 chatroom_id{chatroom_id} 数据')
  216. else:
  217. logger.info('删除好友通知')
  218. # 推送到kafka
  219. input_message=wx_del_contact_message(wxid,username)
  220. await request.app.state.kafka_service.send_message_async(input_message)
  221. async def handle_offline_async(request: Request,token_id,msg,wxid):
  222. '''
  223. 已经离线
  224. '''
  225. wxid = msg.get("Wxid")
  226. app_id = msg.get("Appid")
  227. logger.warning(f'微信ID {wxid}在设备{app_id}已经离线')
  228. k,r=await request.app.state.gewe_service.get_login_info_by_app_id_async(app_id)
  229. print(k)
  230. await request.app.state.redis_service.update_hash_field(k,'status',0)
  231. await request.app.state.redis_service.update_hash_field(k,'modify_at',int(time.time()))
  232. # 推送到kafka
  233. input_message=wx_offline_message(app_id,wxid)
  234. await request.app.state.kafka_service.send_message_async(input_message)
  235. async def handle_mod_contacts_worker_async(request:Request,token_id,app_id,wxid,msg_data):
  236. '''
  237. 好友通过验证及好友资料变更的通知消息
  238. '''
  239. logger.info('好友通过验证及好友资料变更的通知消息')
  240. if not check_chatroom(msg_data["UserName"]["string"]):
  241. contact_wxid = msg_data["UserName"]["string"]
  242. # 更新好友信息
  243. # 检查好友关系,不是好友则删除
  244. # ret,msg,check_relation=gewe_chat.wxchat.check_relation(token_id, app_id,[contact_wxid])
  245. # first_item = check_relation[0]
  246. # check_relation_status=first_item.get('relation')
  247. # logger.info(f'{wxid} 好友 {contact_wxid} 关系检查:{check_relation_status}')
  248. # if check_relation_status != 0:
  249. # gewe_chat.wxchat.delete_contacts_brief_from_cache(wxid, [contact_wxid])
  250. # logger.info(f'好友关系异常:{check_relation_status},删除好友 {contact_wxid} 信息')
  251. # else:
  252. # gewe_chat.wxchat.save_contacts_brief_to_cache(token_id, app_id, wxid, [contact_wxid])
  253. ret,msg,contacts_list = await request.app.state.gewe_service.fetch_contacts_list_async(token_id, app_id)
  254. # friend_wxids = contacts_list['friends'][3:] # 可以调整截取范围
  255. # print(friend_wxids)
  256. #friend_wxids.remove('fmessage')
  257. #friend_wxids.remove('weixin')
  258. friend_wxids = [c for c in contacts_list['friends'] if c not in ['fmessage', 'medianote','weixin','weixingongzhong','tmessage']] # 可以调整截取范围
  259. print(f'{wxid}的好友数量 {len(friend_wxids)}')
  260. await request.app.state.gewe_service.save_contacts_brief_to_cache_async(token_id, app_id, wxid, friend_wxids)
  261. else:
  262. logger.info('群聊好友通过验证及好友资料变更的通知消息')
  263. async def handle_text_async(request: Request,token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  264. '''
  265. 私聊文本消息
  266. '''
  267. msg_content=msg_data["Content"]["string"]
  268. if wxid == from_wxid: #手动发送消息
  269. logger.info("Active message sending detected")
  270. await request.app.state.gewe_service.save_contacts_brief_to_cache_async(token_id,app_id,wxid,[to_wxid])
  271. callback_to_user=msg_data["ToUserName"]["string"]
  272. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  273. input_message=dialogue_message(from_wxid,to_wxid,input_wx_content_dialogue_message)
  274. await request.app.state.kafaka_service.send_message_async(input_message)
  275. logger.info("发送对话 %s",input_message)
  276. else:
  277. callback_to_user=msg_data["FromUserName"]["string"]
  278. # 创建并启动任务协程,将参数传递给 ai_chat_text 函数
  279. task = asyncio.create_task(
  280. ai_chat_text_async(request,token_id, app_id, wxid, msg_data, msg_content)
  281. )
  282. # 设置定时器,1秒后检查任务是否超时。这里需要使用 lambda 来传递参数
  283. timeout_timer = asyncio.create_task(
  284. check_timeout_async(task, request,token_id, wxid, app_id, callback_to_user)
  285. )
  286. # 等待任务协程完成
  287. await task
  288. # 取消定时器
  289. timeout_timer.cancel()
  290. async def check_timeout_async(task: asyncio.Task, request: Request,token_id, wxid, app_id, callback_to_user):
  291. await asyncio.sleep(timeout_duration) # 等待超时时间
  292. if not task.done():
  293. print(f"任务运行时间超过{timeout_duration}秒,token_id={token_id}, app_id={app_id}, callback_to_user={callback_to_user}")
  294. wx_config = await request.app.state.gewe_service.get_wxchat_config_from_cache_async(wxid)
  295. if bool(wx_config.get("chatWaitingMsgEnabled", True)):
  296. await request.app.state.gewe_service.post_text_async(token_id, app_id, callback_to_user, "亲,我正在组织回复的信息,请稍等一会")
  297. async def ai_chat_text_async(request: Request,token_id, app_id, wxid, msg_data, msg_content):
  298. start_time = time.time() # 记录任务开始时间
  299. callback_to_user = msg_data["FromUserName"]["string"]
  300. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
  301. prompt = {"role": "user", "content": [{
  302. "type": "text",
  303. "text": msg_content
  304. }]}
  305. messages_to_send = await request.app.state.gewe_service.save_session_messages_to_cache_async(hash_key, prompt)
  306. # 收到的对话
  307. input_wx_content_dialogue_message = [{"type": "text", "text": msg_content}]
  308. input_message = dialogue_message(callback_to_user, wxid, input_wx_content_dialogue_message)
  309. await request.app.state.kafka_service.send_message_async(input_message)
  310. logger.info("发送对话 %s", input_message)
  311. cache_data = USER_INTERACTIVE_CACHE.get(wxid)
  312. if cache_data and cache_data.get('interactive'):
  313. o = get_first_char_if_digit(msg_content)
  314. if o is not None:
  315. userSelectOptions = cache_data.get('options')
  316. if o < len(userSelectOptions):
  317. o = o - 1
  318. msg_content = userSelectOptions[o].get("value")
  319. messages_to_send = [{"role": "user", "content": msg_content}]
  320. else:
  321. messages_to_send = [{"role": "user", "content": msg_content}]
  322. else:
  323. messages_to_send = [{"role": "user", "content": msg_content}]
  324. res = await gpt_client_async(request,messages_to_send, wxid, callback_to_user)
  325. reply_content = remove_markdown_symbol(res["choices"][0]["message"]["content"])
  326. description = ''
  327. userSelectOptions = []
  328. if isinstance(reply_content, list) and any(item.get("type") == "interactive" for item in reply_content):
  329. for item in reply_content:
  330. if item["type"] == "interactive" and item["interactive"]["type"] == "userSelect":
  331. params = item["interactive"]["params"]
  332. description = params.get("description")
  333. userSelectOptions = params.get("userSelectOptions", [])
  334. values_string = "\n".join(option["value"] for option in userSelectOptions)
  335. if description is not None:
  336. USER_INTERACTIVE_CACHE[wxid] = {
  337. "interactive": True,
  338. "options": userSelectOptions,
  339. }
  340. reply_content = description + '------------------------------\n' + values_string
  341. elif isinstance(reply_content, list) and any(item.get("type") == "text" for item in reply_content):
  342. USER_INTERACTIVE_CACHE[wxid] = {
  343. "interactive": False
  344. }
  345. text = ''
  346. for item in reply_content:
  347. if item["type"] == "text":
  348. text = item["text"]["content"]
  349. if text == '':
  350. # 去除上次上一轮对话再次请求
  351. cache_messages_str = await request.app.state.redis_service.get_hash_field(hash_key, "data")
  352. cache_messages = json.loads(cache_messages_str) if cache_messages_str else []
  353. if len(cache_messages) >= 3:
  354. cache_messages = cache_messages[:-3]
  355. await request.app.state.redis_service.update_hash_field(hash_key, "data", json.dumps(cache_messages, ensure_ascii=False))
  356. messages_to_send = await request.app.state.redis_service.save_session_messages_to_cache_async(hash_key, prompt)
  357. res = await gpt_client_async(request,messages_to_send, wxid, callback_to_user)
  358. reply_content = remove_markdown_symbol(res["choices"][0]["message"]["content"])
  359. if isinstance(reply_content, list):
  360. reply_content = remove_markdown_symbol(reply_content[0].get('text').get("content"))
  361. else:
  362. reply_content = text
  363. else:
  364. USER_INTERACTIVE_CACHE[wxid] = {
  365. "interactive": False
  366. }
  367. reply_content = remove_markdown_symbol(res["choices"][0]["message"]["content"])
  368. await request.app.state.gewe_service.post_text_async(token_id, app_id, callback_to_user, reply_content)
  369. await request.app.state.gewe_service.save_session_messages_to_cache_async(hash_key, {"role": "assistant", "content": reply_content})
  370. # 回复的对话
  371. input_wx_content_dialogue_message = [{"type": "text", "text": reply_content}]
  372. input_message = dialogue_message(wxid, callback_to_user, input_wx_content_dialogue_message, True)
  373. await request.app.state.kafka_service.send_message_async(input_message)
  374. logger.info("发送对话 %s", input_message)
  375. end_time = time.time() # 记录任务结束时间
  376. execution_time = end_time - start_time # 计算执行时间
  377. logger.info(f"AI回答任务完成,耗时 {execution_time:.2f} 秒")
  378. async def handle_text_group_async(request: Request,token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  379. '''
  380. 群聊文本消息
  381. '''
  382. msg_content=msg_data["Content"]["string"]
  383. msg_push_content=msg_data.get("PushContent")
  384. k,login_info=await request.app.state.gewe_service.get_login_info_by_app_id_async(app_id)
  385. nickname=login_info.get("nickName")
  386. if wxid == from_wxid: #手动发送消息
  387. logger.info("Active message sending detected")
  388. await request.app.state.gewe_service.save_contacts_brief_to_cache_async(token_id,app_id,wxid,[to_wxid])
  389. callback_to_user=msg_data["ToUserName"]["string"]
  390. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  391. input_message=dialogue_message(from_wxid,to_wxid,input_wx_content_dialogue_message)
  392. await request.app.state.kafka_service.send_message_async(input_message)
  393. logger.info("发送对话 %s",input_message)
  394. else:
  395. c = await request.app.state.gewe_service.get_wxchat_config_from_cache_async(wxid)
  396. chatroom_id_white_list = c.get("chatroomIdWhiteList", [])
  397. if not chatroom_id_white_list:
  398. logger.info('白名单为空或未定义,不处理')
  399. return
  400. if from_wxid not in chatroom_id_white_list:
  401. logger.info(f'群ID {from_wxid} 不在白名单中,不处理')
  402. return
  403. if '在群聊中@了你' in msg_push_content or '@'+nickname in msg_push_content:
  404. callback_to_user=msg_data["FromUserName"]["string"]
  405. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
  406. prompt={"role": "user", "content": [{
  407. "type": "text",
  408. "text": msg_content
  409. }]}
  410. messages_to_send=await request.app.state.gewe_service.save_session_messages_to_cache_async(hash_key, prompt)
  411. # 收到的对话
  412. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  413. input_message=dialogue_message(callback_to_user,wxid,input_wx_content_dialogue_message)
  414. await request.app.state.kafka_service.send_message_async(input_message)
  415. logger.info("发送对话 %s",input_message)
  416. cache_data = USER_INTERACTIVE_CACHE.get(wxid)
  417. if cache_data and cache_data.get('interactive') :
  418. o=get_first_char_if_digit(msg_content)
  419. if o is not None:
  420. userSelectOptions=cache_data.get('options')
  421. if o < len(userSelectOptions):
  422. o=o-1
  423. msg_content=userSelectOptions[o].get("value")
  424. messages_to_send=[{"role": "user", "content": msg_content}]
  425. else:
  426. messages_to_send=[{"role": "user", "content": msg_content}]
  427. else:
  428. messages_to_send=[{"role": "user", "content": msg_content}]
  429. res=await gpt_client_async(request,messages_to_send,wxid,callback_to_user)
  430. reply_content=remove_markdown_symbol(res["choices"][0]["message"]["content"])
  431. description = ''
  432. userSelectOptions = []
  433. if isinstance(reply_content, list) and any(item.get("type") == "interactive" for item in reply_content):
  434. for item in reply_content:
  435. if item["type"] == "interactive" and item["interactive"]["type"] == "userSelect":
  436. params = item["interactive"]["params"]
  437. description = params.get("description")
  438. userSelectOptions = params.get("userSelectOptions", [])
  439. values_string = "\n".join(option["value"] for option in userSelectOptions)
  440. if description is not None:
  441. USER_INTERACTIVE_CACHE[wxid] = {
  442. "interactive":True,
  443. "options": userSelectOptions,
  444. }
  445. reply_content=description + '------------------------------\n'+values_string
  446. elif isinstance(reply_content, list) and any(item.get("type") == "text" for item in reply_content):
  447. USER_INTERACTIVE_CACHE[wxid] = {
  448. "interactive":False
  449. }
  450. text=''
  451. for item in reply_content:
  452. if item["type"] == "text":
  453. text=item["text"]["content"]
  454. if text=='':
  455. # 去除上次上一轮对话再次请求
  456. cache_messages_str=await request.app.state.redis_service.get_hash_field(hash_key,"data")
  457. cache_messages = json.loads(cache_messages_str) if cache_messages_str else []
  458. if len(cache_messages) >= 3:
  459. cache_messages = cache_messages[:-3]
  460. await request.app.state.redis_service.update_hash_field(hash_key,"data",json.dumps(cache_messages,ensure_ascii=False))
  461. messages_to_send=await request.app.state.gewe_service.save_session_messages_to_cache_async(hash_key, prompt)
  462. res=await gpt_client_async(request,messages_to_send,wxid,callback_to_user)
  463. reply_content=remove_markdown_symbol(res["choices"][0]["message"]["content"])
  464. else:
  465. reply_content=text
  466. else:
  467. USER_INTERACTIVE_CACHE[wxid] = {
  468. "interactive":False
  469. }
  470. reply_content=res["choices"][0]["message"]["content"]
  471. reply_content='@'+extract_nickname(msg_push_content) + reply_content
  472. await request.app.state.gewe_service.post_text_async(token_id,app_id,callback_to_user,reply_content)
  473. await request.app.state.gewe_service.save_session_messages_to_cache_async(hash_key, {"role": "assistant", "content": reply_content})
  474. # 回复的对话
  475. input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
  476. input_message=dialogue_message(wxid,callback_to_user,input_wx_content_dialogue_message,True)
  477. await request.app.state.kafka_service.send_message_async(input_message)
  478. logger.info("发送对话 %s",input_message)
  479. else:
  480. logger.info('群聊公开消息')
  481. callback_to_user=msg_data["FromUserName"]["string"]
  482. group_dialogue_message=[{"type": "text", "text": msg_content}]
  483. input_message=dialogue_message(callback_to_user,wxid,group_dialogue_message)
  484. await request.app.state.kafka_service.send_message_async(input_message)
  485. logger.info("发送对话 %s",input_message)
  486. return
  487. async def handle_image_async(request: Request,token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  488. '''
  489. 私聊图片消息
  490. '''
  491. msg_content=msg_data["Content"]["string"]
  492. callback_to_user=from_wxid
  493. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
  494. wx_img_url= await request.app.state.gewe_service.download_image_msg_async(token_id,app_id,msg_content)
  495. oss_access_key_id="LTAI5tRTG6pLhTpKACJYoPR5"
  496. oss_access_key_secret="E7dMzeeMxq4VQvLg7Tq7uKf3XWpYfN"
  497. oss_endpoint="http://oss-cn-shanghai.aliyuncs.com"
  498. oss_bucket_name="cow-agent"
  499. oss_prefix="cow"
  500. img_url=upload_oss(oss_access_key_id, oss_access_key_secret, oss_endpoint, oss_bucket_name, wx_img_url, oss_prefix)
  501. prompt={
  502. "role": "user",
  503. "content": [{
  504. "type": "image_url",
  505. "image_url": {"url": img_url}
  506. }]
  507. }
  508. await request.app.state.gewe_service.save_session_messages_to_cache_async(hash_key, prompt)
  509. await request.app.state.gewe_service.post_text_async(token_id,app_id,callback_to_user,'已经上传了图片,有什么可以为您服务')
  510. logger.info(f"上传图片 URL: {img_url}")
  511. wx_content_dialogue_message=[{"type": "image_url", "image_url": {"url": img_url}}]
  512. input_message=dialogue_message(wxid,callback_to_user,wx_content_dialogue_message)
  513. await request.app.state.kafka_service.send_message_async(input_message)
  514. logger.info("发送对话 %s",input_message)
  515. async def handle_image_group_async(request: Request,token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  516. logger.info('群聊图片消息')
  517. async def handle_voice_async(request: Request,token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  518. '''
  519. 私聊语音消息
  520. '''
  521. callback_to_user=from_wxid
  522. msg_content=msg_data["Content"]["string"]
  523. msg_id=msg_data["MsgId"]
  524. file_url=await request.app.state.gewe_service.download_audio_msg_async(token_id,app_id,msg_id,msg_content)
  525. react_silk_path=await save_to_local_from_url_async(file_url)
  526. react_wav_path = os.path.splitext(react_silk_path)[0] + ".wav"
  527. audio_convert.any_to_wav(react_silk_path,react_wav_path)
  528. react_voice_text=AliVoice().voiceToText(react_wav_path)
  529. os.remove(react_silk_path)
  530. os.remove(react_wav_path)
  531. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
  532. messages=await request.app.state.gewe_service.save_session_messages_to_cache_async(hash_key, {"role": "user", "content": react_voice_text})
  533. ai_res=await gpt_client_async(request,messages,wxid,callback_to_user)
  534. ai_res_content=remove_markdown_symbol(ai_res["choices"][0]["message"]["content"])
  535. has_url=contains_url(ai_res_content)
  536. if not has_url:
  537. voice_during,voice_url=wx_voice(ai_res_content)
  538. if voice_during < 60 * 1000:
  539. ret,ret_msg,res=await request.app.state.gewe_service.post_voice_async(token_id,app_id,callback_to_user,voice_url,voice_during)
  540. else:
  541. ret,ret_msg,res=await request.app.state.gewe_service.post_text_async(token_id,app_id,callback_to_user,ai_res_content)
  542. logger.warning(f'回应语音消息长度 {voice_during/1000}秒,超过60秒,转为文本回复')
  543. if ret==200:
  544. logger.info((f'{wxid} 向 {callback_to_user} 发送语音文本【{ai_res_content}】{ret_msg}'))
  545. else:
  546. logger.warning((f'{wxid} 向 {callback_to_user} 发送语音文本【{ai_res_content}】{ret_msg}'))
  547. ret,ret_msg,res==await request.app.state.gewe_service.post_text_async(token_id,app_id,callback_to_user,ai_res_content)
  548. logger.info((f'{wxid} 向 {callback_to_user} 发送文本【{ai_res_content}】{ret_msg}'))
  549. else:
  550. logger.info(f"回复内容包含网址,不发送语音,回复文字内容:{ai_res_content}")
  551. ret,ret_msg,res=await request.app.state.gewe_service.post_text_async(token_id,app_id,callback_to_user,ai_res_content)
  552. await request.app.state.gewe_service.save_session_messages_to_cache_async(hash_key, {"role": "assistant", "content": ai_res_content})
  553. # 构造对话消息并发送到 Kafka
  554. input_wx_content_dialogue_message = [{"type": "text", "text": ai_res_content}]
  555. input_message = dialogue_message(wxid, callback_to_user, input_wx_content_dialogue_message,True)
  556. await request.app.state.kafka_service.send_message_async(input_message)
  557. logger.info("发送对话 %s", input_message)
  558. async def handle_voice_group_async(request: Request,token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  559. logger.info('群聊语音消息')
  560. async def handle_name_card_async(request: Request,token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  561. logger.info('名片消息')
  562. try:
  563. msg_content_xml=msg_data["Content"]["string"]
  564. # 解析XML字符串
  565. root = ET.fromstring(msg_content_xml)
  566. # 提取alias属性
  567. alias_value = root.get("alias")
  568. # 加好友资料
  569. scene = int(root.get("scene"))
  570. v3 = root.get("username")
  571. v4 = root.get("antispamticket")
  572. logger.info(f"alias_value: {alias_value}, scene: {scene}, v3: {v3}, v4: {v4}")
  573. # 判断appid 是否已经创建3天
  574. k, login_info = await request.app.state.gewe_service.get_login_info_by_wxid_async(wxid)
  575. creation_timestamp=int(login_info.get('create_at',time.time()))
  576. current_timestamp = time.time()
  577. three_days_seconds = 3 * 24 * 60 * 60 # 三天的秒数
  578. diff_flag=(current_timestamp - creation_timestamp) >= three_days_seconds
  579. if not diff_flag:
  580. log_content=f'名片添加好友功能,{wxid} 用户创建不够三天,不能使用该功能'
  581. logger.warning(log_content)
  582. return
  583. # 将加好友资料添加到待加好友队列
  584. #gewe_chat.wxchat.enqueue_to_add_contacts(wxid,scene,v3,v4)
  585. _,loginfo=await request.app.state.gewe_service.get_login_info_by_wxid_async(wxid)
  586. nickname=loginfo.get('nickName')
  587. add_contact_content=f'您好,我是{nickname}'
  588. #gewe_chat.wxchat.add_contacts(token_id,app_id,scene,Models.OperationType.ADD_FRIEND,v3,v4,add_contact_content)
  589. await request.app.state.gewe_service.add_contacts_async(token_id,app_id,scene,OperationType.ADD_FRIEND.value,v3,v4,add_contact_content)
  590. except ET.ParseError as e:
  591. logger.error(f"XML解析错误: {e}")
  592. except KeyError as e:
  593. logger.error(f"字典键错误: {e}")
  594. except Exception as e:
  595. logger.error(f"未知错误: {e}")
  596. async def handle_xml_async(request: Request,token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  597. '''
  598. 处理xml
  599. '''
  600. try:
  601. msg_content_xml=msg_data["Content"]["string"]
  602. root = ET.fromstring(msg_content_xml)
  603. type_value = int(root.find(".//appmsg/type").text)
  604. handlers = {
  605. 57: handle_xml_reference_async,
  606. 5: handle_xml_invite_group_async
  607. }
  608. handler = handlers.get(type_value)
  609. if handler:
  610. return await handler(request,token_id,app_id, wxid,msg_data,from_wxid, to_wxid)
  611. # elif "邀请你加入了群聊" in msg_content_xml: # 邀请加入群聊
  612. # logger.warning(f"xml消息 {type_value} 邀请你加入了群聊.todo")
  613. else:
  614. print(f"xml消息 {type_value} 未解析")
  615. except ET.ParseError as e:
  616. logger.error(f"解析XML失败: {e}")
  617. except Exception as e:
  618. logger.error(f"未知错误: {e}")
  619. return
  620. async def handle_xml_reference_async(request: Request,token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  621. '''
  622. 引用消息
  623. 判断此类消息的逻辑:$.Data.MsgType=49 并且 解析$.Data.Content.string中的xml msg.appmsg.type=57
  624. '''
  625. callback_to_user=from_wxid
  626. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
  627. msg_content= msg_data["PushContent"]
  628. prompt={"role": "user", "content": [{
  629. "type": "text",
  630. "text": msg_content
  631. }]}
  632. # 收到的对话
  633. messages_to_send=await request.app.state.gewe_service.save_session_messages_to_cache_async(hash_key, prompt)
  634. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  635. input_message=dialogue_message(callback_to_user,wxid,input_wx_content_dialogue_message)
  636. await request.app.state.kafka_service.send_message_async(input_message)
  637. logger.info("发送对话 %s",input_message)
  638. # 回复的对话
  639. res=await gpt_client_async(request,messages_to_send,wxid,callback_to_user)
  640. reply_content=remove_markdown_symbol(res["choices"][0]["message"]["content"])
  641. input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
  642. input_message=dialogue_message(wxid,callback_to_user,input_wx_content_dialogue_message,True)
  643. await request.app.state.kafka_service.kafka_client.produce_message(input_message)
  644. logger.info("发送对话 %s",input_message)
  645. await request.app.state.kafka_service.save_session_messages_to_cache_async(hash_key, {"role": "assistant", "content": reply_content})
  646. await request.app.state.kafka_service.post_text_async(token_id,app_id,callback_to_user,reply_content)
  647. async def handle_xml_invite_group_async (request: Request,token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  648. '''
  649. 群聊邀请
  650. 判断此类消息的逻辑:$.Data.MsgType=49
  651. 并且 解析$.Data.Content.string中的xml msg.appmsg.title=邀请你加入群聊(根据手机设置的系统语言title会有调整,不同语言关键字不同)
  652. '''
  653. logger.info(f'{wxid} 群聊邀请')
  654. msg_content_xml=msg_data["Content"]["string"]
  655. root = ET.fromstring(msg_content_xml)
  656. title_value = root.find(".//appmsg/title").text
  657. if '邀请你加入群聊' in title_value:
  658. invite_url = root.find('.//url').text
  659. ret,msg,data=await request.app.state.gewe_service.agree_join_room_async(token_id,app_id,invite_url)
  660. if ret==200:
  661. logger.info(f'群聊邀请,同意加入群聊 {msg} {data}')
  662. chatroom_id=data.get('chatroomId','')
  663. # if not chatroom_id:
  664. # logger.warning(f'群聊邀请,同意加入群聊失败 {msg} {data}')
  665. # return
  666. ret,msg,data=await request.app.state.gewe_service.save_contract_list_async(token_id,app_id,chatroom_id,3)
  667. logger.info(f'群聊邀请,保存到通讯录 chatroom_id {chatroom_id} {msg}')
  668. await request.app.state.gewe_service.update_group_info_to_cache_async(token_id,app_id,wxid,chatroom_id)
  669. await request.app.state.gewe_service.update_group_members_to_cache_async(token_id,app_id,wxid,chatroom_id)
  670. else:
  671. logger.warning(f'群聊邀请,同意加入群聊失败 {msg} {data}')
  672. async def handle_add_friend_notice_async(request: Request,token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  673. '''
  674. 好友添加请求通知
  675. '''
  676. logger.info('好友添加请求通知')
  677. try:
  678. msg_content_xml=msg_data["Content"]["string"]
  679. root = ET.fromstring(msg_content_xml)
  680. msg_content = root.attrib.get('content', None)
  681. v3= root.attrib.get('encryptusername', None)
  682. v4= root.attrib.get('ticket', None)
  683. scene=root.attrib.get('scene', None)
  684. to_contact_wxid=root.attrib.get('fromusername', None)
  685. wxid=msg_data["ToUserName"]["string"]
  686. # 自动同意好友
  687. # print(v3)
  688. # print(v4)
  689. # print(scene)
  690. # print(msg_content)
  691. # 操作类型,2添加好友 3同意好友 4拒绝好友
  692. #option=2
  693. option=3
  694. reply_add_contact_contact="亲,我是你的好友"
  695. ret,ret_msg=await request.app.state.gewe_service.add_contacts_async(token_id,app_id,scene,option,v3,v4,reply_add_contact_contact)
  696. if ret==200:
  697. logger.info('自动添加好友成功')
  698. # 好友发送的文字
  699. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{to_contact_wxid}'
  700. prompt={"role": "user", "content": [{"type": "text","text": msg_content}]}
  701. messages_to_send=await request.app.state.gewe_service.save_session_messages_to_cache_async(hash_key, prompt)
  702. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  703. input_message=dialogue_message(to_contact_wxid,wxid,input_wx_content_dialogue_message)
  704. await request.app.state.gewe_service.send_message_async(input_message)
  705. logger.info("发送对话 %s",input_message)
  706. callback_to_user=to_contact_wxid
  707. res=await gpt_client_async(messages_to_send,wxid,callback_to_user)
  708. reply_content=remove_markdown_symbol(res["choices"][0]["message"]["content"])
  709. #保存好友信息
  710. await request.app.state.gewe_service.save_contacts_brief_to_cache_async(token_id,app_id, wxid,[to_contact_wxid])
  711. # 保存到缓存
  712. await request.app.state.gewe_service.save_session_messages_to_cache_async(hash_key, {"role": "assistant", "content": reply_content})
  713. # 发送信息
  714. await request.app.state.gewe_service.post_text_async(token_id,app_id, to_contact_wxid,reply_content)
  715. # 发送到kafka
  716. input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
  717. input_message=dialogue_message(wxid,to_contact_wxid,input_wx_content_dialogue_message,True)
  718. request.app.state.kafka_service.send_message_async(input_message)
  719. logger.info("发送对话 %s",input_message)
  720. else:
  721. logger.warning("添加好友失败")
  722. except ET.ParseError as e:
  723. logger.error(f"解析XML失败: {e}")
  724. except Exception as e:
  725. logger.error(f"未知错误: {e}")
  726. return
  727. async def handle_10002_msg(request: Request,token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  728. '''
  729. 群聊邀请
  730. 撤回消息
  731. 拍一拍消息
  732. 地理位置
  733. 踢出群聊通知
  734. 解散群聊通知
  735. 发布群公告
  736. '''
  737. try:
  738. msg_content_xml=msg_data["Content"]["string"]
  739. # 群聊邀请
  740. if '邀请你加入了群聊' in msg_content_xml and check_chatroom(msg_data["FromUserName"]["string"]):
  741. chatroom_id=msg_data["FromUserName"]["string"]
  742. ret,msg,data=await request.app.state.gewe_service.save_contract_list_async(token_id,app_id,chatroom_id,3)
  743. logger.info(f'群聊邀请,保存到通讯录 chatroom_id {chatroom_id} {msg}')
  744. await request.app.state.gewe_service.update_group_info_to_cache_async(token_id,app_id,wxid,chatroom_id)
  745. await request.app.state.gewe_service.update_group_members_to_cache_async(token_id,app_id,wxid,chatroom_id)
  746. if '移出了群聊' in msg_content_xml and 'sysmsgtemplate' in msg_content_xml :
  747. chatroom_id=msg_data["FromUserName"]["string"]
  748. ret,msg,data=await request.app.state.gewe_service.save_contract_list_async(token_id,app_id,chatroom_id,2)
  749. logger.info(f'踢出群聊,移除从通讯录 chatroom_id {chatroom_id} {msg}')
  750. await request.app.state.redis_service.delete_hash_field(f'__AI_OPS_WX__:GROUPS_INFO:{wxid}',chatroom_id)
  751. logger.info(f'清除 chatroom_id{chatroom_id} 数据')
  752. if '已解散该群聊' in msg_content_xml and 'sysmsgtemplate' in msg_content_xml :
  753. chatroom_id=msg_data["FromUserName"]["string"]
  754. ret,msg,data=await request.app.state.gewe_service.save_contract_list_async(token_id,app_id,chatroom_id,2)
  755. logger.info(f'解散群聊,移除从通讯录 chatroom_id {chatroom_id} {msg}')
  756. await request.app.state.redis_service.delete_hash_field(f'__AI_OPS_WX__:GROUPS_INFO:{wxid}',chatroom_id)
  757. logger.info(f'清除 chatroom_id{chatroom_id} 数据')
  758. print('撤回消息,拍一拍消息,地理位置')
  759. except ET.ParseError as e:
  760. logger.error(f"解析XML失败: {e}")
  761. except Exception as e:
  762. logger.error(f"未知错误: {e}")
  763. return