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

messages_resource.py 47KB

4ヶ月前
3ヶ月前
4ヶ月前
4ヶ月前
2ヶ月前
4ヶ月前
2ヶ月前
2ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
4ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
4ヶ月前
2ヶ月前
2ヶ月前
4ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
2ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
4ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
2ヶ月前
4ヶ月前
2ヶ月前
4ヶ月前
2ヶ月前
4ヶ月前
2ヶ月前
4ヶ月前
2ヶ月前
4ヶ月前
2ヶ月前
4ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
4ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
4ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
4ヶ月前
4ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
2ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
2ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
2ヶ月前
3ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
2ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
2ヶ月前
3ヶ月前
3ヶ月前
4ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
4ヶ月前
3ヶ月前
3ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
2ヶ月前
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108
  1. from flask_restful import Resource, reqparse
  2. from flask import jsonify,request
  3. import requests,json,re
  4. from wechat import gewe_chat
  5. from voice.ali.ali_voice import AliVoice
  6. from common import utils,redis_helper,memory,kafka_helper
  7. from common.log import logger
  8. import xml.etree.ElementTree as ET
  9. import threading,time
  10. from model import Models
  11. import os
  12. from voice import audio_convert
  13. timeout_duration = 8.0
  14. WX_BACKLIST=['fmessage', 'medianote','weixin','weixingongzhong']
  15. class MessagesResource(Resource):
  16. def __init__(self):
  17. self.parser = reqparse.RequestParser()
  18. def post(self):
  19. msg = request.get_json()
  20. logger.info(f"收到微信回调消息: {json.dumps(msg, separators=(',', ':'),ensure_ascii=False)}")
  21. type_name =msg.get("TypeName")
  22. app_id = msg.get("Appid")
  23. # token_id = "f828cb3c-1039-489f-b9ae-7494d1778a15"
  24. token_id=get_token_id_by_app_id(app_id)
  25. if token_id=="":
  26. logger.warning('找不到登录信息,不处理')
  27. return jsonify({"message": "收到微信回调消息"})
  28. wxid = msg.get("Wxid",'')
  29. # 自发命令
  30. # if type_name=='AddMsg':
  31. # from_wxid = msg_data["FromUserName"]["string"]
  32. # if wxid=='from_wxid':
  33. # pass
  34. if type_name == 'AddMsg':
  35. handle_self_cmd(wxid,msg)
  36. msg_data = msg.get("Data")
  37. from_wxid = msg_data["FromUserName"]["string"]
  38. config=redis_helper.redis_helper.get_hash(f"__AI_OPS_WX__:WXCHAT_CONFIG")
  39. wxids=config.keys()
  40. WX_BACKLIST.extend(wxids)
  41. if from_wxid in WX_BACKLIST:
  42. logger.warning(f'微信ID {wxid} 在黑名单,不处理')
  43. return jsonify({"message": "收到微信回调消息"})
  44. wx_config = gewe_chat.wxchat.get_wxchat_config_from_cache(wxid)
  45. if type_name=='AddMsg':
  46. if not bool(wx_config.get("agentEnabled",False)):
  47. logger.info(f'微信ID {wxid} 未托管,不处理')
  48. return jsonify({"message": "收到微信回调消息"})
  49. #wxid = msg.get("Wxid")
  50. msg_data = msg.get("Data")
  51. msg_type = msg_data.get("MsgType",None)
  52. from_wxid = msg_data["FromUserName"]["string"]
  53. to_wxid = msg_data["ToUserName"]["string"]
  54. msg_push_content=msg_data.get("PushContent") #群发
  55. #wx_config = gewe_chat.wxchat.get_wxchat_config_from_cache(wxid)
  56. handlers = {
  57. 1: handle_text,
  58. 3: handle_image,
  59. 34: handle_voice,
  60. 42: handle_name_card,
  61. 49: handle_xml,
  62. 37: handle_add_friend_notice,
  63. 10002: handle_10002_msg
  64. }
  65. # (扫码进群情况)判断受否是群聊,并添加到通信录
  66. if check_chatroom(from_wxid) or check_chatroom(to_wxid):
  67. logger.info('群信息')
  68. chatroom_id=from_wxid
  69. ret,msg,data=gewe_chat.wxchat.save_contract_list(token_id,app_id,chatroom_id,3)
  70. logger.info(f'保存到通讯录 chatroom_id {chatroom_id} {msg}')
  71. gewe_chat.wxchat.update_group_info_to_cache(token_id,app_id,wxid,chatroom_id)
  72. gewe_chat.wxchat.update_group_members_to_cache(token_id,app_id,wxid,chatroom_id)
  73. handlers[1]=handle_text_group
  74. handlers[3]=handle_image_group
  75. handlers[34]=handle_voice_group
  76. handler = handlers.get(msg_type)
  77. if handler:
  78. return handler(token_id,app_id, wxid,msg_data,from_wxid, to_wxid)
  79. else:
  80. logger.warning(f"微信回调消息类型 {msg_type} 未处理")
  81. elif type_name=='ModContacts':
  82. '''
  83. 好友通过验证及好友资料变更的通知消息
  84. '''
  85. wxid = msg.get("Wxid")
  86. msg_data = msg.get("Data")
  87. #
  88. #handle_mod_contacts(token_id,app_id,wxid,msg_data)
  89. #
  90. threading.Thread(target=handle_mod_contacts, args=(token_id,app_id,wxid,msg_data)).start()
  91. contact_wxid = msg_data["UserName"]["string"]
  92. nickname=msg_data["NickName"]["string"]
  93. city=msg_data["City"]
  94. signature=msg_data["Signature"]
  95. province=msg_data["Province"]
  96. bigHeadImgUrl=msg_data["SnsUserInfo"]["SnsBgimgId"]
  97. country=msg_data["Country"]
  98. sex=msg_data["Sex"]
  99. pyInitial=msg_data["PyInitial"]["string"]
  100. quanPin=msg_data["QuanPin"]["string"]
  101. remark=msg_data.get("Remark").get("string","")
  102. remarkPyInitial=msg_data.get("RemarkPyInitial").get("string","")
  103. remarkQuanPin=msg_data.get("RemarkQuanPin").get("string","")
  104. smallHeadImgUrl=msg_data["smallHeadImgUrl"]
  105. # data=gewe_chat.wxchat.get_brief_info(token_id,app_id,[contact_wxid])
  106. # contact=data[0]
  107. # alias=contact.get("alias")
  108. #推动到kafka
  109. contact_data = {
  110. "alias": None,
  111. "bigHeadImgUrl": bigHeadImgUrl,
  112. "cardImgUrl": None,
  113. "city": city,
  114. "country": country,
  115. "description": None,
  116. "labelList": None,
  117. "nickName": nickname,
  118. "phoneNumList": None,
  119. "province": province,
  120. "pyInitial": pyInitial,
  121. "quanPin": quanPin,
  122. "remark": remark,
  123. "remarkPyInitial": remarkPyInitial,
  124. "remarkQuanPin": remarkQuanPin,
  125. "sex": sex,
  126. "signature": signature,
  127. "smallHeadImgUrl": smallHeadImgUrl,
  128. "snsBgImg": None,
  129. "userName": contact_wxid
  130. }
  131. input_message=utils.wx_mod_contact_message(wxid,contact_data)
  132. kafka_helper.kafka_client.produce_message(input_message)
  133. elif type_name=="DelContacts":
  134. '''
  135. 删除好友通知/退出群聊
  136. '''
  137. msg_data = msg.get("Data")
  138. username=msg_data["UserName"]["string"]
  139. if check_chatroom(username):
  140. logger.info('退出群聊')
  141. wxid = msg.get("Wxid")
  142. chatroom_id=username
  143. redis_helper.redis_helper.delete_hash_field(f'__AI_OPS_WX__:GROUPS_INFO:{wxid}',chatroom_id)
  144. logger.info(f'清除 chatroom_id{chatroom_id} 数据')
  145. else:
  146. logger.info('删除好友通知')
  147. # 推送到kafka
  148. input_message=utils.wx_del_contact_message(wxid,username)
  149. kafka_helper.kafka_client.produce_message(input_message)
  150. elif type_name=="Offline":
  151. '''
  152. 已经离线
  153. '''
  154. wxid = msg.get("Wxid")
  155. logger.warning(f'微信ID {wxid}在设备{app_id}已经离线')
  156. k,r=get_login_info_by_app_id(app_id)
  157. print(k)
  158. redis_helper.redis_helper.update_hash_field(k,'status',0)
  159. redis_helper.redis_helper.update_hash_field(k,'modify_at',int(time.time()))
  160. # 推送到kafka
  161. input_message=utils.wx_offline_message(app_id,wxid)
  162. kafka_helper.kafka_client.produce_message(input_message)
  163. else:
  164. logger.warning(f"未知消息类型")
  165. return jsonify({"message": "收到微信回调消息"})
  166. def handle_name_card(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  167. logger.info('名片消息')
  168. try:
  169. msg_content_xml=msg_data["Content"]["string"]
  170. # 解析XML字符串
  171. root = ET.fromstring(msg_content_xml)
  172. # 提取alias属性
  173. alias_value = root.get("alias")
  174. # 加好友资料
  175. scene = int(root.get("scene"))
  176. v3 = root.get("username")
  177. v4 = root.get("antispamticket")
  178. logger.info(f"alias_value: {alias_value}, scene: {scene}, v3: {v3}, v4: {v4}")
  179. # 判断appid 是否已经创建3天
  180. k, login_info = utils.get_login_info_by_wxid(wxid)
  181. creation_timestamp=int(login_info.get('create_at',time.time()))
  182. current_timestamp = time.time()
  183. three_days_seconds = 3 * 24 * 60 * 60 # 三天的秒数
  184. diff_flag=(current_timestamp - creation_timestamp) >= three_days_seconds
  185. if not diff_flag:
  186. log_content=f'名片添加好友功能,{wxid} 用户创建不够三天,不能使用该功能'
  187. logger.warning(log_content)
  188. response=jsonify({'code': 401, 'message':log_content})
  189. response.status_code = 401
  190. return response
  191. # 将加好友资料添加到待加好友队列
  192. #gewe_chat.wxchat.enqueue_to_add_contacts(wxid,scene,v3,v4)
  193. _,loginfo=utils.get_login_info_by_wxid(wxid)
  194. nickname=loginfo.get('nickName')
  195. add_contact_content=f'您好,我是{nickname}'
  196. #gewe_chat.wxchat.add_contacts(token_id,app_id,scene,Models.OperationType.ADD_FRIEND,v3,v4,add_contact_content)
  197. gewe_chat.wxchat.add_contacts(token_id,app_id,scene,Models.OperationType.ADD_FRIEND.value,v3,v4,add_contact_content)
  198. except ET.ParseError as e:
  199. logger.error(f"XML解析错误: {e}")
  200. except KeyError as e:
  201. logger.error(f"字典键错误: {e}")
  202. except Exception as e:
  203. logger.error(f"未知错误: {e}")
  204. def handle_self_cmd(wxid,msg):
  205. '''
  206. 个人微信命令处理
  207. 如果是个人微信的指令,wxid == from_wxid
  208. commands = {
  209. '启用托管': True,
  210. '关闭托管': False
  211. }
  212. '''
  213. msg_data=msg.get("Data")
  214. from_wxid=msg_data["FromUserName"]["string"]
  215. msg_content=msg_data["Content"]["string"]
  216. if wxid == from_wxid:
  217. commands = {
  218. '启用托管': True,
  219. '关闭托管': False
  220. }
  221. if msg_content in commands:
  222. c_dict = gewe_chat.wxchat.get_wxchat_config_from_cache(wxid)
  223. if c_dict:
  224. from model import Models
  225. agent_config=Models.AgentConfig.model_validate(c_dict)
  226. agent_config.agentEnabled=commands[msg_content]
  227. gewe_chat.wxchat.save_wxchat_config(wxid, agent_config.model_dump())
  228. logger.info(f'{wxid} {"启动" if commands[msg_content] else "关闭"}托管')
  229. def handle_text(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  230. '''
  231. 私聊文本消息
  232. '''
  233. msg_content=msg_data["Content"]["string"]
  234. if wxid == from_wxid: #手动发送消息
  235. logger.info("Active message sending detected")
  236. gewe_chat.wxchat.save_contacts_brief_to_cache(token_id,app_id,wxid,[to_wxid])
  237. callback_to_user=msg_data["ToUserName"]["string"]
  238. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  239. input_message=utils.dialogue_message(from_wxid,to_wxid,input_wx_content_dialogue_message)
  240. kafka_helper.kafka_client.produce_message(input_message)
  241. logger.info("发送对话 %s",input_message)
  242. else:
  243. callback_to_user=msg_data["FromUserName"]["string"]
  244. # hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
  245. # prompt={"role": "user", "content": [{
  246. # "type": "text",
  247. # "text": msg_content
  248. # }]}
  249. # messages_to_send=gewe_chat.wxchat.save_session_messages_to_cache(hash_key, prompt)
  250. # # 收到的对话
  251. # input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  252. # input_message=utils.dialogue_message(callback_to_user,wxid,input_wx_content_dialogue_message)
  253. # kafka_helper.kafka_client.produce_message(input_message)
  254. # logger.info("发送对话 %s",input_message)
  255. # cache_data = memory.USER_INTERACTIVE_CACHE.get(wxid)
  256. # if cache_data and cache_data.get('interactive') :
  257. # o=get_first_char_if_digit(msg_content)
  258. # if o is not None:
  259. # userSelectOptions=cache_data.get('options')
  260. # if o < len(userSelectOptions):
  261. # o=o-1
  262. # msg_content=userSelectOptions[o].get("value")
  263. # messages_to_send=[{"role": "user", "content": msg_content}]
  264. # else:
  265. # messages_to_send=[{"role": "user", "content": msg_content}]
  266. # else:
  267. # messages_to_send=[{"role": "user", "content": msg_content}]
  268. # res=fast_gpt_api(messages_to_send,wxid,callback_to_user)
  269. # reply_content=res["choices"][0]["message"]["content"]
  270. # description = ''
  271. # userSelectOptions = []
  272. # if isinstance(reply_content, list) and any(item.get("type") == "interactive" for item in reply_content):
  273. # for item in reply_content:
  274. # if item["type"] == "interactive" and item["interactive"]["type"] == "userSelect":
  275. # params = item["interactive"]["params"]
  276. # description = params.get("description")
  277. # userSelectOptions = params.get("userSelectOptions", [])
  278. # values_string = "\n".join(option["value"] for option in userSelectOptions)
  279. # if description is not None:
  280. # memory.USER_INTERACTIVE_CACHE[wxid] = {
  281. # "interactive":True,
  282. # "options": userSelectOptions,
  283. # }
  284. # reply_content=description + '------------------------------\n'+values_string
  285. # elif isinstance(reply_content, list) and any(item.get("type") == "text" for item in reply_content):
  286. # memory.USER_INTERACTIVE_CACHE[wxid] = {
  287. # "interactive":False
  288. # }
  289. # text=''
  290. # for item in reply_content:
  291. # if item["type"] == "text":
  292. # text=item["text"]["content"]
  293. # if text=='':
  294. # # 去除上次上一轮对话再次请求
  295. # cache_messages_str=redis_helper.redis_helper.get_hash_field(hash_key,"data")
  296. # cache_messages = json.loads(cache_messages_str) if cache_messages_str else []
  297. # if len(cache_messages) >= 3:
  298. # cache_messages = cache_messages[:-3]
  299. # redis_helper.redis_helper.update_hash_field(hash_key,"data",json.dumps(cache_messages,ensure_ascii=False))
  300. # messages_to_send=gewe_chat.wxchat.save_session_messages_to_cache(hash_key, prompt)
  301. # res=fast_gpt_api(messages_to_send,wxid,callback_to_user)
  302. # reply_content=res["choices"][0]["message"]["content"]
  303. # if isinstance(reply_content, list) :
  304. # reply_content=reply_content[0].get('text').get("content")
  305. # else:
  306. # reply_content=text
  307. # else:
  308. # memory.USER_INTERACTIVE_CACHE[wxid] = {
  309. # "interactive":False
  310. # }
  311. # reply_content=res["choices"][0]["message"]["content"]
  312. # gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,reply_content)
  313. # gewe_chat.wxchat.save_session_messages_to_cache(hash_key, {"role": "assistant", "content": reply_content})
  314. # # 回复的对话
  315. # input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
  316. # input_message=utils.dialogue_message(wxid,callback_to_user,input_wx_content_dialogue_message,True)
  317. # kafka_helper.kafka_client.produce_message(input_message)
  318. # logger.info("发送对话 %s",input_message)
  319. # 创建并启动任务线程,将参数传递给 ai_chat_text 函数
  320. task_thread = threading.Thread(
  321. target=ai_chat_text,
  322. args=(token_id,app_id,wxid,msg_data,msg_content)
  323. )
  324. task_thread.start()
  325. # 设置定时器,1秒后检查任务是否超时。这里需要使用 lambda 来传递参数
  326. timeout_timer = threading.Timer(
  327. timeout_duration,
  328. lambda:check_timeout(task_thread, token_id, wxid,app_id, callback_to_user)
  329. )
  330. timeout_timer.start()
  331. # 等待任务线程完成
  332. task_thread.join()
  333. # 取消定时器
  334. timeout_timer.cancel()
  335. def check_timeout(task_thread:threading.Thread, token_id,wxid, app_id, callback_to_user):
  336. if task_thread.is_alive():
  337. print(f"任务运行时间超过{timeout_duration}秒,token_id={token_id}, app_id={app_id}, callback_to_user={callback_to_user}")
  338. wx_config = gewe_chat.wxchat.get_wxchat_config_from_cache(wxid)
  339. if bool(wx_config.get("chatWaitingMsgEnabled",True)):
  340. gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,"亲,我正在组织回复的信息,请稍等一会")
  341. def ai_chat_text(token_id,app_id,wxid,msg_data,msg_content):
  342. start_time = time.time() # 记录任务开始时间
  343. callback_to_user=msg_data["FromUserName"]["string"]
  344. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
  345. prompt={"role": "user", "content": [{
  346. "type": "text",
  347. "text": msg_content
  348. }]}
  349. messages_to_send=gewe_chat.wxchat.save_session_messages_to_cache(hash_key, prompt)
  350. # 收到的对话
  351. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  352. input_message=utils.dialogue_message(callback_to_user,wxid,input_wx_content_dialogue_message)
  353. kafka_helper.kafka_client.produce_message(input_message)
  354. logger.info("发送对话 %s",input_message)
  355. cache_data = memory.USER_INTERACTIVE_CACHE.get(wxid)
  356. if cache_data and cache_data.get('interactive') :
  357. o=get_first_char_if_digit(msg_content)
  358. if o is not None:
  359. userSelectOptions=cache_data.get('options')
  360. if o < len(userSelectOptions):
  361. o=o-1
  362. msg_content=userSelectOptions[o].get("value")
  363. messages_to_send=[{"role": "user", "content": msg_content}]
  364. else:
  365. messages_to_send=[{"role": "user", "content": msg_content}]
  366. else:
  367. messages_to_send=[{"role": "user", "content": msg_content}]
  368. res=fast_gpt_api(messages_to_send,wxid,callback_to_user)
  369. reply_content=remove_markdown_symbol(res["choices"][0]["message"]["content"])
  370. description = ''
  371. userSelectOptions = []
  372. if isinstance(reply_content, list) and any(item.get("type") == "interactive" for item in reply_content):
  373. for item in reply_content:
  374. if item["type"] == "interactive" and item["interactive"]["type"] == "userSelect":
  375. params = item["interactive"]["params"]
  376. description = params.get("description")
  377. userSelectOptions = params.get("userSelectOptions", [])
  378. values_string = "\n".join(option["value"] for option in userSelectOptions)
  379. if description is not None:
  380. memory.USER_INTERACTIVE_CACHE[wxid] = {
  381. "interactive":True,
  382. "options": userSelectOptions,
  383. }
  384. reply_content=description + '------------------------------\n'+values_string
  385. elif isinstance(reply_content, list) and any(item.get("type") == "text" for item in reply_content):
  386. memory.USER_INTERACTIVE_CACHE[wxid] = {
  387. "interactive":False
  388. }
  389. text=''
  390. for item in reply_content:
  391. if item["type"] == "text":
  392. text=item["text"]["content"]
  393. if text=='':
  394. # 去除上次上一轮对话再次请求
  395. cache_messages_str=redis_helper.redis_helper.get_hash_field(hash_key,"data")
  396. cache_messages = json.loads(cache_messages_str) if cache_messages_str else []
  397. if len(cache_messages) >= 3:
  398. cache_messages = cache_messages[:-3]
  399. redis_helper.redis_helper.update_hash_field(hash_key,"data",json.dumps(cache_messages,ensure_ascii=False))
  400. messages_to_send=gewe_chat.wxchat.save_session_messages_to_cache(hash_key, prompt)
  401. res=fast_gpt_api(messages_to_send,wxid,callback_to_user)
  402. reply_content=remove_markdown_symbol(res["choices"][0]["message"]["content"])
  403. if isinstance(reply_content, list) :
  404. reply_content=remove_markdown_symbol(reply_content[0].get('text').get("content"))
  405. else:
  406. reply_content=text
  407. else:
  408. memory.USER_INTERACTIVE_CACHE[wxid] = {
  409. "interactive":False
  410. }
  411. reply_content=remove_markdown_symbol(res["choices"][0]["message"]["content"])
  412. gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,reply_content)
  413. gewe_chat.wxchat.save_session_messages_to_cache(hash_key, {"role": "assistant", "content": reply_content})
  414. # 回复的对话
  415. input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
  416. input_message=utils.dialogue_message(wxid,callback_to_user,input_wx_content_dialogue_message,True)
  417. kafka_helper.kafka_client.produce_message(input_message)
  418. logger.info("发送对话 %s",input_message)
  419. end_time = time.time() # 记录任务结束时间
  420. execution_time = end_time - start_time # 计算执行时间
  421. logger.info(f"AI回答任务完成,耗时 {execution_time:.2f} 秒")
  422. def handle_text_group(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  423. '''
  424. 群聊文本消息
  425. '''
  426. msg_content=msg_data["Content"]["string"]
  427. msg_push_content=msg_data.get("PushContent")
  428. k,login_info=get_login_info_by_app_id(app_id)
  429. nickname=login_info.get("nickName")
  430. if wxid == from_wxid: #手动发送消息
  431. logger.info("Active message sending detected")
  432. gewe_chat.wxchat.save_contacts_brief_to_cache(token_id,app_id,wxid,[to_wxid])
  433. callback_to_user=msg_data["ToUserName"]["string"]
  434. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  435. input_message=utils.dialogue_message(from_wxid,to_wxid,input_wx_content_dialogue_message)
  436. kafka_helper.kafka_client.produce_message(input_message)
  437. logger.info("发送对话 %s",input_message)
  438. else:
  439. c = gewe_chat.wxchat.get_wxchat_config_from_cache(wxid)
  440. chatroom_id_white_list = c.get("chatroomIdWhiteList", [])
  441. if not chatroom_id_white_list:
  442. logger.info('白名单为空或未定义,不处理')
  443. return
  444. if from_wxid not in chatroom_id_white_list:
  445. logger.info(f'群ID {from_wxid} 不在白名单中,不处理')
  446. return
  447. if '在群聊中@了你' in msg_push_content or '@'+nickname in msg_push_content:
  448. callback_to_user=msg_data["FromUserName"]["string"]
  449. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
  450. prompt={"role": "user", "content": [{
  451. "type": "text",
  452. "text": msg_content
  453. }]}
  454. messages_to_send=gewe_chat.wxchat.save_session_messages_to_cache(hash_key, prompt)
  455. # 收到的对话
  456. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  457. input_message=utils.dialogue_message(callback_to_user,wxid,input_wx_content_dialogue_message)
  458. kafka_helper.kafka_client.produce_message(input_message)
  459. logger.info("发送对话 %s",input_message)
  460. cache_data = memory.USER_INTERACTIVE_CACHE.get(wxid)
  461. if cache_data and cache_data.get('interactive') :
  462. o=get_first_char_if_digit(msg_content)
  463. if o is not None:
  464. userSelectOptions=cache_data.get('options')
  465. if o < len(userSelectOptions):
  466. o=o-1
  467. msg_content=userSelectOptions[o].get("value")
  468. messages_to_send=[{"role": "user", "content": msg_content}]
  469. else:
  470. messages_to_send=[{"role": "user", "content": msg_content}]
  471. else:
  472. messages_to_send=[{"role": "user", "content": msg_content}]
  473. res=fast_gpt_api(messages_to_send,wxid,callback_to_user)
  474. reply_content=remove_markdown_symbol(res["choices"][0]["message"]["content"])
  475. description = ''
  476. userSelectOptions = []
  477. if isinstance(reply_content, list) and any(item.get("type") == "interactive" for item in reply_content):
  478. for item in reply_content:
  479. if item["type"] == "interactive" and item["interactive"]["type"] == "userSelect":
  480. params = item["interactive"]["params"]
  481. description = params.get("description")
  482. userSelectOptions = params.get("userSelectOptions", [])
  483. values_string = "\n".join(option["value"] for option in userSelectOptions)
  484. if description is not None:
  485. memory.USER_INTERACTIVE_CACHE[wxid] = {
  486. "interactive":True,
  487. "options": userSelectOptions,
  488. }
  489. reply_content=description + '------------------------------\n'+values_string
  490. elif isinstance(reply_content, list) and any(item.get("type") == "text" for item in reply_content):
  491. memory.USER_INTERACTIVE_CACHE[wxid] = {
  492. "interactive":False
  493. }
  494. text=''
  495. for item in reply_content:
  496. if item["type"] == "text":
  497. text=item["text"]["content"]
  498. if text=='':
  499. # 去除上次上一轮对话再次请求
  500. cache_messages_str=redis_helper.redis_helper.get_hash_field(hash_key,"data")
  501. cache_messages = json.loads(cache_messages_str) if cache_messages_str else []
  502. if len(cache_messages) >= 3:
  503. cache_messages = cache_messages[:-3]
  504. redis_helper.redis_helper.update_hash_field(hash_key,"data",json.dumps(cache_messages,ensure_ascii=False))
  505. messages_to_send=gewe_chat.wxchat.save_session_messages_to_cache(hash_key, prompt)
  506. res=fast_gpt_api(messages_to_send,wxid,callback_to_user)
  507. reply_content=remove_markdown_symbol(res["choices"][0]["message"]["content"])
  508. else:
  509. reply_content=text
  510. else:
  511. memory.USER_INTERACTIVE_CACHE[wxid] = {
  512. "interactive":False
  513. }
  514. reply_content=res["choices"][0]["message"]["content"]
  515. reply_content='@'+extract_nickname(msg_push_content) + reply_content
  516. gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,reply_content)
  517. gewe_chat.wxchat.save_session_messages_to_cache(hash_key, {"role": "assistant", "content": reply_content})
  518. # 回复的对话
  519. input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
  520. input_message=utils.dialogue_message(wxid,callback_to_user,input_wx_content_dialogue_message,True)
  521. kafka_helper.kafka_client.produce_message(input_message)
  522. logger.info("发送对话 %s",input_message)
  523. else:
  524. logger.info('群聊公开消息')
  525. callback_to_user=msg_data["FromUserName"]["string"]
  526. dialogue_message=[{"type": "text", "text": msg_content}]
  527. input_message=utils.dialogue_message(callback_to_user,wxid,dialogue_message)
  528. kafka_helper.kafka_client.produce_message(input_message)
  529. logger.info("发送对话 %s",input_message)
  530. return
  531. def handle_image(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  532. '''
  533. 图片消息
  534. '''
  535. msg_content=msg_data["Content"]["string"]
  536. callback_to_user=from_wxid
  537. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
  538. wx_img_url=gewe_chat.wxchat.download_image_msg(token_id,app_id,msg_content)
  539. oss_access_key_id="LTAI5tRTG6pLhTpKACJYoPR5"
  540. oss_access_key_secret="E7dMzeeMxq4VQvLg7Tq7uKf3XWpYfN"
  541. oss_endpoint="http://oss-cn-shanghai.aliyuncs.com"
  542. oss_bucket_name="cow-agent"
  543. oss_prefix="cow"
  544. img_url=utils.upload_oss(oss_access_key_id, oss_access_key_secret, oss_endpoint, oss_bucket_name, wx_img_url, oss_prefix)
  545. prompt={
  546. "role": "user",
  547. "content": [{
  548. "type": "image_url",
  549. "image_url": {"url": img_url}
  550. }]
  551. }
  552. gewe_chat.wxchat.save_session_messages_to_cache(hash_key, prompt)
  553. gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,'已经上传了图片,有什么可以为您服务')
  554. logger.info(f"上传图片 URL: {img_url}")
  555. wx_content_dialogue_message=[{"type": "image_url", "image_url": {"url": img_url}}]
  556. input_message=utils.dialogue_message(wxid,callback_to_user,wx_content_dialogue_message)
  557. kafka_helper.kafka_client.produce_message(input_message)
  558. logger.info("发送对话 %s",input_message)
  559. def handle_image_group(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  560. logger.info('群聊图片消息')
  561. def handle_voice(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  562. '''
  563. 语音消息
  564. '''
  565. callback_to_user=from_wxid
  566. msg_content=msg_data["Content"]["string"]
  567. msg_id=msg_data["MsgId"]
  568. file_url=gewe_chat.wxchat.download_audio_msg(token_id,app_id,msg_id,msg_content)
  569. react_silk_path=utils.save_to_local_from_url(file_url)
  570. react_wav_path = os.path.splitext(react_silk_path)[0] + ".wav"
  571. audio_convert.any_to_wav(react_silk_path,react_wav_path)
  572. react_voice_text=AliVoice().voiceToText(react_wav_path)
  573. os.remove(react_silk_path)
  574. os.remove(react_wav_path)
  575. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
  576. messages=gewe_chat.wxchat.save_session_messages_to_cache(hash_key, {"role": "user", "content": react_voice_text})
  577. ai_res=fast_gpt_api(messages,wxid,callback_to_user)
  578. ai_res_content=remove_markdown_symbol(ai_res["choices"][0]["message"]["content"])
  579. has_url=contains_url(ai_res_content)
  580. if not has_url:
  581. voice_during,voice_url=utils.wx_voice(ai_res_content)
  582. if voice_during < 60 * 1000:
  583. ret,ret_msg,res=gewe_chat.wxchat.post_voice(token_id,app_id,callback_to_user,voice_url,voice_during)
  584. else:
  585. ret,ret_msg,res=gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,ai_res_content)
  586. logger.warning(f'回应语音消息长度 {voice_during/1000}秒,超过60秒,转为文本回复')
  587. if ret==200:
  588. logger.info((f'{wxid} 向 {callback_to_user} 发送语音文本【{ai_res_content}】{ret_msg}'))
  589. else:
  590. logger.warning((f'{wxid} 向 {callback_to_user} 发送语音文本【{ai_res_content}】{ret_msg}'))
  591. ret,ret_msg,res=gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,ai_res_content)
  592. logger.info((f'{wxid} 向 {callback_to_user} 发送文本【{ai_res_content}】{ret_msg}'))
  593. else:
  594. logger.info(f"回复内容包含网址,不发送语音,回复文字内容:{ai_res_content}")
  595. ret,ret_msg,res=gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,ai_res_content)
  596. gewe_chat.wxchat.save_session_messages_to_cache(hash_key, {"role": "assistant", "content": ai_res_content})
  597. # 构造对话消息并发送到 Kafka
  598. input_wx_content_dialogue_message = [{"type": "text", "text": ai_res_content}]
  599. input_message = utils.dialogue_message(wxid, callback_to_user, input_wx_content_dialogue_message,True)
  600. kafka_helper.kafka_client.produce_message(input_message)
  601. logger.info("发送对话 %s", input_message)
  602. def handle_voice_group(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  603. logger.info('语音消息')
  604. def handle_xml(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  605. '''
  606. 处理xml
  607. '''
  608. msg_content_xml=msg_data["Content"]["string"]
  609. root = ET.fromstring(msg_content_xml)
  610. type_value = int(root.find(".//appmsg/type").text)
  611. handlers = {
  612. 57: handle_xml_reference,
  613. 5: handle_xml_invite_group
  614. }
  615. handler = handlers.get(type_value)
  616. if handler:
  617. return handler(token_id,app_id, wxid,msg_data,from_wxid, to_wxid)
  618. # elif "邀请你加入了群聊" in msg_content_xml: # 邀请加入群聊
  619. # logger.warning(f"xml消息 {type_value} 邀请你加入了群聊.todo")
  620. else:
  621. print(f"xml消息 {type_value} 未解析")
  622. def handle_xml_reference(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  623. '''
  624. 引用消息
  625. 判断此类消息的逻辑:$.Data.MsgType=49 并且 解析$.Data.Content.string中的xml msg.appmsg.type=57
  626. '''
  627. callback_to_user=from_wxid
  628. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{callback_to_user}'
  629. msg_content= msg_data["PushContent"]
  630. prompt={"role": "user", "content": [{
  631. "type": "text",
  632. "text": msg_content
  633. }]}
  634. # 收到的对话
  635. messages_to_send=gewe_chat.wxchat.save_session_messages_to_cache(hash_key, prompt)
  636. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  637. input_message=utils.dialogue_message(callback_to_user,wxid,input_wx_content_dialogue_message)
  638. kafka_helper.kafka_client.produce_message(input_message)
  639. logger.info("发送对话 %s",input_message)
  640. # 回复的对话
  641. res=fast_gpt_api(messages_to_send,wxid,callback_to_user)
  642. reply_content=remove_markdown_symbol(res["choices"][0]["message"]["content"])
  643. input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
  644. input_message=utils.dialogue_message(wxid,callback_to_user,input_wx_content_dialogue_message,True)
  645. kafka_helper.kafka_client.produce_message(input_message)
  646. logger.info("发送对话 %s",input_message)
  647. gewe_chat.wxchat.save_session_messages_to_cache(hash_key, {"role": "assistant", "content": reply_content})
  648. gewe_chat.wxchat.post_text(token_id,app_id,callback_to_user,reply_content)
  649. def handle_xml_invite_group(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  650. '''
  651. 群聊邀请
  652. 判断此类消息的逻辑:$.Data.MsgType=49
  653. 并且 解析$.Data.Content.string中的xml msg.appmsg.title=邀请你加入群聊(根据手机设置的系统语言title会有调整,不同语言关键字不同)
  654. '''
  655. logger.info(f'{wxid} 群聊邀请')
  656. msg_content_xml=msg_data["Content"]["string"]
  657. root = ET.fromstring(msg_content_xml)
  658. title_value = root.find(".//appmsg/title").text
  659. if '邀请你加入群聊' in title_value:
  660. invite_url = root.find('.//url').text
  661. ret,msg,data=gewe_chat.wxchat.agree_join_room(token_id,app_id,invite_url)
  662. if ret==200:
  663. logger.info(f'群聊邀请,同意加入群聊 {msg} {data}')
  664. chatroom_id=data.get('chatroomId','')
  665. # if not chatroom_id:
  666. # logger.warning(f'群聊邀请,同意加入群聊失败 {msg} {data}')
  667. # return
  668. ret,msg,data=gewe_chat.wxchat.save_contract_list(token_id,app_id,chatroom_id,3)
  669. logger.info(f'群聊邀请,保存到通讯录 chatroom_id {chatroom_id} {msg}')
  670. gewe_chat.wxchat.update_group_info_to_cache(token_id,app_id,wxid,chatroom_id)
  671. gewe_chat.wxchat.update_group_members_to_cache(token_id,app_id,wxid,chatroom_id)
  672. else:
  673. logger.warning(f'群聊邀请,同意加入群聊失败 {msg} {data}')
  674. def handle_add_friend_notice(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  675. '''
  676. 好友添加请求通知
  677. '''
  678. logger.info('好友添加请求通知')
  679. msg_content_xml=msg_data["Content"]["string"]
  680. root = ET.fromstring(msg_content_xml)
  681. msg_content = root.attrib.get('content', None)
  682. v3= root.attrib.get('encryptusername', None)
  683. v4= root.attrib.get('ticket', None)
  684. scene=root.attrib.get('scene', None)
  685. to_contact_wxid=root.attrib.get('fromusername', None)
  686. wxid=msg_data["ToUserName"]["string"]
  687. # 自动同意好友
  688. # print(v3)
  689. # print(v4)
  690. # print(scene)
  691. # print(msg_content)
  692. # 操作类型,2添加好友 3同意好友 4拒绝好友
  693. #option=2
  694. option=3
  695. reply_add_contact_contact="亲,我是你的好友"
  696. ret,ret_msg=gewe_chat.wxchat.add_contacts(token_id,app_id,scene,option,v3,v4,reply_add_contact_contact)
  697. if ret==200:
  698. logger.info('自动添加好友成功')
  699. # 好友发送的文字
  700. hash_key = f'__AI_OPS_WX__:MESSAGES:{wxid}:{to_contact_wxid}'
  701. prompt={"role": "user", "content": [{"type": "text","text": msg_content}]}
  702. messages_to_send=gewe_chat.wxchat.save_session_messages_to_cache(hash_key, prompt)
  703. input_wx_content_dialogue_message=[{"type": "text", "text": msg_content}]
  704. input_message=utils.dialogue_message(to_contact_wxid,wxid,input_wx_content_dialogue_message)
  705. kafka_helper.kafka_client.produce_message(input_message)
  706. logger.info("发送对话 %s",input_message)
  707. callback_to_user=to_contact_wxid
  708. res=fast_gpt_api(messages_to_send,wxid,callback_to_user)
  709. reply_content=remove_markdown_symbol(res["choices"][0]["message"]["content"])
  710. #保存好友信息
  711. gewe_chat.wxchat.save_contacts_brief_to_cache(token_id,app_id, wxid,[to_contact_wxid])
  712. # 保存到缓存
  713. gewe_chat.wxchat.save_session_messages_to_cache(hash_key, {"role": "assistant", "content": reply_content})
  714. # 发送信息
  715. gewe_chat.wxchat.post_text(token_id,app_id, to_contact_wxid,reply_content)
  716. # 发送到kafka
  717. input_wx_content_dialogue_message=[{"type": "text", "text": reply_content}]
  718. input_message=utils.dialogue_message(wxid,to_contact_wxid,input_wx_content_dialogue_message,True)
  719. kafka_helper.kafka_client.produce_message(input_message)
  720. logger.info("发送对话 %s",input_message)
  721. else:
  722. logger.warning("添加好友失败")
  723. def handle_10002_msg(token_id,app_id, wxid,msg_data,from_wxid, to_wxid):
  724. '''
  725. 群聊邀请
  726. 撤回消息
  727. 拍一拍消息
  728. 地理位置
  729. 踢出群聊通知
  730. 解散群聊通知
  731. 发布群公告
  732. '''
  733. msg_content_xml=msg_data["Content"]["string"]
  734. # 群聊邀请
  735. if '邀请你加入了群聊' in msg_content_xml and check_chatroom(msg_data["FromUserName"]["string"]):
  736. chatroom_id=msg_data["FromUserName"]["string"]
  737. ret,msg,data=gewe_chat.wxchat.save_contract_list(token_id,app_id,chatroom_id,3)
  738. logger.info(f'群聊邀请,保存到通讯录 chatroom_id {chatroom_id} {msg}')
  739. gewe_chat.wxchat.update_group_info_to_cache(token_id,app_id,wxid,chatroom_id)
  740. gewe_chat.wxchat.update_group_members_to_cache(token_id,app_id,wxid,chatroom_id)
  741. if '移出了群聊' in msg_content_xml and 'sysmsgtemplate' in msg_content_xml :
  742. chatroom_id=msg_data["FromUserName"]["string"]
  743. ret,msg,data=gewe_chat.wxchat.save_contract_list(token_id,app_id,chatroom_id,2)
  744. logger.info(f'踢出群聊,移除从通讯录 chatroom_id {chatroom_id} {msg}')
  745. redis_helper.redis_helper.delete_hash_field(f'__AI_OPS_WX__:GROUPS_INFO:{wxid}',chatroom_id)
  746. logger.info(f'清除 chatroom_id{chatroom_id} 数据')
  747. if '已解散该群聊' in msg_content_xml and 'sysmsgtemplate' in msg_content_xml :
  748. chatroom_id=msg_data["FromUserName"]["string"]
  749. ret,msg,data=gewe_chat.wxchat.save_contract_list(token_id,app_id,chatroom_id,2)
  750. logger.info(f'解散群聊,移除从通讯录 chatroom_id {chatroom_id} {msg}')
  751. redis_helper.redis_helper.delete_hash_field(f'__AI_OPS_WX__:GROUPS_INFO:{wxid}',chatroom_id)
  752. logger.info(f'清除 chatroom_id{chatroom_id} 数据')
  753. print('撤回消息,拍一拍消息,地理位置')
  754. def handle_mod_contacts(token_id,app_id,wxid,msg_data):
  755. '''
  756. 好友通过验证及好友资料变更的通知消息
  757. '''
  758. logger.info('好友通过验证及好友资料变更的通知消息')
  759. if not check_chatroom(msg_data["UserName"]["string"]):
  760. contact_wxid = msg_data["UserName"]["string"]
  761. # 更新好友信息
  762. # 检查好友关系,不是好友则删除
  763. # ret,msg,check_relation=gewe_chat.wxchat.check_relation(token_id, app_id,[contact_wxid])
  764. # first_item = check_relation[0]
  765. # check_relation_status=first_item.get('relation')
  766. # logger.info(f'{wxid} 好友 {contact_wxid} 关系检查:{check_relation_status}')
  767. # if check_relation_status != 0:
  768. # gewe_chat.wxchat.delete_contacts_brief_from_cache(wxid, [contact_wxid])
  769. # logger.info(f'好友关系异常:{check_relation_status},删除好友 {contact_wxid} 信息')
  770. # else:
  771. # gewe_chat.wxchat.save_contacts_brief_to_cache(token_id, app_id, wxid, [contact_wxid])
  772. ret,msg,contacts_list = gewe_chat.wxchat.fetch_contacts_list(token_id, app_id)
  773. # friend_wxids = contacts_list['friends'][3:] # 可以调整截取范围
  774. # print(friend_wxids)
  775. #friend_wxids.remove('fmessage')
  776. #friend_wxids.remove('weixin')
  777. friend_wxids = [c for c in contacts_list['friends'] if c not in ['fmessage', 'medianote','weixin','weixingongzhong']] # 可以调整截取范围
  778. print(f'{wxid}的好友数量 {len(friend_wxids)}')
  779. gewe_chat.wxchat.save_contacts_brief_to_cache(token_id, app_id, wxid, friend_wxids)
  780. else:
  781. logger.info('群聊好友通过验证及好友资料变更的通知消息')
  782. def get_messages_from_cache(hash_key,object:object)->list:
  783. '''
  784. 对话列表
  785. '''
  786. messages=redis_helper.redis_helper.get_hash(hash_key)
  787. wxid=hash_key.split(':')[-1]
  788. if not messages:
  789. messages=[{"role": "system", "content": ""}]
  790. messages.append(object)
  791. redis_helper.redis_helper.set_hash(hash_key,{"data":json.dumps(messages,ensure_ascii=False)},3600)
  792. else:
  793. messages_str=redis_helper.redis_helper.get_hash_field(hash_key,"data")
  794. messages = json.loads(messages_str) if messages_str else []
  795. #判断是否含有图片
  796. last_message = messages[-1]
  797. content = last_message.get("content", [])
  798. if isinstance(content, list) and content:
  799. last_content_type = content[-1].get("type")
  800. if last_content_type == 'image_url':
  801. content.append(object['content'][0])
  802. messages[-1]['content']=content
  803. else:
  804. messages.append(object)
  805. else:
  806. messages.append(object)
  807. redis_helper.redis_helper.set_hash(hash_key,{"data":json.dumps(messages,ensure_ascii=False)},3600)
  808. return messages
  809. def fast_gpt_api(messages:list,wixd:str,friend_wxid:str):
  810. c=gewe_chat.wxchat.get_wxchat_config_from_cache(wixd)
  811. api_key=c.get('agentTokenId',"sk-jr69ONIehfGKe9JFphuNk4DU5Y5wooHKHhQv7oSnFzVbwCnW65fXO9kvH")
  812. print(f'流程key:{api_key}\n')
  813. #api_key="sk-jr69ONIehfGKe9JFphuNk4DU5Y5wooHKHhQv7oSnFzVbwCnW65fXO9kvH" #测试
  814. #api_key="sk-uJDBdKmJVb2cmfldGOvlIY6Qx0AzqWMPD3lS1IzgQYzHNOXv9SKNI" #开发2
  815. api_url = "http://106.15.182.218:3000/api/v1/chat/completions"
  816. headers = {
  817. "Content-Type": "application/json",
  818. "Authorization": f"Bearer {api_key}"
  819. }
  820. session_id=f'{wixd}-{friend_wxid}'
  821. data={
  822. "model": "",
  823. "messages":messages,
  824. "chatId": session_id,
  825. "detail": True
  826. }
  827. #print(json.dumps(data,ensure_ascii=False))
  828. logger.info("[CHATGPT] 请求={}".format(json.dumps(data, ensure_ascii=False)))
  829. response = requests.post(url=api_url, headers=headers, data=json.dumps(data), timeout=600)
  830. response.raise_for_status()
  831. response_data = response.json()
  832. logger.info("[CHATGPT] 响应={}".format(json.dumps(response_data, separators=(',', ':'),ensure_ascii=False)))
  833. #print(response_data)
  834. return response_data
  835. def get_token_id_by_app_id(app_id: str) -> str:
  836. # 使用 SCAN 避免一次性返回所有的匹配键,逐步扫描
  837. cursor = 0
  838. while True:
  839. cursor, login_keys = redis_helper.redis_helper.client.scan(cursor, match='__AI_OPS_WX__:LOGININFO:*')
  840. # 批量获取所有键的 hash 数据
  841. for k in login_keys:
  842. r = redis_helper.redis_helper.get_hash(k)
  843. if r.get("appId") == app_id:
  844. return r.get("tokenId", "")
  845. # 如果游标为 0,则表示扫描完成
  846. if cursor == 0:
  847. break
  848. return ""
  849. def get_login_info_by_app_id(app_id: str) ->dict:
  850. # 使用 SCAN 避免一次性返回所有的匹配键,逐步扫描
  851. cursor = 0
  852. while True:
  853. cursor, login_keys = redis_helper.redis_helper.client.scan(cursor, match='__AI_OPS_WX__:LOGININFO:*')
  854. # 批量获取所有键的 hash 数据
  855. for k in login_keys:
  856. r = redis_helper.redis_helper.get_hash(k)
  857. if r.get("appId") == app_id:
  858. return k,r
  859. # 如果游标为 0,则表示扫描完成
  860. if cursor == 0:
  861. break
  862. return ""
  863. def contains_url(text):
  864. # 定义检测网址的正则表达式
  865. url_pattern = re.compile(
  866. r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
  867. )
  868. # 检查字符串是否包含网址
  869. return bool(url_pattern.search(text))
  870. def get_first_char_if_digit(s):
  871. if s and s[0].isdigit(): # 判断字符串是否非空且首字符为数字
  872. return int(s[0]) # 返回数字形式
  873. return None # 如果不是数字则返回 None
  874. def remove_at_mention_regex(text):
  875. # 使用正则表达式去掉“在群聊中@了你”
  876. return re.sub(r"在群聊中@了你", "", text)
  877. def extract_nickname(text)->str:
  878. if "在群聊中@了你" in text:
  879. # 如果包含 "在群聊中@了你",提取其前面的名字
  880. match = re.search(r"^(.*?)在群聊中@了你", text)
  881. if match:
  882. return match.group(1).strip()
  883. elif ": @" in text:
  884. # 如果包含 ": @",提取其前面的名字
  885. return text.split(": @")[0].strip()
  886. return ''
  887. def check_chatroom(userName):
  888. pattern = r'^\d+@chatroom$'
  889. if re.match(pattern, userName):
  890. return True
  891. return False
  892. # def remove_markdown_symbol(text: str):
  893. # # 移除markdown格式,目前先移除**
  894. # if not text or not isinstance(text, str):
  895. # return text
  896. # return re.sub(r'\*\*(.*?)\*\*', r'\1', text)
  897. def remove_markdown_symbol(text: str):
  898. # 移除markdown格式,目前先移除**
  899. if not text or not isinstance(text, str):
  900. return text
  901. # 去除加粗、斜体等格式
  902. #text = re.sub(r'\*\*([^*]+)\*\*', r'\1', text) # 去除加粗
  903. text=re.sub(r'\*\*(.*?)\*\*', r'\1', text)
  904. text = re.sub(r'\*([^*]+)\*', r'\1', text) # 去除斜体
  905. text = re.sub(r'__([^_]+)__', r'\1', text) # 去除加粗(下划线)
  906. text = re.sub(r'_(.*?)_', r'\1', text) # 去除斜体(下划线)
  907. # 去除行内代码块
  908. text = re.sub(r'`([^`]+)`', r'\1', text)
  909. # 去除换行符\n,或者多余的空格
  910. #text = re.sub(r'\n+', ' ', text)
  911. # 去除列表编号等
  912. #text = re.sub(r'^\d+\.\s*', '', text, flags=re.MULTILINE)
  913. #text = re.sub('[\\\`\*\_\[\]\#\+\-\!\>]', '', text)
  914. text = re.sub('[\\\`\*\_\[\]\#\+\!\>]', '', text)
  915. print(text)
  916. return text