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.

gewe_service.py 72KB

1 ay önce
1 ay önce
2 hafta önce
1 ay önce
1 ay önce
1 ay önce
1 ay önce
1 ay önce
3 hafta önce
1 ay önce
1 ay önce
1 ay önce
3 hafta önce
1 ay önce
3 hafta önce
1 ay önce
3 hafta önce
1 ay önce
1 ay önce
1 ay önce
2 hafta önce
1 ay önce
2 hafta önce
1 ay önce
3 hafta önce
1 ay önce
1 ay önce
1 ay önce
1 ay önce
1 ay önce
1 ay önce
1 ay önce
1 ay önce
1 ay önce
1 ay önce
2 hafta önce
1 ay önce
3 hafta önce
1 ay önce
3 hafta önce
1 ay önce
1 ay önce
1 ay önce
1 hafta önce
1 ay önce
1 hafta önce
1 hafta önce
1 ay önce
1 ay önce
1 hafta önce
1 ay önce
2 hafta önce
1 ay önce
1 ay önce
1 ay önce
1 hafta önce
1 ay önce
1 hafta önce
1 ay önce
2 hafta önce
1 ay önce
1 hafta önce
1 ay önce
3 hafta önce
1 ay önce
3 hafta önce
1 ay önce
2 hafta önce
1 ay önce
2 hafta önce
1 ay önce
3 hafta önce
2 hafta önce
1 hafta önce
1 ay önce
1 hafta önce
1 ay önce
1 hafta önce
1 hafta önce
1 hafta önce
1 hafta önce
1 ay önce
1 hafta önce
1 hafta önce
1 ay önce
1 hafta önce
1 ay önce
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704
  1. import aiohttp
  2. import asyncio
  3. import json
  4. import base64
  5. import io
  6. import json
  7. import os
  8. import threading
  9. import time,datetime
  10. import uuid
  11. from fastapi import FastAPI, Depends
  12. from common.singleton import singleton
  13. from aiohttp import ClientError
  14. from json.decoder import JSONDecodeError
  15. from common.log import logger
  16. from common.utils import check_chatroom
  17. from model.models import AddGroupContactsHistory
  18. from services.redis_service import RedisService
  19. #@singleton
  20. class GeWeService:
  21. _instance = None
  22. _lock = asyncio.Lock() # 异步锁,确保单例初始化线程安全
  23. def __init__(self,redis_service:RedisService, base_url: str):
  24. if GeWeService._instance is not None:
  25. raise RuntimeError("请使用 get_instance() 获取单例!")
  26. self.base_url = base_url
  27. self.redis_service=redis_service
  28. @classmethod
  29. async def get_instance(cls, app:FastAPI,base_url: str = "http://api.geweapi.com/gewe"):
  30. """
  31. 获取 GeWeChatCom 单例,确保只初始化一次。
  32. """
  33. async with cls._lock: # 确保多个协程不会并发创建多个实例
  34. if cls._instance is None:
  35. cls._instance = cls(app,base_url)
  36. return cls._instance
  37. # _instance = None
  38. # def __new__(cls,app:FastAPI,base_url="http://api.geweapi.com/gewe"):
  39. # if not cls._instance:
  40. # cls._instance = super(GeWeService, cls).__new__(cls)
  41. # cls._instance.client = None
  42. # cls._instance.lock_renewal_thread = None
  43. # return cls._instance
  44. # def __init__(self,app:FastAPI,base_url="http://api.geweapi.com/gewe"):
  45. # if not hasattr(self, 'initialized'):
  46. # #self.kafka_service =kafka_service # 获取 KafkaService 单例
  47. # # self.kafka_service =app.state.kafka_service
  48. # self.redis_service=app.state.redis_service
  49. # self.base_url = base_url
  50. # self.initialized = True
  51. ############################### 登录模块 ###############################
  52. async def check_login_async(self, token_id: str, app_id: str, uuid: str, captch_code: str = ""):
  53. """
  54. 执行登录(步骤3)
  55. 获取到登录二维码后需每间隔5s调用本接口来判断是否登录成功。
  56. """
  57. api_url = f"{self.base_url}/v2/api/login/checkLogin"
  58. headers = {
  59. 'X-GEWE-TOKEN': token_id,
  60. 'Content-Type': 'application/json'
  61. }
  62. data = {"appId": app_id, "uuid": uuid}
  63. if captch_code:
  64. data["captchCode"] = captch_code
  65. async with aiohttp.ClientSession() as session:
  66. async with session.post(api_url, headers=headers, json=data) as response:
  67. response_object = await response.json()
  68. logger.info(f'登录验证码-请求参数 {json.dumps(data)}')
  69. logger.info(f'登录验证码-返回响应 {response_object}')
  70. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  71. async def qrCallback(self, uuid: str, base64_string: str):
  72. """
  73. 处理二维码回调,显示二维码图片,并提供多种二维码访问链接
  74. """
  75. try:
  76. from PIL import Image
  77. base64_string = base64_string.split(',')[1]
  78. img_data = base64.b64decode(base64_string)
  79. img = Image.open(io.BytesIO(img_data))
  80. _thread = threading.Thread(target=img.show, args=("QRCode",))
  81. _thread.setDaemon(True)
  82. _thread.start()
  83. except Exception as e:
  84. logger.error(f"处理二维码回调时发生错误: {e}")
  85. url = f"http://weixin.qq.com/x/{uuid}"
  86. qr_api1 = f"https://api.isoyu.com/qr/?m=1&e=L&p=20&url={url}"
  87. qr_api2 = f"https://api.qrserver.com/v1/create-qr-code/?size=400×400&data={url}"
  88. qr_api3 = f"https://api.pwmqr.com/qrcode/create/?url={url}"
  89. qr_api4 = f"https://my.tv.sohu.com/user/a/wvideo/getQRCode.do?text={url}"
  90. logger.info("您也可以通过以下网站扫描二维码:")
  91. logger.info(f"{qr_api3}\n{qr_api4}\n{qr_api2}\n{qr_api1}")
  92. return [qr_api1, qr_api2, qr_api3, qr_api4]
  93. async def get_login_qr_code_async(self, token_id: str, app_id: str = "", region_id: str = "440000"):
  94. """
  95. 获取登录二维码(步骤2)
  96. 第一次登录时传空,后续登录时需传appId。
  97. """
  98. api_url = f"{self.base_url}/v2/api/login/getLoginQrCode"
  99. headers = {
  100. 'X-GEWE-TOKEN': token_id,
  101. 'Content-Type': 'application/json'
  102. }
  103. data = {
  104. "appId": app_id,
  105. "regionId": region_id
  106. }
  107. async with aiohttp.ClientSession() as session:
  108. async with session.post(api_url, headers=headers, json=data) as response:
  109. response_data = await response.json()
  110. logger.info(f'{token_id} 的登录APP信息:{json.dumps(response_data, separators=(",", ":"), ensure_ascii=False)}')
  111. return response_data.get('data')
  112. ############################### 账号管理 ###############################
  113. async def reconnection(self, token_id, app_id):
  114. '''
  115. 断线重连
  116. 当系统返回账号已离线,但是手机顶部还显示ipad在线,可用此接口尝试重连,若返回错误/失败则必须重新调用步骤一登录
  117. 本接口非常用接口,可忽略
  118. '''
  119. api_url = f"{self.base_url}/v2/api/login/reconnection"
  120. headers = {
  121. 'X-GEWE-TOKEN': token_id,
  122. 'Content-Type': 'application/json'
  123. }
  124. data = {
  125. "appId": app_id
  126. }
  127. async with aiohttp.ClientSession() as session:
  128. async with session.post(api_url, headers=headers, json=data) as response:
  129. response_object = await response.json()
  130. return response_object
  131. async def logout_async(self, token_id, app_id):
  132. '''
  133. 退出
  134. '''
  135. api_url = f"{self.base_url}/v2/api/login/logout"
  136. headers = {
  137. 'X-GEWE-TOKEN': token_id,
  138. 'Content-Type': 'application/json'
  139. }
  140. data = {
  141. "appId": app_id
  142. }
  143. async with aiohttp.ClientSession() as session:
  144. async with session.post(api_url, headers=headers, json=data) as response:
  145. response_data = await response.json()
  146. return response_data.get('data')
  147. async def check_online(self, token_id, app_id):
  148. '''
  149. 检查是否在线
  150. 响应结果的data=true则是在线,反之为离线
  151. '''
  152. api_url = f"{self.base_url}/v2/api/login/checkOnline"
  153. headers = {
  154. 'X-GEWE-TOKEN': token_id,
  155. 'Content-Type': 'application/json'
  156. }
  157. data = {
  158. "appId": app_id
  159. }
  160. async with aiohttp.ClientSession() as session:
  161. async with session.post(api_url, headers=headers, json=data) as response:
  162. response_data = await response.json()
  163. return response_data.get('data')
  164. ############################### 联系人模块 ###############################
  165. async def fetch_contacts_list_async(self, token_id, app_id):
  166. '''
  167. 获取通讯录列表
  168. 本接口为长耗时接口,耗时时间根据好友数量递增,若接口返回超时可通过获取通讯录列表缓存接口获取响应结果
  169. 本接口返回的群聊仅为保存到通讯录中的群聊,若想获取会话列表中的所有群聊,需要通过消息订阅做二次处理。
  170. 原因:当未获取的群有成员在群内发消息的话会有消息回调, 开发者此刻调用获取群详情接口查询群信息入库保存即可,
  171. 比如说手机上三年前不说话的群,侧滑删除了,用户手机上也不会看到被删除的群聊的 ,但是有群成员说了话他会显示,
  172. 原理就是各个终端(Android、IOS、桌面版微信)取得了消息回调,又去获取群详情信息,本地数据库缓存了下来,显示的手机群聊,让用户感知的。
  173. '''
  174. api_url = f"{self.base_url}/v2/api/contacts/fetchContactsList"
  175. headers = {
  176. 'X-GEWE-TOKEN': token_id,
  177. 'Content-Type': 'application/json'
  178. }
  179. data = {
  180. "appId": app_id
  181. }
  182. async with aiohttp.ClientSession() as session:
  183. async with session.post(api_url, headers=headers, json=data) as response:
  184. response_object = await response.json()
  185. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  186. async def fetch_contacts_list_cache_async(self, token_id, app_id):
  187. '''
  188. 获取通讯录列表缓存
  189. 通讯录列表数据缓存10分钟,超时则需要重新调用获取通讯录列表接口
  190. '''
  191. api_url = f"{self.base_url}/v2/api/contacts/fetchContactsListCache"
  192. headers = {
  193. 'X-GEWE-TOKEN': token_id,
  194. 'Content-Type': 'application/json'
  195. }
  196. data = {
  197. "appId": app_id
  198. }
  199. async with aiohttp.ClientSession() as session:
  200. async with session.post(api_url, headers=headers, json=data) as response:
  201. response_data = await response.json()
  202. return response_data.get('data')
  203. async def get_brief_info_async(self, token_id, app_id, wxids: list):
  204. '''
  205. 获取群/好友简要信息
  206. 1<= wxids <=100
  207. '''
  208. api_url = f"{self.base_url}/v2/api/contacts/getBriefInfo"
  209. headers = {
  210. 'X-GEWE-TOKEN': token_id,
  211. 'Content-Type': 'application/json'
  212. }
  213. data = {
  214. "appId": app_id,
  215. "wxids": wxids # list 1<= wxids <=100
  216. }
  217. async with aiohttp.ClientSession() as session:
  218. async with session.post(api_url, headers=headers, json=data) as response:
  219. response_data = await response.json()
  220. return response_data.get('data')
  221. async def get_detail_info_async(self, token_id, app_id, wxids):
  222. '''
  223. 获取群/好友详细信息
  224. 1<= wxids <=20
  225. '''
  226. api_url = f"{self.base_url}/v2/api/contacts/getDetailInfo"
  227. headers = {
  228. 'X-GEWE-TOKEN': token_id,
  229. 'Content-Type': 'application/json'
  230. }
  231. data = {
  232. "appId": app_id,
  233. "wxids": wxids # list 1<= wxids <=20
  234. }
  235. async with aiohttp.ClientSession() as session:
  236. async with session.post(api_url, headers=headers, json=data) as response:
  237. response_data = await response.json()
  238. return response_data.get('data')
  239. async def delete_friend_async(self, token_id, app_id, friend_wxid):
  240. '''
  241. 删除好友
  242. '''
  243. api_url = f"{self.base_url}/v2/api/contacts/deleteFriend"
  244. headers = {
  245. 'X-GEWE-TOKEN': token_id,
  246. 'Content-Type': 'application/json'
  247. }
  248. data = {
  249. "appId": app_id,
  250. "wxid": friend_wxid
  251. }
  252. async with aiohttp.ClientSession() as session:
  253. async with session.post(api_url, headers=headers, json=data) as response:
  254. response_object = await response.json()
  255. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  256. async def set_friend_remark(self, token_id, app_id, friend_wxid, remark):
  257. '''
  258. 设置好友备注
  259. '''
  260. api_url = f"{self.base_url}/v2/api/contacts/setFriendRemark"
  261. headers = {
  262. 'X-GEWE-TOKEN': token_id,
  263. 'Content-Type': 'application/json'
  264. }
  265. data = {
  266. "appId": app_id,
  267. "wxid": friend_wxid,
  268. "remark": remark
  269. }
  270. async with aiohttp.ClientSession() as session:
  271. async with session.post(api_url, headers=headers, json=data) as response:
  272. response_object = await response.json()
  273. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  274. ############################### 消息模块 ###############################
  275. async def post_text_async(self, token_id, app_id, to_wxid, content):
  276. api_url = f"{self.base_url}/v2/api/message/postText"
  277. headers = {
  278. 'X-GEWE-TOKEN': token_id,
  279. 'Content-Type': 'application/json'
  280. }
  281. data = {
  282. "appId": app_id,
  283. "toWxid": to_wxid,
  284. "content": content
  285. }
  286. async with aiohttp.ClientSession() as session:
  287. async with session.post(api_url, headers=headers, json=data) as response:
  288. response_object = await response.json()
  289. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  290. async def post_image_async(self, token_id, app_id, to_wxid, img_url):
  291. api_url = f"{self.base_url}/v2/api/message/postImage"
  292. headers = {
  293. 'X-GEWE-TOKEN': token_id,
  294. 'Content-Type': 'application/json'
  295. }
  296. data = {
  297. "appId": app_id,
  298. "toWxid": to_wxid,
  299. "imgUrl": img_url
  300. }
  301. async with aiohttp.ClientSession() as session:
  302. async with session.post(api_url, headers= headers, json=data) as response:
  303. response_object = await response.json()
  304. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  305. async def post_voice_async(self, token_id, app_id, to_wxid, voice_url, voice_duration):
  306. api_url = f"{self.base_url}/v2/api/message/postVoice"
  307. headers = {
  308. 'X-GEWE-TOKEN': token_id,
  309. 'Content-Type': 'application/json'
  310. }
  311. data = {
  312. "appId": app_id,
  313. "toWxid": to_wxid,
  314. "voiceUrl": voice_url,
  315. "voiceDuration": voice_duration
  316. }
  317. async with aiohttp.ClientSession() as session:
  318. async with session.post(api_url, headers=headers, json=data) as response:
  319. response_object = await response.json()
  320. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  321. async def post_video_async(self, token_id, app_id, to_wxid, video_url, video_thumb_url, video_duration):
  322. api_url = f"{self.base_url}/v2/api/message/postVideo"
  323. headers = {
  324. 'X-GEWE-TOKEN': token_id,
  325. 'Content-Type': 'application/json'
  326. }
  327. data = {
  328. "appId": app_id,
  329. "toWxid": to_wxid,
  330. "videoUrl": video_url,
  331. "videoDuration": video_duration,
  332. "thumbUrl": video_thumb_url
  333. }
  334. async with aiohttp.ClientSession() as session:
  335. async with session.post(api_url, headers=headers, json=data) as response:
  336. response_object = await response.json()
  337. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  338. async def post_mini_app_aysnc(self, token_id, app_id,to_wxid, mini_app_id,display_name,page_path,cover_img_url,title,user_name):
  339. '''
  340. 发送小程序消息
  341. '''
  342. api_url = f"{self.base_url}/v2/api/message/postMiniApp"
  343. headers = {
  344. 'X-GEWE-TOKEN': token_id,
  345. 'Content-Type': 'application/json'
  346. }
  347. data = {
  348. "appId": app_id,
  349. "toWxid": to_wxid,
  350. "miniAppId": mini_app_id,
  351. "displayName": display_name,
  352. "pagePath": page_path,
  353. "coverImgUrl": cover_img_url,
  354. "title": title,
  355. "userName": user_name
  356. }
  357. async with aiohttp.ClientSession() as session:
  358. async with session.post(api_url, headers=headers, json=data) as response:
  359. response_object = await response.json()
  360. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  361. async def post_link_async(self,token_id, app_id,to_wxid, title,desc,link_url,thumb_url):
  362. '''
  363. 发送链接消息
  364. '''
  365. api_url = f"{self.base_url}/v2/api/message/postLink"
  366. headers = {
  367. 'X-GEWE-TOKEN': token_id,
  368. 'Content-Type': 'application/json'
  369. }
  370. data = {
  371. "appId": app_id,
  372. "toWxid": to_wxid,
  373. "title": title,
  374. "desc": desc,
  375. "linkUrl": link_url,
  376. "thumbUrl": thumb_url
  377. }
  378. async with aiohttp.ClientSession() as session:
  379. async with session.post(api_url, headers=headers, json=data) as response:
  380. response_object = await response.json()
  381. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  382. async def post_name_card_async(self,token_id, app_id,to_wxid,nickName,nameCard_wxid):
  383. '''
  384. 发送名片消息
  385. '''
  386. api_url = f"{self.base_url}/v2/api/message/postNameCard"
  387. headers = {
  388. 'X-GEWE-TOKEN': token_id,
  389. 'Content-Type': 'application/json'
  390. }
  391. data = {
  392. "appId": app_id,
  393. "toWxid": to_wxid,
  394. "nickName": nickName,
  395. "nameCardWxid": nameCard_wxid
  396. }
  397. async with aiohttp.ClientSession() as session:
  398. async with session.post(api_url, headers=headers, json=data) as response:
  399. response_object = await response.json()
  400. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  401. # 发送emoji消息
  402. async def post_emoji_async(self,token_id, app_id,to_wxid,emoji_md5,emoji_size):
  403. api_url = f"{self.base_url}/v2/api/message/postEmoji"
  404. headers = {
  405. 'X-GEWE-TOKEN': token_id,
  406. 'Content-Type': 'application/json'
  407. }
  408. data = {
  409. "appId": app_id,
  410. "toWxid": to_wxid,
  411. "emojiMd5": emoji_md5,
  412. "emojiSize": emoji_size
  413. }
  414. async with aiohttp.ClientSession() as session:
  415. async with session.post(api_url, headers=headers, json=data) as response:
  416. response_object = await response.json()
  417. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  418. # 发送appmsg消息
  419. async def post_app_msg_async(self,token_id, app_id,to_wxid,appmsg):
  420. '''
  421. 本接口可用于发送所有包含节点的消息,例如:音乐分享、视频号、引用消息等等
  422. '''
  423. api_url = f"{self.base_url}/v2/api/message/postAppMsg"
  424. headers = {
  425. 'X-GEWE-TOKEN': token_id,
  426. 'Content-Type': 'application/json'
  427. }
  428. data = {
  429. "appId": app_id,
  430. "toWxid": to_wxid,
  431. "appmsg": appmsg
  432. }
  433. async with aiohttp.ClientSession() as session:
  434. async with session.post(api_url, headers=headers, json=data) as response:
  435. response_object = await response.json()
  436. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  437. async def forward_image_async(self, token_id, app_id, to_wxid, aeskey, cdnthumburl, cdnthumblength, cdnthumbheight, cdnthumbwidth, length, md5):
  438. api_url = f"{self.base_url}/v2/api/message/forwardImage"
  439. headers = {
  440. 'X-GEWE-TOKEN': token_id,
  441. 'Content-Type': 'application/json'
  442. }
  443. data = {
  444. "appId": app_id,
  445. "toWxid": to_wxid,
  446. "xml": f"<?xml version=\"1.0\"?>\n<msg>\n\t<img aeskey=\"{aeskey}\" encryver=\"1\" cdnthumbaeskey=\"{aeskey}\" cdnthumburl=\"{cdnthumburl}\" cdnthumblength=\"{cdnthumblength}\" cdnthumbheight=\"{cdnthumbheight}\" cdnthumbwidth=\"{cdnthumbwidth}\" cdnmidheight=\"0\" cdnmidwidth=\"0\" cdnhdheight=\"0\" cdnhdwidth=\"0\" cdnmidimgurl=\"{cdnthumburl}\" length=\"{length}\" md5=\"{md5}\" />\n\t<platform_signature></platform_signature>\n\t<imgdatahash></imgdatahash>\n</msg>"
  447. }
  448. async with aiohttp.ClientSession() as session:
  449. async with session.post(api_url, headers=headers, json=data) as response:
  450. response_object = await response.json()
  451. return response_object.get('data', None), response_object.get('ret', None), response_object.get('msg', None)
  452. async def forward_video_async(self, token_id, app_id, to_wxid, aeskey, cdnvideourl, length,video_duration):
  453. api_url = f"{self.base_url}/v2/api/message/forwardVideo"
  454. headers = {
  455. 'X-GEWE-TOKEN': token_id,
  456. 'Content-Type': 'application/json'
  457. }
  458. data = {
  459. "appId": app_id,
  460. "toWxid": to_wxid,
  461. "xml": f"<?xml version=\"1.0\"?>\n<msg>\n\t<videomsg aeskey=\"{aeskey}\" cdnvideourl=\"{cdnvideourl}\" cdnthumbaeskey=\"{aeskey}\" cdnthumburl=\"{cdnvideourl}\" length=\"{length}\" playlength=\"{video_duration}\" cdnthumblength=\"8192\" cdnthumbwidth=\"135\" cdnthumbheight=\"240\" fromusername=\"zhangchuan2288\" md5=\"8804c121e9db91dd844f7a34035beb88\" newmd5=\"\" isplaceholder=\"0\" rawmd5=\"\" rawlength=\"0\" cdnrawvideourl=\"\" cdnrawvideoaeskey=\"\" overwritenewmsgid=\"0\" originsourcemd5=\"\" isad=\"0\" />\n</msg>"
  462. }
  463. async with aiohttp.ClientSession() as session:
  464. async with session.post(api_url, headers=headers, json=data) as response:
  465. response_object = await response.json()
  466. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  467. # 转发文件
  468. async def forward_file_async(self, token_id, app_id, to_wxid, xml):
  469. api_url = f"{self.base_url}/v2/api/message/forwardFile"
  470. headers = {
  471. 'X-GEWE-TOKEN': token_id,
  472. 'Content-Type': 'application/json'
  473. }
  474. data = {
  475. "appId": app_id,
  476. "toWxid": to_wxid,
  477. "xml": xml
  478. }
  479. async with aiohttp.ClientSession() as session:
  480. async with session.post(api_url, headers=headers, json=data) as response:
  481. response_object = await response.json()
  482. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  483. # 转发链接
  484. async def forward_link_async(self, token_id, app_id, to_wxid, xml):
  485. api_url = f"{self.base_url}/v2/api/message/forwardLink"
  486. headers = {
  487. 'X-GEWE-TOKEN': token_id,
  488. 'Content-Type': 'application/json'
  489. }
  490. data = {
  491. "appId": app_id,
  492. "toWxid": to_wxid,
  493. "xml": xml
  494. }
  495. async with aiohttp.ClientSession() as session:
  496. async with session.post(api_url, headers, json=data) as response:
  497. response_object = await response.json()
  498. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  499. # 转发小程序
  500. async def forward_mini_app_async(self, token_id, app_id, to_wxid, xml,cover_img_url):
  501. api_url = f"{self.base_url}/v2/api/message/forwardMiniApp"
  502. headers = {
  503. 'X-GEWE-TOKEN': token_id,
  504. 'Content-Type': 'application/json'
  505. }
  506. data = {
  507. "appId": app_id,
  508. "toWxid": to_wxid,
  509. "xml": xml,
  510. "coverImgUrl":cover_img_url
  511. }
  512. async with aiohttp.ClientSession() as session:
  513. async with session.post(api_url, headers=headers, json=data) as response:
  514. response_object = await response.json()
  515. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  516. async def add_contacts_async(self, token_id: str, app_id: str, scene: int, option: int, v3: str, v4: str, content: str):
  517. api_url = f"{self.base_url}/v2/api/contacts/addContacts"
  518. headers = {
  519. 'X-GEWE-TOKEN': token_id,
  520. 'Content-Type': 'application/json'
  521. }
  522. data = {
  523. "appId": app_id,
  524. "scene": scene,
  525. "option": option,
  526. "v3": v3,
  527. "v4": v4,
  528. "content": content
  529. }
  530. async with aiohttp.ClientSession() as session:
  531. async with session.post(api_url, headers=headers, json=data) as response:
  532. response_object = await response.json()
  533. return response_object.get('ret', None), response_object.get('msg', None)
  534. async def check_relation(self, token_id, app_id, wxids: list):
  535. '''
  536. 检查好友关系
  537. '''
  538. api_url = f"{self.base_url}/v2/api/contacts/checkRelation"
  539. headers = {
  540. 'X-GEWE-TOKEN': token_id,
  541. 'Content-Type': 'application/json'
  542. }
  543. data = {
  544. "appId": app_id,
  545. "wxids": wxids # list 1<= wxids <=20
  546. }
  547. async with aiohttp.ClientSession() as session:
  548. async with session.post(api_url, headers=headers, json=data) as response:
  549. response_object = await response.json()
  550. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  551. ############################### 下载模块 ###############################
  552. async def download_audio_msg_async(self, token_id: str, app_id: str, msg_id: int, xml: str):
  553. data = {
  554. "appId": app_id,
  555. "msgId": msg_id,
  556. "xml": xml
  557. }
  558. headers = {
  559. 'X-GEWE-TOKEN': token_id,
  560. 'Content-Type': 'application/json'
  561. }
  562. url = 'http://api.geweapi.com/gewe/v2/api/message/downloadVoice'
  563. async with aiohttp.ClientSession() as session:
  564. async with session.post(url, json=data, headers=headers) as response:
  565. if response.ok:
  566. data = await response.json()
  567. if data['ret'] == 200:
  568. return data['data']['fileUrl']
  569. else:
  570. return False
  571. else:
  572. return False
  573. async def download_image_msg_async(self, token_id: str, app_id: str, xml: str):
  574. data = {
  575. "appId": app_id,
  576. "type": 2,
  577. "xml": xml
  578. }
  579. headers = {
  580. 'X-GEWE-TOKEN': token_id,
  581. 'Content-Type': 'application/json'
  582. }
  583. async with aiohttp.ClientSession() as session:
  584. async with session.post(f"{self.base_url}/v2/api/message/downloadImage", json=data, headers=headers) as response:
  585. if response.ok:
  586. data = await response.json()
  587. response_object = await response.json()
  588. if data['ret'] == 200:
  589. return data['data']['fileUrl']
  590. else:
  591. return False
  592. else:
  593. return False
  594. async def download_cdn_msg_async(self, token_id:str,aeskey: str, file_id: str, type: str,total_size:str,suffix:str):
  595. api_url = f"{self.base_url}/v2/api/message/downloadCdn"
  596. data = {
  597. "aeskey": aeskey,
  598. "fileId":file_id,
  599. "type": type, #下载的文件类型 1:高清图片 2:常规图片 3:缩略图 4:视频 5:文件
  600. "totalSize":total_size, #文件大小
  601. "suffix": suffix #下载类型为文件时,传文件的后缀(例:doc)
  602. }
  603. headers = {
  604. 'X-GEWE-TOKEN': token_id,
  605. 'Content-Type': 'application/json'
  606. }
  607. async with aiohttp.ClientSession() as session:
  608. async with session.post(api_url, headers=headers, json=data) as response:
  609. response_object = await response.json()
  610. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  611. async def download_audio_file(self, fileUrl: str, file_name: str):
  612. local_filename = f'./silk/{file_name}.silk'
  613. async with aiohttp.ClientSession() as session:
  614. async with session.get(fileUrl) as response:
  615. if response.status == 200:
  616. with open(local_filename, 'wb') as f:
  617. while True:
  618. chunk = await response.content.read(1024)
  619. if not chunk:
  620. break
  621. f.write(chunk)
  622. return local_filename
  623. else:
  624. return None
  625. ############################### 群模块 ###############################
  626. async def get_chatroom_info_async(self, token_id, app_id, chatroom_id):
  627. '''
  628. 获取群信息
  629. '''
  630. api_url = f"{self.base_url}/v2/api/group/getChatroomInfo"
  631. headers = {
  632. 'X-GEWE-TOKEN': token_id,
  633. 'Content-Type': 'application/json'
  634. }
  635. data = {
  636. "appId": app_id,
  637. "chatroomId": chatroom_id
  638. }
  639. async with aiohttp.ClientSession() as session:
  640. async with session.post(api_url, headers=headers, json=data) as response:
  641. response_object = await response.json()
  642. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  643. async def add_group_member_as_friend_async(self, token_id, app_id, chatroom_id, member_wxid, content):
  644. '''
  645. 添加群成员为好友
  646. '''
  647. api_url = f"{self.base_url}/v2/api/group/addGroupMemberAsFriend"
  648. headers = {
  649. 'X-GEWE-TOKEN': token_id,
  650. 'Content-Type': 'application/json'
  651. }
  652. data = {
  653. "appId": app_id,
  654. "chatroomId": chatroom_id,
  655. "content": content,
  656. "memberWxid": member_wxid,
  657. }
  658. async with aiohttp.ClientSession() as session:
  659. async with session.post(api_url, headers=headers, json=data) as response:
  660. response_object = await response.json()
  661. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  662. async def save_contract_list_async(self, token_id, app_id, chatroom_id, oper_type):
  663. '''
  664. 群保存到通讯录
  665. 操作类型 3保存到通讯录 2从通讯录移除
  666. '''
  667. api_url = f"{self.base_url}/v2/api/group/saveContractList"
  668. headers = {
  669. 'X-GEWE-TOKEN': token_id,
  670. 'Content-Type': 'application/json'
  671. }
  672. data = {
  673. "appId": app_id,
  674. "chatroomId": chatroom_id,
  675. "operType": oper_type
  676. }
  677. async with aiohttp.ClientSession() as session:
  678. async with session.post(api_url, headers=headers, json=data) as response:
  679. response_object = await response.json()
  680. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  681. async def get_group_memberlist_async(self, token_id, app_id, chatroom_id):
  682. '''
  683. 获取群成员列表
  684. '''
  685. api_url = f"{self.base_url}/v2/api/group/getChatroomMemberList"
  686. headers = {
  687. 'X-GEWE-TOKEN': token_id,
  688. 'Content-Type': 'application/json'
  689. }
  690. data = {
  691. "appId": app_id,
  692. "chatroomId": chatroom_id,
  693. }
  694. async with aiohttp.ClientSession() as session:
  695. async with session.post(api_url, headers=headers, json=data) as response:
  696. response_object = await response.json()
  697. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  698. async def agree_join_room_async(self, token_id, app_id, url):
  699. '''
  700. 同意入群
  701. '''
  702. api_url = f"{self.base_url}/v2/api/group/agreeJoinRoom"
  703. headers = {
  704. 'X-GEWE-TOKEN': token_id,
  705. 'Content-Type': 'application/json'
  706. }
  707. data = {
  708. "appId": app_id,
  709. "url": url,
  710. }
  711. async with aiohttp.ClientSession() as session:
  712. async with session.post(api_url, headers=headers, json=data) as response:
  713. response_object = await response.json()
  714. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  715. ############################### 个人模块 ###############################
  716. async def get_profile_async(self, token_id, app_id):
  717. api_url = f"{self.base_url}/v2/api/personal/getProfile"
  718. headers = {
  719. 'X-GEWE-TOKEN': token_id,
  720. 'Content-Type': 'application/json'
  721. }
  722. data = {
  723. "appId": app_id,
  724. }
  725. async with aiohttp.ClientSession() as session:
  726. async with session.post(api_url, headers=headers, json=data) as response:
  727. response_object = await response.json()
  728. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  729. ############################### 朋友圈模块 ###################################
  730. # 在新设备登录后的1-3天内,您将无法使用朋友圈发布、点赞、评论等功能。在此期间,如果尝试进行这些操作,您将收到来自微信团队的提醒。请注意遵守相关规定。
  731. async def sns_visible_scope(self, token_id, app_id, option):
  732. '''
  733. 朋友圈可见范围 option 可选项
  734. 1:全部
  735. 2:最近半年
  736. 3:最近一个月
  737. 4:最近三天
  738. '''
  739. api_url = f"{self.base_url}/v2/api/sns/snsVisibleScope"
  740. headers = {
  741. 'X-GEWE-TOKEN': token_id,
  742. 'Content-Type': 'application/json'
  743. }
  744. data = {
  745. "appId": app_id,
  746. "option": option,
  747. }
  748. async with aiohttp.ClientSession() as session:
  749. async with session.post(api_url, headers=headers, json=data) as response:
  750. response_object = await response.json()
  751. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  752. async def stranger_visibility_enabled(self, token_id, app_id, enabled: bool):
  753. '''
  754. 是否允许陌生人查看朋友圈
  755. '''
  756. api_url = f"{self.base_url}/v2/api/sns/strangerVisibilityEnabled"
  757. headers = {
  758. 'X-GEWE-TOKEN': token_id,
  759. 'Content-Type': 'application/json'
  760. }
  761. data = {
  762. "appId": app_id,
  763. "enabled": enabled
  764. }
  765. async with aiohttp.ClientSession() as session:
  766. async with session.post(api_url, headers=headers, json=data) as response:
  767. response_object = await response.json()
  768. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  769. async def send_text_sns_async(self, token_id, app_id, content):
  770. '''
  771. 发送文字朋友圈
  772. '''
  773. api_url = f"{self.base_url}/v2/api/sns/sendTextSns"
  774. headers = {
  775. 'X-GEWE-TOKEN': token_id,
  776. 'Content-Type': 'application/json'
  777. }
  778. data = {
  779. "appId": app_id,
  780. "content": content
  781. }
  782. async with aiohttp.ClientSession() as session:
  783. async with session.post(api_url, headers=headers, json=data) as response:
  784. response_object = await response.json()
  785. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  786. async def send_image_sns_async(self, token_id, app_id, content, img_infos: list):
  787. '''
  788. 发送图片朋友圈
  789. '''
  790. api_url = f"{self.base_url}/v2/api/sns/sendImgSns"
  791. headers = {
  792. 'X-GEWE-TOKEN': token_id,
  793. 'Content-Type': 'application/json'
  794. }
  795. data = {
  796. "appId": app_id,
  797. "allowWxIds": [],
  798. "atWxIds": [],
  799. "disableWxIds": [],
  800. "content": content,
  801. "imgInfos": img_infos, # 通过上传朋友圈图片接口获取
  802. "privacy": False
  803. }
  804. async with aiohttp.ClientSession() as session:
  805. async with session.post(api_url, headers=headers, json=data) as response:
  806. response_object = await response.json()
  807. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  808. async def upload_sns_image_async(self, token_id, app_id, img_urls: list):
  809. '''
  810. 上传朋友圈图片
  811. '''
  812. api_url = f"{self.base_url}/v2/api/sns/uploadSnsImage"
  813. headers = {
  814. 'X-GEWE-TOKEN': token_id,
  815. 'Content-Type': 'application/json'
  816. }
  817. data = {
  818. "appId": app_id,
  819. "imgUrls": img_urls
  820. }
  821. async with aiohttp.ClientSession() as session:
  822. async with session.post(api_url, headers=headers, json=data) as response:
  823. response_object = await response.json()
  824. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  825. async def upload_send_image_sns_async(self, token_id, app_id, content, img_infos: list):
  826. '''
  827. 上传并发送图片朋友圈
  828. '''
  829. ret, msg, data = await self.upload_sns_image_async(token_id, app_id, img_infos)
  830. if ret != 200:
  831. logger.warning(f'上传图片失败 {ret} {msg} {data}')
  832. return ret, msg, data
  833. ret, msg, data = await self.send_image_sns_async(token_id, app_id, content, data)
  834. if ret != 200:
  835. logger.warning(f'发送图片失败 {ret} {msg} {data}')
  836. return ret, msg, data
  837. async def send_video_sns_async(self, token_id, app_id, content: str, video_info: dict):
  838. '''
  839. 发送视频朋友圈
  840. '''
  841. api_url = f"{self.base_url}/v2/api/sns/sendVideoSns"
  842. headers = {
  843. 'X-GEWE-TOKEN': token_id,
  844. 'Content-Type': 'application/json'
  845. }
  846. data = {
  847. "appId": app_id,
  848. "content": content,
  849. "allowWxIds": [],
  850. "atWxIds": [],
  851. "disableWxIds": [],
  852. "videoInfo": video_info,
  853. "privacy": False
  854. }
  855. async with aiohttp.ClientSession() as session:
  856. async with session.post(api_url, headers=headers, json=data) as response:
  857. response_object = await response.json()
  858. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  859. async def upload_sns_video_async(self, token_id, app_id, video_url: str, video_thumb_url: str):
  860. '''
  861. 上传朋友圈视频
  862. '''
  863. api_url = f"{self.base_url}/v2/api/sns/uploadSnsVideo"
  864. headers = {
  865. 'X-GEWE-TOKEN': token_id,
  866. 'Content-Type': 'application/json'
  867. }
  868. data = {
  869. "appId": app_id,
  870. "thumbUrl": video_thumb_url,
  871. "videoUrl": video_url,
  872. }
  873. async with aiohttp.ClientSession() as session:
  874. async with session.post(api_url, headers=headers, json=data) as response:
  875. response_object = await response.json()
  876. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  877. async def upload_send_video_sns_async(self, token_id, app_id, content: str, video_url: str, video_thumb_url: str):
  878. '''
  879. 上传并发送视频朋友圈
  880. '''
  881. ret, msg, data = self.upload_sns_video_async(token_id, app_id, video_url, video_thumb_url)
  882. if ret != 200:
  883. logger.warning(f'上传视频失败 {ret} {msg} {data}')
  884. return ret, msg, data
  885. ret,msg,data=self.send_video_sns_async(token_id, app_id, content, data)
  886. if ret != 200:
  887. logger.warning(f'发送视频失败 {ret} {msg} {data}')
  888. return ret,msg,data
  889. async def get_sns_list_async(self, token_id, app_id, sns_id: str):
  890. api_url = f"{self.base_url}/v2/api/sns/snsList"
  891. headers = {
  892. 'X-GEWE-TOKEN': token_id,
  893. 'Content-Type': 'application/json'
  894. }
  895. data = {
  896. "appId": app_id,
  897. "snsId": sns_id,
  898. }
  899. async with aiohttp.ClientSession() as session:
  900. async with session.post(api_url, headers=headers, json=data) as response:
  901. response_object = await response.json()
  902. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  903. ############################### 标签模块 ###############################
  904. async def label_add_async(self, token_id, app_id, label_name):
  905. api_url = f"{self.base_url}/v2/api/label/add"
  906. headers = {
  907. 'X-GEWE-TOKEN': token_id,
  908. 'Content-Type': 'application/json'
  909. }
  910. data = {
  911. "appId": app_id,
  912. "labelName": label_name,
  913. }
  914. async with aiohttp.ClientSession() as session:
  915. async with session.post(api_url, headers=headers, json=data) as response:
  916. response_object = await response.json()
  917. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  918. async def label_delete_async(self, token_id, app_id, label_ids: list):
  919. api_url = f"{self.base_url}/v2/api/label/delete"
  920. label_ids_str = ','.join(map(str, label_ids))
  921. headers = {
  922. 'X-GEWE-TOKEN': token_id,
  923. 'Content-Type': 'application/json'
  924. }
  925. data = {
  926. "appId": app_id,
  927. "labelIds": label_ids_str,
  928. }
  929. async with aiohttp.ClientSession() as session:
  930. async with session.post(api_url, headers=headers, json=data) as response:
  931. response_object = await response.json()
  932. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  933. async def label_list_async(self, token_id, app_id):
  934. api_url = f"{self.base_url}/v2/api/label/list"
  935. headers = {
  936. 'X-GEWE-TOKEN': token_id,
  937. 'Content-Type': 'application/json'
  938. }
  939. data = {
  940. "appId": app_id,
  941. }
  942. try:
  943. async with aiohttp.ClientSession() as session:
  944. try:
  945. async with session.post(api_url, headers=headers, json=data) as response:
  946. # 检查响应状态码
  947. if response.status != 200:
  948. return response.status, f"HTTP Error: {response.status}", None
  949. try:
  950. response_object = await response.json()
  951. except JSONDecodeError:
  952. return 501, "Invalid JSON response", None
  953. # 检查返回的数据结构
  954. if not isinstance(response_object, dict):
  955. return 501, "Invalid response format", None
  956. # 返回处理后的数据
  957. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  958. except ClientError as e:
  959. return 501, f"Network error: {str(e)}", None
  960. except Exception as e:
  961. return None, f"Unexpected error: {str(e)}", None
  962. async def label_modify_members_async(self, token_id, app_id,label_ids: list,wx_ids: list):
  963. '''
  964. 注意
  965. 由于好友标签信息存储在用户客户端,因此每次在修改时都需要进行全量修改。举例来说,考虑好友A(wxid_asdfaihp123),该好友已经被标记为标签ID为1和2。
  966. 在添加标签ID为3时,传递的参数如下:labelIds:1,2,3,wxIds:[wxid_asdfaihp123]。这表示要给好友A添加标签ID为3,同时保留已有的标签ID 1和2。
  967. 而在删除标签ID为1时,传递的参数如下:labelIds:2,3 ,wxIds:[wxid_asdfaihp123]。这表示要将好友A的标签ID 1删除,而保留标签ID 2。
  968. '''
  969. api_url = f"{self.base_url}/v2/api/label/modifyMemberList"
  970. label_ids_str = ','.join(map(str, label_ids))
  971. headers = {
  972. 'X-GEWE-TOKEN': token_id,
  973. 'Content-Type': 'application/json'
  974. }
  975. data = {
  976. "appId": app_id,
  977. "wxIds": wx_ids,
  978. "labelIds": label_ids_str
  979. }
  980. async with aiohttp.ClientSession() as session:
  981. async with session.post(api_url, headers=headers, json=data) as response:
  982. response_object = await response.json()
  983. return response_object.get('ret', None), response_object.get('msg', None), response_object.get('data', None)
  984. ############################### 其他 ###############################
  985. async def save_session_messages_to_cache_async(self, hash_key,item:object)->list:
  986. '''
  987. 对话列表
  988. '''
  989. messages = await self.redis_service.get_hash(hash_key)
  990. wxid=hash_key.split(':')[-1]
  991. if not messages:
  992. messages=[{"role": "system", "content": ""}]
  993. messages.append(item)
  994. await self.redis_service.set_hash(hash_key, {"data": json.dumps(messages, ensure_ascii=False)}, 600)
  995. else:
  996. messages_str = await self.redis_service.get_hash_field(hash_key, "data")
  997. messages = json.loads(messages_str) if messages_str else []
  998. #判断是否含有图片
  999. last_message = messages[-1]
  1000. content = last_message.get("content", [])
  1001. if isinstance(content, list) and content:
  1002. last_content_type = content[-1].get("type")
  1003. if last_content_type == 'image_url':
  1004. content.append(item['content'][0])
  1005. messages[-1]['content']=content
  1006. else:
  1007. messages.append(item)
  1008. else:
  1009. if last_message!= item:
  1010. messages.append(item)
  1011. await self.redis_service.set_hash(hash_key,{"data":json.dumps(messages,ensure_ascii=False)},600)
  1012. return messages
  1013. async def get_contacts_brief_from_cache_async(self, wxid)->list:
  1014. """
  1015. 获取联系人信息保存到 Redis 缓存。
  1016. """
  1017. hash_key = f"__AI_OPS_WX__:CONTACTS_BRIEF:{wxid}"
  1018. cache_str = await self.redis_service.get_hash_field(hash_key, "data")
  1019. return json.loads(cache_str) if cache_str else []
  1020. async def save_contacts_brief_to_cache_async(self, token_id, app_id, wxid, contacts_wxids: list)->list:
  1021. """
  1022. 将联系人信息保存到 Redis 缓存。
  1023. """
  1024. # Redis 缓存的 key
  1025. hash_key = f"__AI_OPS_WX__:CONTACTS_BRIEF:{wxid}"
  1026. # 获取缓存中的数据
  1027. cache_str = await self.redis_service.get_hash_field(hash_key, "data")
  1028. cache = json.loads(cache_str) if cache_str else []
  1029. # 回调处理
  1030. if len(contacts_wxids) == 1:
  1031. cache_wxids = [f['userName'] for f in cache]
  1032. friends_brief = await self.get_brief_info_async(token_id, app_id, contacts_wxids)
  1033. if contacts_wxids[0] in cache_wxids:
  1034. # 替换已经存在的数据
  1035. for i in range(len(cache)):
  1036. if cache[i]['userName'] == contacts_wxids[0]:
  1037. cache[i] = friends_brief[0]
  1038. else:
  1039. cache.extend(f for f in friends_brief if f["nickName"])
  1040. friends_no_brief_wxid = [f['userName'] for f in friends_brief if not f["nickName"]]
  1041. if friends_no_brief_wxid:
  1042. detailed_info = await self.get_detail_info_async(token_id, app_id, friends_no_brief_wxid)
  1043. cache.extend(detailed_info)
  1044. # 分批处理
  1045. else:
  1046. cache=[]
  1047. # 缓存为空,分批处理 contacts_wxids
  1048. batch_size = 100
  1049. for i in range(0, len(contacts_wxids), batch_size):
  1050. batch = contacts_wxids[i:i + batch_size]
  1051. friends_brief = await self.get_brief_info_async(token_id, app_id, batch)
  1052. cache.extend(f for f in friends_brief if f["nickName"])
  1053. friends_no_brief_wxid = [f['userName'] for f in friends_brief if not f["nickName"]]
  1054. if friends_no_brief_wxid:
  1055. detailed_info = await self.get_detail_info_async(token_id, app_id, friends_no_brief_wxid)
  1056. cache.extend(detailed_info)
  1057. # 更新缓存
  1058. await self.redis_service.update_hash_field(hash_key, "data", json.dumps(cache, ensure_ascii=False))
  1059. return cache
  1060. async def delete_contacts_brief_from_cache_async(self, wxid, contacts_wxids: list):
  1061. """
  1062. 删除联系人信息保存到 Redis 缓存。
  1063. """
  1064. hash_key = f"__AI_OPS_WX__:CONTACTS_BRIEF:{wxid}"
  1065. cache_str = await self.redis_service.get_hash_field(hash_key, "data")
  1066. cache = json.loads(cache_str) if cache_str else []
  1067. # 将 contacts_wxids 转换为集合,提高查找效率
  1068. wxids_set = set(contacts_wxids)
  1069. # 过滤 cache:保留 userName 不在集合中的对象
  1070. filtered_cache_async = [contact for contact in cache if contact["userName"] not in wxids_set]
  1071. # # 如果需要原地修改原 cache 列表:
  1072. # cache[:] = filtered_cache_async
  1073. await self.redis_service.update_hash_field(hash_key, "data", json.dumps(filtered_cache_async, ensure_ascii=False))
  1074. async def save_groups_info_to_cache_async(self, token_id, app_id, wxid, chatroom_ids: list)->list:
  1075. """
  1076. 将群信息保存到 Redis 缓存。
  1077. """
  1078. # Redis 缓存的 key
  1079. hash_key = f"__AI_OPS_WX__:GROUPS_INFO:{wxid}"
  1080. # 获取当前缓存中所有的 chatroom_id
  1081. existing_chatrooms = await self.redis_service.get_hash(hash_key)
  1082. # 找出需要删除的 chatroom_ids
  1083. chatrooms_to_delete = set(existing_chatrooms.keys()) - set(chatroom_ids)
  1084. # 删除缓存中不再需要的 chatroom_id 数据
  1085. for chatroom_id in chatrooms_to_delete:
  1086. await self.redis_service.delete_hash_field(hash_key, chatroom_id)
  1087. for chatroom_id in chatroom_ids:
  1088. if not check_chatroom(chatroom_id):
  1089. await self.redis_service.delete_hash_field(f'__AI_OPS_WX__:GROUPS_INFO:{wxid}',chatroom_id)
  1090. await self.redis_service.delete_hash_field(f'__AI_OPS_WX__:GROUPS_MEMBERS:{wxid}',chatroom_id)
  1091. logger.info(f'{chatroom_id} 不是有效的群,不处理')
  1092. continue
  1093. # 获取群信息
  1094. ret, msg, data =await self.get_chatroom_info_async(token_id, app_id, chatroom_id)
  1095. if ret != 200:
  1096. continue
  1097. # print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
  1098. # print('群信息save_groups_info_to_cache_async')
  1099. # print(data)
  1100. # print(bool(not data.get('memberList', [])))
  1101. # print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
  1102. if not data.get('memberList', []):
  1103. await self.redis_service.delete_hash_field(f'__AI_OPS_WX__:GROUPS_INFO:{wxid}',chatroom_id)
  1104. await self.redis_service.delete_hash_field(f'__AI_OPS_WX__:GROUPS_MEMBERS:{wxid}',chatroom_id)
  1105. logger.info(f'通过 成员列表为空 清理 {chatroom_id}群成员信息')
  1106. continue
  1107. # 更新缓存
  1108. await self.redis_service.update_hash_field(hash_key, chatroom_id, json.dumps(data, ensure_ascii=False))
  1109. await asyncio.sleep(0.1)
  1110. async def save_groups_members_to_cache_async(self, token_id, app_id, wxid, chatroom_ids: list):
  1111. """
  1112. 将群成员保存到 Redis 缓存。
  1113. """
  1114. # Redis 缓存的 key
  1115. hash_key = f"__AI_OPS_WX__:GROUPS_MEMBERS:{wxid}"
  1116. # 获取当前缓存中所有的 chatroom_id
  1117. existing_chatrooms = await self.redis_service.get_hash(hash_key)
  1118. # 找出需要删除的 chatroom_ids
  1119. chatrooms_to_delete = set(existing_chatrooms.keys()) - set(chatroom_ids)
  1120. # 删除缓存中不再需要的 chatroom_id 数据
  1121. for chatroom_id in chatrooms_to_delete:
  1122. await self.redis_service.delete_hash_field(hash_key, chatroom_id)
  1123. for chatroom_id in chatroom_ids:
  1124. # 获取群信息
  1125. ret, msg, data = await self.get_group_memberlist_async(token_id, app_id, chatroom_id)
  1126. if ret != 200:
  1127. continue
  1128. # 更新缓存
  1129. await self.redis_service.update_hash_field(hash_key, chatroom_id, json.dumps(data, ensure_ascii=False))
  1130. await asyncio.sleep(0.1)
  1131. async def update_group_members_to_cache_async(self, token_id, app_id, wxid, chatroom_id: str):
  1132. """
  1133. 更新将群信息保存到 Redis 缓存。
  1134. """
  1135. if not check_chatroom(chatroom_id):
  1136. return
  1137. # Redis 缓存的 key
  1138. hash_key = f"__AI_OPS_WX__:GROUPS_MEMBERS:{wxid}"
  1139. # 获取群信息
  1140. ret, msg, data = await self.get_group_memberlist_async(token_id, app_id, chatroom_id)
  1141. if ret != 200:
  1142. if msg in '获取群成员列表异常:null':
  1143. await self.redis_service.delete_hash_field(f'__AI_OPS_WX__:GROUPS_INFO:{wxid}',chatroom_id)
  1144. await self.redis_service.delete_hash_field(f'__AI_OPS_WX__:GROUPS_MEMBERS:{wxid}',chatroom_id)
  1145. logger.info(f'通过 [获取群成员列表异常:null] 清理 {chatroom_id}群成员信息')
  1146. return
  1147. else:
  1148. logger.error(f"获取{chatroom_id}群成员信息失败,错误信息:{ret} {msg}")
  1149. return
  1150. await self.redis_service.update_hash_field(hash_key, chatroom_id, json.dumps(data, ensure_ascii=False))
  1151. async def get_group_members_from_cache_async(self, wxid,chatroom_id)->dict:
  1152. """
  1153. 获取缓存中单个群成员。
  1154. """
  1155. hash_key = f"__AI_OPS_WX__:GROUPS_MEMBERS:{wxid}"
  1156. cache = await self.redis_service.get_hash_field(hash_key,chatroom_id)
  1157. groups=json.loads(cache) if cache else {}
  1158. return groups
  1159. async def get_groups_members_from_cache_async(self, wxid)->list:
  1160. """
  1161. 获取缓存中所有群成员。
  1162. """
  1163. hash_key = f"__AI_OPS_WX__:GROUPS_MEMBERS:{wxid}"
  1164. cache = await self.redis_service.get_hash(hash_key)
  1165. groups=[json.loads(v) for v in cache.values()]
  1166. return groups
  1167. async def get_groups_info_members_from_cache_async(self, wxid)->list:
  1168. groups_info_hash_key = f"__AI_OPS_WX__:GROUPS_INFO:{wxid}"
  1169. groups_info_cache = await self.redis_service.get_hash(groups_info_hash_key)
  1170. groups_menbers_hash_key= f"__AI_OPS_WX__:GROUPS_MEMBERS:{wxid}"
  1171. groups_menbers_cache = await self.redis_service.get_hash(groups_menbers_hash_key)
  1172. groups_info_cache_list=[{"chatroom_id": field, "value": json.loads(value)} for field, value in groups_info_cache.items()]
  1173. groups_menbers_cache_list=[{"chatroom_id": field, "value": json.loads(value)} for field, value in groups_menbers_cache.items()]
  1174. # 合并逻辑
  1175. merged_data = []
  1176. # 遍历 group_info
  1177. for info in groups_info_cache_list:
  1178. chatroom_id = info['chatroom_id']
  1179. group_data = info['value']
  1180. # 查找对应的 group_members
  1181. members = next((m['value'] for m in groups_menbers_cache_list if m['chatroom_id'] == chatroom_id), None)
  1182. if members:
  1183. # 合并数据
  1184. merged_group = {
  1185. "nickName": group_data['nickName'],
  1186. "chatroomId": group_data['chatroomId'],
  1187. "memberList": members['memberList'],
  1188. "chatroomOwner": members.get('chatroomOwner', None),
  1189. "adminWxid": members.get('adminWxid',None)
  1190. }
  1191. merged_data.append(merged_group)
  1192. return merged_data
  1193. async def get_group_info_members_from_cache_async(self, wxid,chatroom_id)->dict:
  1194. group_info_cache= await self.get_group_info_from_cache_async(wxid,chatroom_id)
  1195. group_menbers_cache= await self.get_group_members_from_cache_async(wxid,chatroom_id)
  1196. group_info_members = {
  1197. "nickName": group_info_cache['nickName'],
  1198. "chatroomId": group_info_cache['chatroomId'],
  1199. "memberList": group_menbers_cache['memberList'],
  1200. "chatroomOwner": group_menbers_cache['chatroomOwner'],
  1201. "adminWxid": group_menbers_cache['adminWxid']
  1202. }
  1203. return group_info_members
  1204. async def update_group_info_to_cache_async(self, token_id, app_id, wxid, chatroom_id: str):
  1205. """
  1206. 更新将群信息保存到 Redis 缓存。
  1207. """
  1208. if not check_chatroom(chatroom_id):
  1209. return
  1210. # Redis 缓存的 key
  1211. hash_key = f"__AI_OPS_WX__:GROUPS_INFO:{wxid}"
  1212. if not check_chatroom(chatroom_id):
  1213. await self.redis_service.delete_hash_field(f'__AI_OPS_WX__:GROUPS_INFO:{wxid}',chatroom_id)
  1214. await self.redis_service.delete_hash_field(f'__AI_OPS_WX__:GROUPS_MEMBERS:{wxid}',chatroom_id)
  1215. return
  1216. # 获取群信息
  1217. ret, msg, data =await self.get_chatroom_info_async(token_id, app_id, chatroom_id)
  1218. if ret != 200:
  1219. logger.error(f"获取{chatroom_id}群成员信息失败,错误信息:{ret} {msg}")
  1220. return
  1221. await self.redis_service.update_hash_field(hash_key, chatroom_id, json.dumps(data, ensure_ascii=False))
  1222. async def get_groups_info_from_cache_async(self, wxid)->list:
  1223. """
  1224. 获取群信息保存到 Redis 缓存。
  1225. """
  1226. hash_key = f"__AI_OPS_WX__:GROUPS_INFO:{wxid}"
  1227. cache = await self.redis_service.get_hash(hash_key)
  1228. groups=[json.loads(v) for v in cache.values()]
  1229. return groups
  1230. async def get_group_info_from_cache_async(self, wxid,chatroom_id)->dict:
  1231. """
  1232. 获取群信息保存到 Redis 缓存。
  1233. """
  1234. hash_key = f"__AI_OPS_WX__:GROUPS_INFO:{wxid}"
  1235. cache = await self.redis_service.get_hash_field(hash_key,chatroom_id)
  1236. groups=json.loads(cache) if cache else {}
  1237. return groups
  1238. async def get_wxchat_config_from_cache_async(self, wxid):
  1239. """
  1240. 获取配置信息
  1241. """
  1242. hash_key = f"__AI_OPS_WX__:WXCHAT_CONFIG"
  1243. config = await self.redis_service.get_hash_field(hash_key, wxid)
  1244. return json.loads(config) if config else {}
  1245. async def save_wxchat_config_async(self, wxid, config:dict):
  1246. """
  1247. 保存配置信息
  1248. """
  1249. hash_key = f"__AI_OPS_WX__:WXCHAT_CONFIG"
  1250. await self.redis_service.update_hash_field(hash_key, wxid, json.dumps(config, ensure_ascii=False))
  1251. async def get_global_config_from_cache_async(self):
  1252. """
  1253. 获取配置信息
  1254. """
  1255. hash_key = f"__AI_OPS_WX__:GLOBAL_CONFIG"
  1256. config = await self.redis_service.get_hash_field(hash_key,"data")
  1257. return json.loads(config) if config else {}
  1258. async def save_global_config_async(self, config:dict):
  1259. """
  1260. 保存配置信息
  1261. """
  1262. hash_key = f"__AI_OPS_WX__:GLOBAL_CONFIG"
  1263. await self.redis_service.update_hash_field(hash_key, "data,", json.dumps(config, ensure_ascii=False))
  1264. async def get_login_info_from_cache_async(self,tel):
  1265. hash_key = f"__AI_OPS_WX__:LOGININFO:{tel}"
  1266. cache = await self.redis_service.get_hash(hash_key)
  1267. return cache
  1268. async def get_login_info_by_wxid_async(self,wxid:str)->tuple:
  1269. cursor = 0
  1270. while True:
  1271. cursor, login_keys =await self.redis_service.client.scan(cursor, match='__AI_OPS_WX__:LOGININFO:*')
  1272. # 批量获取所有键的 hash 数据
  1273. for k in login_keys:
  1274. r = await self.redis_service.get_hash(k)
  1275. if r.get("wxid") == wxid:
  1276. return k,r
  1277. # 如果游标为 0,则表示扫描完成
  1278. if cursor == 0:
  1279. break
  1280. return None,None
  1281. async def get_login_info_by_app_id_async(self,app_id:str)->tuple:
  1282. cursor = 0
  1283. while True:
  1284. cursor, login_keys =await self.redis_service.client.scan(cursor, match='__AI_OPS_WX__:LOGININFO:*')
  1285. #print(f'login_keys:{login_keys}')
  1286. # 批量获取所有键的 hash 数据
  1287. for k in login_keys:
  1288. r = await self.redis_service.get_hash(k)
  1289. #print(r)
  1290. if r.get("appId") == app_id:
  1291. return k,r
  1292. # 如果游标为 0,则表示扫描完成
  1293. if cursor == 0:
  1294. break
  1295. return None,None
  1296. async def get_token_id_by_app_id_async(self,app_id: str) -> str:
  1297. # 使用 SCAN 避免一次性返回所有的匹配键,逐步扫描
  1298. cursor = 0
  1299. while True:
  1300. cursor, login_keys =await self.redis_service.client.scan(cursor, match='__AI_OPS_WX__:LOGININFO:*')
  1301. # 批量获取所有键的 hash 数据
  1302. for k in login_keys:
  1303. r = await self.redis_service.get_hash(k)
  1304. if r.get("appId") == app_id:
  1305. return r.get("tokenId", "")
  1306. # 如果游标为 0,则表示扫描完成
  1307. if cursor == 0:
  1308. break
  1309. return ""
  1310. async def save_login_wx_captch_code_to_cache_async(self,tel,captch_code):
  1311. hash_key = f"__AI_OPS_WX__:WXCAPTCHCODE:{tel}"
  1312. await self.redis_service.set_hash(hash_key,{"data":captch_code},30)
  1313. # async def get_login_wx_captch_code_from_cache_async(self,token_id)->str:
  1314. # hash_key = f"__AI_OPS_WX__:WXCAPTCHCODE:{token_id}"
  1315. # r=await self.redis_service.get_hash_field(hash_key,"data")
  1316. # return r
  1317. async def get_login_wx_captch_code_from_cache_async(self,tel)->str:
  1318. hash_key = f"__AI_OPS_WX__:WXCAPTCHCODE:{tel}"
  1319. r=await self.redis_service.get_hash_field(hash_key,"data")
  1320. return r
  1321. async def acquire_login_lock_async(self, token_id, expire_time=10):
  1322. hash_key = f"__AI_OPS_WX__:LOGINLOCK:{token_id}"
  1323. identifier=str(uuid.uuid4())
  1324. if await self.redis_service.client.setnx(hash_key, identifier):
  1325. await self.redis_service.client.expire(hash_key, expire_time)
  1326. return True
  1327. return False
  1328. async def release_login_lock_async(self, token_id):
  1329. hash_key = f"__AI_OPS_WX__:LOGINLOCK:{token_id}"
  1330. await self.redis_service.client.delete(hash_key)
  1331. async def save_group_add_contacts_history_async(self,wxid,chatroom_id,contact_wxid,history:AddGroupContactsHistory):
  1332. '''
  1333. 保存群加好友历史
  1334. '''
  1335. hash_key = f"__AI_OPS_WX__:GROUPS_ADD_CONTACT_HISTORY:{wxid}:{chatroom_id}"
  1336. data_str=await self.redis_service.get_hash_field(hash_key,contact_wxid)
  1337. data=json.loads(data_str) if data_str else []
  1338. data.append(history.model_dump())
  1339. await self.redis_service.update_hash_field(hash_key, contact_wxid, json.dumps(data, ensure_ascii=False))
  1340. async def get_group_add_contacts_history_async(self,wxid,chatroom_id,contact_wxid)->list[AddGroupContactsHistory]:
  1341. '''
  1342. 获取群加好友历史
  1343. '''
  1344. hash_key = f"__AI_OPS_WX__:GROUPS_ADD_CONTACT_HISTORY:{wxid}:{chatroom_id}"
  1345. data_str=await self.redis_service.get_hash_field(hash_key,contact_wxid)
  1346. data=json.loads(data_str) if data_str else []
  1347. #TypeAdapter.validate_python(List[Models.AddGroupContactsHistory], data)
  1348. return [AddGroupContactsHistory.model_validate(item) for item in data]
  1349. async def check_wixd_group_add_contacts_history_async(self,wxid,chatroom_id):
  1350. '''
  1351. 返回群发送好友达到2次的wxid
  1352. '''
  1353. hash_key = f"__AI_OPS_WX__:GROUPS_ADD_CONTACT_HISTORY:{wxid}:{chatroom_id}"
  1354. cache = await self.redis_service.get_hash(hash_key)
  1355. wxids = [key for key, value in cache.items() if len(json.loads(value)) == 2]
  1356. return wxids
  1357. async def is_group_add_contacts_history_one_day_200_async(self, wxid, chatroom_id) -> bool:
  1358. # 生成 hash_key
  1359. hash_key = f"__AI_OPS_WX__:GROUPS_ADD_CONTACT_HISTORY:{wxid}:{chatroom_id}"
  1360. cache = await self.redis_service.get_hash(hash_key)
  1361. today_list = []
  1362. today = datetime.datetime.now().date()
  1363. for key, value in cache.items():
  1364. value_data_list = json.loads(value)
  1365. for value_data in value_data_list:
  1366. add_time_date = datetime.datetime.fromtimestamp(value_data["addTime"]).date()
  1367. if add_time_date == today:
  1368. today_list.append(value_data)
  1369. if len(today_list) == 200:
  1370. return True
  1371. return False
  1372. async def is_group_add_contacts_history_one_day_90_async(self, wxid) -> bool:
  1373. today_list = []
  1374. today = datetime.datetime.now().date()
  1375. cursor = 0
  1376. hash_key = f"__AI_OPS_WX__:GROUPS_ADD_CONTACT_HISTORY:{wxid}:*"
  1377. while True:
  1378. cursor, history_keys =await self.redis_service.client.scan(cursor, match= hash_key)
  1379. #print(f'login_keys:{login_keys}')
  1380. # 批量获取所有键的 hash 数据
  1381. for k in history_keys:
  1382. cache = await self.redis_service.get_hash(k)
  1383. for key, value in cache.items():
  1384. value_data_list = json.loads(value)
  1385. for value_data in value_data_list:
  1386. add_time_date = datetime.datetime.fromtimestamp(value_data["addTime"]).date()
  1387. if add_time_date == today:
  1388. today_list.append(value_data)
  1389. if len(today_list) == 90:
  1390. return True
  1391. # 如果游标为 0,则表示扫描完成
  1392. if cursor == 0:
  1393. break
  1394. return False
  1395. async def is_group_add_contacts_history_one_day_async(self, wxid,today_total=90) -> bool:
  1396. today_list = []
  1397. today = datetime.datetime.now().date()
  1398. cursor = 0
  1399. hash_key = f"__AI_OPS_WX__:GROUPS_ADD_CONTACT_HISTORY:{wxid}:*"
  1400. while True:
  1401. cursor, history_keys =await self.redis_service.client.scan(cursor, match= hash_key)
  1402. #print(f'login_keys:{login_keys}')
  1403. # 批量获取所有键的 hash 数据
  1404. for k in history_keys:
  1405. cache = await self.redis_service.get_hash(k)
  1406. for key, value in cache.items():
  1407. value_data_list = json.loads(value)
  1408. for value_data in value_data_list:
  1409. add_time_date = datetime.datetime.fromtimestamp(value_data["addTime"]).date()
  1410. if add_time_date == today:
  1411. today_list.append(value_data)
  1412. if len(today_list) == today_total:
  1413. return True
  1414. # 如果游标为 0,则表示扫描完成
  1415. if cursor == 0:
  1416. break
  1417. return False
  1418. async def enqueue_to_add_contacts_async(self,wxid,scene:int,v3,v4):
  1419. """
  1420. 入列待添加好友
  1421. """
  1422. hash_key = f'__AI_OPS_WX__:TO_ADD_CONTACTS:{wxid}'
  1423. data_str=json.dumps({"wxid":wxid,"scene":scene,"v3":v3,"v4":v4},ensure_ascii=False)
  1424. await self.redis_service.enqueue(hash_key,data_str)
  1425. async def dequeue_to_add_contacts_async(self,wxid)->dict:
  1426. """
  1427. 出列待添加好友
  1428. """
  1429. hash_key = f'__AI_OPS_WX__:TO_ADD_CONTACTS:{wxid}'
  1430. data=await self.redis_service.dequeue(hash_key)
  1431. return json.loads(data) if data else {}
  1432. # async def acquire_task_run_time_lock_async(self,task_name,run_time,expire_time=None):
  1433. # hash_key = f"__AI_OPS_WX__:{task_name}"
  1434. # if await self.redis_service.client.setnx(hash_key, run_time):
  1435. # await self.redis_service.client.expire(hash_key, expire_time)
  1436. # return True
  1437. # return False
  1438. async def save_task_run_time_async(self,task_name,log:list,expire_time=None):
  1439. '''
  1440. 任务运行锁
  1441. '''
  1442. hash_key = f"__AI_OPS_WX__:TASK_RUN_TIME:{task_name}"
  1443. await self.redis_service.set_hash(hash_key,{"data": json.dumps(log, ensure_ascii=False)}, expire_time)
  1444. async def get_task_run_time_async(self,task_name)->list:
  1445. hash_key = f"__AI_OPS_WX__:TASK_RUN_TIME:{task_name}"
  1446. cache= await self.redis_service.get_hash_field(hash_key,"data")
  1447. return json.loads(cache) if cache else []
  1448. async def save_task_run_time_by_wxid_async(self,wxid,task_name,log:list,expire_time=None):
  1449. '''
  1450. 任务运行锁
  1451. '''
  1452. hash_key = f"__AI_OPS_WX__:TASK_RUN_TIME:{wxid}:{task_name}"
  1453. await self.redis_service.set_hash(hash_key,{"data": json.dumps(log, ensure_ascii=False)}, expire_time)
  1454. async def get_task_run_time_by_wxid_async(self,wxid,task_name)->list:
  1455. hash_key = f"__AI_OPS_WX__:TASK_RUN_TIME:{wxid}:{task_name}"
  1456. cache= await self.redis_service.get_hash_field(hash_key,"data")
  1457. return json.loads(cache) if cache else []
  1458. async def save_wx_expection_async(self,wxid,api_name,exception:str,expire_time=None):
  1459. hash_key = f"__AI_OPS_WX__:WX_EXCEPTION:{wxid}:{api_name}"
  1460. await self.redis_service.set_hash(hash_key,{"data": json.dumps(exception, ensure_ascii=False)}, expire_time)
  1461. async def get_wx_expection_async(self,wxid,api_name:str)->str:
  1462. hash_key = f"__AI_OPS_WX__:WX_EXCEPTION:{wxid}:{api_name}"
  1463. cache= await self.redis_service.get_hash_field(hash_key,"data")
  1464. return json.loads(cache) if cache else ""
  1465. async def wx_add_contacts_from_chatroom_task_status_async(self,wxid,chatroom_id)->int:
  1466. history_hash_key = f'__AI_OPS_WX__:GROUPS_ADD_CONTACT_HISTORY:{wxid}:{chatroom_id}'
  1467. cache=await self.redis_service.get_hash(history_hash_key)
  1468. group=await self. get_group_members_from_cache_async(wxid,chatroom_id)
  1469. chatroom_member_list = group.get('memberList', [])
  1470. chatroom_owner_wxid = group.get('chatroomOwner', None)
  1471. admin_wxids = group.get('adminWxid', [])
  1472. admin_wxids = group.get('adminWxid')
  1473. if admin_wxids is None:
  1474. admin_wxids = []
  1475. if chatroom_owner_wxid:
  1476. admin_wxids.append(chatroom_owner_wxid)
  1477. #unavailable_wixds=set(admin_wxids)
  1478. available_members=[m["wxid"] for m in chatroom_member_list if m["wxid"] not in admin_wxids]
  1479. if len(available_members) > len(cache.keys()):
  1480. return 1
  1481. for key, value in cache.items():
  1482. value_data_list = json.loads(value)
  1483. if len(value_data_list) <2:
  1484. return 1
  1485. return 2
  1486. async def delete_group_add_contacts_history_async(self,wxid,chatroom_id)->dict:
  1487. hash_key = f"__AI_OPS_WX__:GROUPS_ADD_CONTACT_HISTORY:{wxid}:{chatroom_id}"
  1488. await self.redis_service.delete_hash(hash_key)
  1489. async def is_human_handle_msg_async(self,wxid)->bool:
  1490. hash_key = f"__AI_OPS_WX__:HUMAN_HANDLE_MSG:{wxid}"
  1491. cache= await self.redis_service.get_hash_field(hash_key,"data")
  1492. return True if cache else False
  1493. async def set_human_handle_msg_async(self,wxid,expire_time=60*30):
  1494. hash_key = f"__AI_OPS_WX__:HUMAN_HANDLE_MSG:{wxid}"
  1495. await self.redis_service.set_hash(hash_key,{"data": json.dumps(int(time.time()), ensure_ascii=False)}, expire_time)
  1496. async def is_human_handle_msg_with_contact_wxid_async(self,wxid,contact_wxid)->bool:
  1497. hash_key = f"__AI_OPS_WX__:HUMAN_HANDLE_MSG_WTIH_CONTACT:{wxid}:{contact_wxid}"
  1498. cache= await self.redis_service.get_hash_field(hash_key,"data")
  1499. return True if cache else False
  1500. async def set_human_handle_msg_with_contact_wxid_async(self,wxid,contact_wxid,expire_time=60*30)->bool:
  1501. hash_key = f"__AI_OPS_WX__:HUMAN_HANDLE_MSG_WTIH_CONTACT:{wxid}:{contact_wxid}"
  1502. cache= await self.redis_service.set_hash(hash_key,{"data": json.dumps(int(time.time()), ensure_ascii=False)}, expire_time)
  1503. return True if cache else False
  1504. # 依赖项:获取 GeWeChatCom 单例
  1505. async def get_gewe_service(app: FastAPI = Depends()) -> GeWeService:
  1506. return app.state.gewe_service