Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

1029 lines
40KB

  1. import requests
  2. import json
  3. import base64
  4. import io
  5. import json
  6. import os
  7. import threading
  8. import time
  9. import uuid
  10. import requests
  11. from io import BytesIO
  12. from PIL import Image
  13. from common import redis_helper
  14. from common.log import logger
  15. from model import Models
  16. from pydantic import TypeAdapter
  17. from typing import List
  18. wxchat=None
  19. class GeWeChatCom:
  20. def __init__(self, base_url):
  21. self.base_url = base_url
  22. ############################### 登录模块 ###############################
  23. def check_login(self, token_id, app_id, uuid,captch_code=""):
  24. '''
  25. 执行登录(步骤3)
  26. 获取到登录二维码后需每间隔5s调用本接口来判断是否登录成功
  27. 新设备登录平台,次日凌晨会掉线一次,重新登录时需调用获取二维码且传appId取码,登录成功后则可以长期在线
  28. 登录成功后请保存appId与wxid的对应关系,后续接口中会用到
  29. '''
  30. api_url = f"{self.base_url}/v2/api/login/checkLogin"
  31. headers = {
  32. 'X-GEWE-TOKEN': token_id,
  33. 'Content-Type': 'application/json'
  34. }
  35. data = {
  36. "appId": app_id,
  37. "uuid": uuid,
  38. "captchCode":captch_code
  39. }
  40. if captch_code=="":
  41. data = {
  42. "appId": app_id,
  43. "uuid": uuid
  44. }
  45. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  46. # response_data = response.json()
  47. # print(response_data)
  48. # return response_data.get('data')
  49. response_object = response.json()
  50. print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
  51. logger.info(f'登录验证码-请求参数 {json.dumps(data)}')
  52. logger.info(f'登录验证码-返回响应 {response_object}')
  53. print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
  54. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  55. def get_login_qr_code(self, token_id,app_id="",region_id="440000"):
  56. '''
  57. 获取登录二维码(步骤2)
  58. appId参数为设备ID,首次登录传空,会自动触发创建设备,掉线后重新登录则必须传接口返回的appId,注意同一个号避免重复创建设备,以免触发官方风控
  59. 取码时传的appId需要与上次登录扫码的微信一致,否则会导致登录失败
  60. 响应结果中的qrImgBase64为微信二维码图片的base64,前端需要将二维码图片展示给用户并进行手机扫码操作(PS: 扫码后调用步骤2,手机上才显示登录)。
  61. (或使用响应结果中的qrData生成二维码)
  62. '''
  63. api_url = f"{self.base_url}/v2/api/login/getLoginQrCode"
  64. headers = {
  65. 'X-GEWE-TOKEN': token_id,
  66. 'Content-Type': 'application/json'
  67. }
  68. # if app_id=="":
  69. # data = {
  70. # "appId": app_id
  71. # }
  72. # else:
  73. # data = {
  74. # "appId": app_id,
  75. # "regionId":region_id
  76. # }
  77. data = {
  78. "appId": app_id,
  79. "regionId":region_id
  80. }
  81. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  82. response_data = response.json()
  83. data=json.dumps(response_data, separators=(',', ':'),ensure_ascii=False)
  84. #logger.info(f'{token_id} 的登录APP信息:{data}')
  85. return response_data.get('data')
  86. def qrCallback(self,uuid, base64_string):
  87. try:
  88. from PIL import Image
  89. base64_string = base64_string.split(',')[1]
  90. img_data = base64.b64decode(base64_string)
  91. img = Image.open(io.BytesIO(img_data))
  92. _thread = threading.Thread(target=img.show, args=("QRCode",))
  93. _thread.setDaemon(True)
  94. _thread.start()
  95. except Exception as e:
  96. pass
  97. import qrcode
  98. # url = f"https://login.weixin.qq.com/l/{uuid}"
  99. # http://weixin.qq.com/x/4b7fY2d93zNCXhHFkNk8
  100. url = f"http://weixin.qq.com/x/{uuid}"
  101. qr_api1 = "https://api.isoyu.com/qr/?m=1&e=L&p=20&url={}".format(url)
  102. qr_api2 = "https://api.qrserver.com/v1/create-qr-code/?size=400×400&data={}".format(url)
  103. qr_api3 = "https://api.pwmqr.com/qrcode/create/?url={}".format(url)
  104. qr_api4 = "https://my.tv.sohu.com/user/a/wvideo/getQRCode.do?text={}".format(url)
  105. print("You can also scan QRCode in any website below:")
  106. print(qr_api3)
  107. print(qr_api4)
  108. print(qr_api2)
  109. print(qr_api1)
  110. # _send_qr_code([qr_api3, qr_api4, qr_api2, qr_api1])
  111. qr = qrcode.QRCode(border=1)
  112. qr.add_data(url)
  113. qr.make(fit=True)
  114. qr.print_ascii(invert=True)
  115. return [qr_api1 ,qr_api2, qr_api3, qr_api4]
  116. ############################### 账号管理 ###############################
  117. def reconnection(self,token_id,app_id):
  118. '''
  119. 断线重连
  120. 当系统返回账号已离线,但是手机顶部还显示ipad在线,可用此接口尝试重连,若返回错误/失败则必须重新调用步骤一登录
  121. 本接口非常用接口,可忽略
  122. '''
  123. api_url = f"{self.base_url}/v2/api/login/reconnection"
  124. headers = {
  125. 'X-GEWE-TOKEN': token_id,
  126. 'Content-Type': 'application/json'
  127. }
  128. data = {
  129. "appId": app_id
  130. }
  131. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  132. response = response.json()
  133. print(response)
  134. return response
  135. def logout(self,token_id,app_id):
  136. '''
  137. 退出
  138. '''
  139. api_url = f"{self.base_url}/v2/api/login/logout"
  140. headers = {
  141. 'X-GEWE-TOKEN': token_id,
  142. 'Content-Type': 'application/json'
  143. }
  144. data = {
  145. "appId": app_id
  146. }
  147. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  148. response_data = response.json()
  149. print(response_data)
  150. return response_data.get('data')
  151. def check_online(self,token_id,app_id):
  152. '''
  153. 检查是否在线
  154. 响应结果的data=true则是在线,反之为离线
  155. '''
  156. api_url = f"{self.base_url}/v2/api/login/checkOnline"
  157. headers = {
  158. 'X-GEWE-TOKEN': token_id,
  159. 'Content-Type': 'application/json'
  160. }
  161. data = {
  162. "appId": app_id
  163. }
  164. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  165. response_data = response.json()
  166. print(response_data)
  167. return response_data.get('data')
  168. ############################### 联系人模块 ###############################
  169. def fetch_contacts_list(self, token_id, app_id):
  170. '''
  171. 获取通讯录列表
  172. 本接口为长耗时接口,耗时时间根据好友数量递增,若接口返回超时可通过获取通讯录列表缓存接口获取响应结果
  173. 本接口返回的群聊仅为保存到通讯录中的群聊,若想获取会话列表中的所有群聊,需要通过消息订阅做二次处理。
  174. 原因:当未获取的群有成员在群内发消息的话会有消息回调, 开发者此刻调用获取群详情接口查询群信息入库保存即可,
  175. 比如说手机上三年前不说话的群,侧滑删除了,用户手机上也不会看到被删除的群聊的 ,但是有群成员说了话他会显示,
  176. 原理就是各个终端(Android、IOS、桌面版微信)取得了消息回调,又去获取群详情信息,本地数据库缓存了下来,显示的手机群聊,让用户感知的。
  177. '''
  178. api_url = f"{self.base_url}/v2/api/contacts/fetchContactsList"
  179. headers = {
  180. 'X-GEWE-TOKEN': token_id,
  181. 'Content-Type': 'application/json'
  182. }
  183. data = {
  184. "appId": app_id
  185. }
  186. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  187. response_object = response.json()
  188. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  189. def fetch_contacts_list_cache(self, token_id, app_id):
  190. '''
  191. 获取通讯录列表缓存
  192. 通讯录列表数据缓存10分钟,超时则需要重新调用获取通讯录列表接口
  193. '''
  194. api_url = f"{self.base_url}/v2/api/contacts/fetchContactsListCache"
  195. headers = {
  196. 'X-GEWE-TOKEN': token_id,
  197. 'Content-Type': 'application/json'
  198. }
  199. data = {
  200. "appId": app_id
  201. }
  202. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  203. response_data = response.json()
  204. print(response_data)
  205. return response_data.get('data')
  206. def get_brief_info(self,token_id, app_id,wxids):
  207. '''
  208. 获取群/好友简要信息
  209. 1<= wxids <=100
  210. '''
  211. api_url = f"{self.base_url}/v2/api/contacts/getBriefInfo"
  212. headers = {
  213. 'X-GEWE-TOKEN': token_id,
  214. 'Content-Type': 'application/json'
  215. }
  216. data = {
  217. "appId": app_id,
  218. "wxids":wxids # list 1<= wxids <=100
  219. }
  220. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  221. response_data = response.json()
  222. # print(response_data)
  223. return response_data.get('data')
  224. def get_detail_info(self,token_id, app_id,wxids):
  225. '''
  226. 获取群/好友详细信息
  227. 1<= wxids <=20
  228. '''
  229. api_url = f"{self.base_url}/v2/api/contacts/getDetailInfo"
  230. headers = {
  231. 'X-GEWE-TOKEN': token_id,
  232. 'Content-Type': 'application/json'
  233. }
  234. data = {
  235. "appId": app_id,
  236. "wxids":wxids # list 1<= wxids <=20
  237. }
  238. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  239. response_data = response.json()
  240. print(f'需要详细信息的wxids:\n{wxids}\n{response_data}')
  241. return response_data.get('data')
  242. def delete_friend(self,token_id, app_id,friend_wxid):
  243. '''
  244. 删除好友
  245. '''
  246. api_url = f"{self.base_url}/v2/api/contacts/deleteFriend"
  247. headers = {
  248. 'X-GEWE-TOKEN': token_id,
  249. 'Content-Type': 'application/json'
  250. }
  251. data = {
  252. "appId": app_id,
  253. "wxid":friend_wxid
  254. }
  255. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  256. response_object = response.json()
  257. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  258. def set_friend_remark(self,token_id, app_id,friend_wxid,remark):
  259. '''
  260. 设置好友备注
  261. '''
  262. api_url = f"{self.base_url}/v2/api/contacts/setFriendRemark"
  263. headers = {
  264. 'X-GEWE-TOKEN': token_id,
  265. 'Content-Type': 'application/json'
  266. }
  267. data = {
  268. "appId": app_id,
  269. "wxid":friend_wxid,
  270. "remark":remark
  271. }
  272. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  273. response_object = response.json()
  274. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  275. ############################### 消息模块 ###############################
  276. def post_text(self,token_id,app_id,to_wxid,content):
  277. api_url = f"{self.base_url}/v2/api/message/postText"
  278. headers = {
  279. 'X-GEWE-TOKEN': token_id,
  280. 'Content-Type': 'application/json'
  281. }
  282. data = {
  283. "appId": app_id,
  284. "toWxid": to_wxid,
  285. "content": content
  286. }
  287. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  288. response_object = response.json()
  289. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  290. def post_image(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. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  302. response_object = response.json()
  303. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  304. def post_voice(self,token_id,app_id,to_wxid,voice_url,voice_duration):
  305. api_url = f"{self.base_url}/v2/api/message/postVoice"
  306. headers = {
  307. 'X-GEWE-TOKEN': token_id,
  308. 'Content-Type': 'application/json'
  309. }
  310. data = {
  311. "appId": app_id,
  312. "toWxid": to_wxid,
  313. "voiceUrl": voice_url,
  314. "voiceDuration":voice_duration
  315. }
  316. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  317. response_object = response.json()
  318. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  319. def post_video(self,token_id,app_id,to_wxid,video_url,video_thumb_url,video_duration):
  320. api_url = f"{self.base_url}/v2/api/message/postVideo"
  321. headers = {
  322. 'X-GEWE-TOKEN': token_id,
  323. 'Content-Type': 'application/json'
  324. }
  325. data = {
  326. "appId": app_id,
  327. "toWxid": to_wxid,
  328. "videoUrl": video_url,
  329. "videoDuration":video_duration,
  330. "thumbUrl":video_thumb_url
  331. }
  332. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  333. response_object = response.json()
  334. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  335. def forward_image(self,token_id,app_id,to_wxid,aeskey,cdnthumburl,cdnthumblength,cdnthumbheight,cdnthumbwidth,length,md5):
  336. api_url = f"{self.base_url}/v2/api/message/forwardImage"
  337. headers = {
  338. 'X-GEWE-TOKEN': token_id,
  339. 'Content-Type': 'application/json'
  340. }
  341. data = {
  342. "appId": app_id,
  343. "toWxid": to_wxid,
  344. "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>"
  345. }
  346. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  347. response_object = response.json()
  348. return response_object.get('data',None),response_object.get('ret',None),response_object.get('msg',None)
  349. def forward_video(self,token_id,app_id,to_wxid,aeskey,cdnvideourl,length):
  350. api_url = f"{self.base_url}/v2/api/message/forwardVideo"
  351. headers = {
  352. 'X-GEWE-TOKEN': token_id,
  353. 'Content-Type': 'application/json'
  354. }
  355. data = {
  356. "appId": app_id,
  357. "toWxid": to_wxid,
  358. "xml": f"<?xml version=\"1.0\"?>\n<msg>\n\t<videomsg aeskey=\"{aeskey}\" cdnvideourl=\"{cdnvideourl}\" cdnthumbaeskey=\"{aeskey}\" cdnthumburl=\"{cdnvideourl}\" length=\"{length}\" playlength=\"7\" 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>"
  359. }
  360. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  361. response_object = response.json()
  362. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  363. def add_contacts(self,token_id:str,app_id:str,scene:int,option:int,v3:str,v4:str,content:str):
  364. api_url = f"{self.base_url}/v2/api/contacts/addContacts"
  365. headers = {
  366. 'X-GEWE-TOKEN': token_id,
  367. 'Content-Type': 'application/json'
  368. }
  369. data = {
  370. "appId": app_id,
  371. "scene": scene,
  372. "option": option,
  373. "v3":v3,
  374. "v4":v4,
  375. "content":content
  376. }
  377. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  378. response_object = response.json()
  379. print(response_object)
  380. return response_object.get('ret',None),response_object.get('msg',None)
  381. def check_relation(self,token_id, app_id,wxids:list):
  382. '''
  383. 检查好友关系
  384. '''
  385. api_url = f"{self.base_url}/v2/api/contacts/checkRelation"
  386. headers = {
  387. 'X-GEWE-TOKEN': token_id,
  388. 'Content-Type': 'application/json'
  389. }
  390. data = {
  391. "appId": app_id,
  392. "wxids":wxids # list 1<= wxids <=20
  393. }
  394. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  395. response_object = response.json()
  396. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  397. ############################### 下载模块 ###############################
  398. def download_audio_msg(self,token_id:str,app_id:str,msg_id: int, xml: str):
  399. data = {
  400. "appId": app_id,
  401. "msgId": msg_id,
  402. "xml": xml
  403. }
  404. print(json.dumps(data))
  405. headers = {
  406. 'X-GEWE-TOKEN': token_id,
  407. 'Content-Type': 'application/json'
  408. }
  409. # http://api.geweapi.com/gewe/v2/api/gewe/v2/api/message/downloadVoice
  410. # response = requests.post(f"{self.base_url}/v2/api/gewe/v2/api/message/downloadVoice", json=data, headers=headers)
  411. url='http://api.geweapi.com/gewe/v2/api/message/downloadVoice'
  412. # url='http://api.geweapi.com/gewe/v2/api/gewe/v2/api/message/downloadVoice'
  413. response = requests.post(f"{url}", json=data, headers=headers)
  414. if response.ok:
  415. data = response.json()
  416. print(data)
  417. if data['ret'] == 200:
  418. print("Gewe download audio msg successfully.")
  419. print(data['data']['fileUrl'])
  420. return data['data']['fileUrl']
  421. else:
  422. print("Gewe download audio msg in error.")
  423. return False
  424. else:
  425. return False
  426. def download_image_msg(self,token_id:str,app_id:str,xml: str):
  427. data = {
  428. "appId": app_id,
  429. "type": 2,
  430. "xml": xml
  431. }
  432. print(json.dumps(data))
  433. headers = {
  434. 'X-GEWE-TOKEN': token_id,
  435. 'Content-Type': 'application/json'
  436. }
  437. response = requests.post(f"{self.base_url}/v2/api/message/downloadImage", json=data, headers=headers)
  438. if response.ok:
  439. data = response.json()
  440. # print(data)
  441. if data['ret'] == 200:
  442. print("Gewe download image msg successfully.")
  443. print(data['data']['fileUrl'])
  444. return data['data']['fileUrl']
  445. else:
  446. print("Gewe download image msg in error.")
  447. return False
  448. else:
  449. return False
  450. def download_audio_file(fileUrl: str, file_name: str):
  451. # 定义保存文件的本地路径和文件名
  452. local_filename = f'./silk/{file_name}.silk'
  453. # 使用requests库的get方法获取文件内容
  454. response = requests.get(fileUrl, stream=True)
  455. # 检查请求是否成功
  456. if response.status_code == 200:
  457. # 打开文件以二进制写入模式
  458. with open(local_filename, 'wb') as f:
  459. # 逐块写入文件,通常使用1024字节的块大小
  460. for chunk in response.iter_content(1024):
  461. f.write(chunk)
  462. print(f"文件已成功下载到 {local_filename}")
  463. else:
  464. print(f"请求失败,状态码: {response.status_code}")
  465. ############################### 群模块 ###############################
  466. def get_chatroom_info(self, token_id, app_id, chatroom_id):
  467. '''
  468. 获取群信息
  469. '''
  470. api_url = f"{self.base_url}/v2/api/group/getChatroomInfo"
  471. headers = {
  472. 'X-GEWE-TOKEN': token_id,
  473. 'Content-Type': 'application/json'
  474. }
  475. data = {
  476. "appId": app_id,
  477. "chatroomId": chatroom_id
  478. }
  479. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  480. response_object = response.json()
  481. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  482. def add_group_member_as_friend(self, token_id, app_id, chatroom_id, member_wxid, content):
  483. '''
  484. 添加群成员为好友
  485. '''
  486. api_url = f"{self.base_url}/v2/api/group/addGroupMemberAsFriend"
  487. headers = {
  488. 'X-GEWE-TOKEN': token_id,
  489. 'Content-Type': 'application/json'
  490. }
  491. data = {
  492. "appId": app_id,
  493. "chatroomId": chatroom_id,
  494. "content": content,
  495. "memberWxid": member_wxid,
  496. }
  497. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  498. response_object = response.json()
  499. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  500. def save_contract_list(self, token_id, app_id, chatroom_id,oper_type):
  501. '''
  502. 群保存到通讯录
  503. 操作类型 3保存到通讯录 2从通讯录移除
  504. '''
  505. api_url = f"{self.base_url}/v2/api/group/saveContractList"
  506. headers = {
  507. 'X-GEWE-TOKEN': token_id,
  508. 'Content-Type': 'application/json'
  509. }
  510. data = {
  511. "appId": app_id,
  512. "chatroomId": chatroom_id,
  513. "operType": oper_type
  514. }
  515. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  516. response_object = response.json()
  517. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  518. def get_group_memberlist(self,token_id,app_id,chatroom_id):
  519. api_url = f"{self.base_url}/v2/api/group/getChatroomMemberList"
  520. headers = {
  521. 'X-GEWE-TOKEN': token_id,
  522. 'Content-Type': 'application/json'
  523. }
  524. data = {
  525. "appId": app_id,
  526. "chatroomId": chatroom_id,
  527. }
  528. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  529. response_object = response.json()
  530. #print(response_object)
  531. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  532. ############################### 朋友圈模块 ###################################
  533. # 在新设备登录后的1-3天内,您将无法使用朋友圈发布、点赞、评论等功能。在此期间,如果尝试进行这些操作,您将收到来自微信团队的提醒。请注意遵守相关规定。
  534. def sns_visible_scope(self, token_id, app_id,option):
  535. '''
  536. 朋友圈可见范围 option 可选项
  537. 1:全部
  538. 2:最近半年
  539. 3:最近一个月
  540. 4:最近三天
  541. '''
  542. api_url = f"{self.base_url}/v2/api/sns/snsVisibleScope"
  543. headers = {
  544. 'X-GEWE-TOKEN': token_id,
  545. 'Content-Type': 'application/json'
  546. }
  547. data = {
  548. "appId": app_id,
  549. "option": option,
  550. }
  551. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  552. response_object = response.json()
  553. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  554. def stranger_visibility_enabled(self, token_id, app_id,enabled:bool):
  555. '''
  556. 是否允许陌生人查看朋友圈
  557. '''
  558. api_url = f"{self.base_url}/v2/api/sns/strangerVisibilityEnabled"
  559. headers = {
  560. 'X-GEWE-TOKEN': token_id,
  561. 'Content-Type': 'application/json'
  562. }
  563. data = {
  564. "appId": app_id,
  565. "enabled": enabled
  566. }
  567. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  568. response_object = response.json()
  569. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  570. def send_text_sns(self, token_id, app_id,content):
  571. '''
  572. 发送文字朋友圈
  573. '''
  574. api_url = f"{self.base_url}/v2/api/sns/sendTextSns"
  575. headers = {
  576. 'X-GEWE-TOKEN': token_id,
  577. 'Content-Type': 'application/json'
  578. }
  579. data = {
  580. "appId": app_id,
  581. "content": content
  582. }
  583. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  584. response_object = response.json()
  585. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  586. def send_image_sns(self, token_id, app_id,content,img_infos:list):
  587. '''
  588. 发送图片朋友圈
  589. '''
  590. api_url = f"{self.base_url}/v2/api/sns/sendImgSns"
  591. print(api_url)
  592. headers = {
  593. 'X-GEWE-TOKEN': token_id,
  594. 'Content-Type': 'application/json'
  595. }
  596. data = {
  597. "appId": app_id,
  598. "allowWxIds": [],
  599. "atWxIds": [],
  600. "disableWxIds": [],
  601. "content":content,
  602. "imgInfos": img_infos, # 通过上传朋友圈图片接口获取
  603. "privacy": False
  604. }
  605. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  606. response_object = response.json()
  607. print(response_object)
  608. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  609. def send_video_sns(self, token_id, app_id,content:str,video_info:object):
  610. '''
  611. 发送视频朋友圈
  612. '''
  613. api_url = f"{self.base_url}/v2/api/sns/sendVideoSns"
  614. headers = {
  615. 'X-GEWE-TOKEN': token_id,
  616. 'Content-Type': 'application/json'
  617. }
  618. data = {
  619. "appId": app_id,
  620. "content":content,
  621. "allowWxIds": [],
  622. "atWxIds": [],
  623. "disableWxIds": [],
  624. "videoInfo":video_info,
  625. "privacy": False
  626. }
  627. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  628. response_object = response.json()
  629. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  630. def upload_sns_image(self, token_id, app_id,img_urls:list):
  631. '''
  632. 上传朋友圈图片
  633. '''
  634. api_url = f"{self.base_url}/v2/api/sns/uploadSnsImage"
  635. headers = {
  636. 'X-GEWE-TOKEN': token_id,
  637. 'Content-Type': 'application/json'
  638. }
  639. data = {
  640. "appId": app_id,
  641. "imgUrls": img_urls
  642. }
  643. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  644. response_object = response.json()
  645. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  646. def upload_sns_video(self, token_id, app_id,video_url:str,video_thumb_url:str):
  647. '''
  648. 上传朋友圈视频
  649. '''
  650. api_url = f"{self.base_url}/v2/api/sns/uploadSnsVideo"
  651. headers = {
  652. 'X-GEWE-TOKEN': token_id,
  653. 'Content-Type': 'application/json'
  654. }
  655. data = {
  656. "appId": app_id,
  657. "thumbUrl": video_thumb_url,
  658. "videoUrl": video_url,
  659. }
  660. response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
  661. response_object = response.json()
  662. return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
  663. ############################### 其他 ###############################
  664. def save_session_messages_to_cache(self, hash_key,item:object)->list:
  665. '''
  666. 对话列表
  667. '''
  668. messages=redis_helper.redis_helper.get_hash(hash_key)
  669. wxid=hash_key.split(':')[-1]
  670. if not messages:
  671. messages=[{"role": "system", "content": ""}]
  672. messages.append(item)
  673. redis_helper.redis_helper.set_hash(hash_key,{"data":json.dumps(messages,ensure_ascii=False)},600)
  674. else:
  675. messages_str=redis_helper.redis_helper.get_hash_field(hash_key,"data")
  676. messages = json.loads(messages_str) if messages_str else []
  677. #判断是否含有图片
  678. last_message = messages[-1]
  679. content = last_message.get("content", [])
  680. if isinstance(content, list) and content:
  681. last_content_type = content[-1].get("type")
  682. if last_content_type == 'image_url':
  683. content.append(item['content'][0])
  684. messages[-1]['content']=content
  685. else:
  686. messages.append(item)
  687. else:
  688. if last_message!= item:
  689. messages.append(item)
  690. redis_helper.redis_helper.set_hash(hash_key,{"data":json.dumps(messages,ensure_ascii=False)},600)
  691. return messages
  692. def get_contacts_brief_from_cache(self, wxid)->list:
  693. """
  694. 获取联系人信息保存到 Redis 缓存。
  695. """
  696. hash_key = f"__AI_OPS_WX__:CONTACTS_BRIEF:{wxid}"
  697. cache_str = redis_helper.redis_helper.get_hash_field(hash_key, "data")
  698. return json.loads(cache_str) if cache_str else []
  699. def save_contacts_brief_to_cache(self, token_id, app_id, wxid, contacts_wxids: list)->list:
  700. """
  701. 将联系人信息保存到 Redis 缓存。
  702. """
  703. # Redis 缓存的 key
  704. hash_key = f"__AI_OPS_WX__:CONTACTS_BRIEF:{wxid}"
  705. # 获取缓存中的数据
  706. cache_str = redis_helper.redis_helper.get_hash_field(hash_key, "data")
  707. cache = json.loads(cache_str) if cache_str else []
  708. # 回调处理
  709. if len(contacts_wxids) == 1:
  710. cache_wxids = [f['userName'] for f in cache]
  711. friends_brief = self.get_brief_info(token_id, app_id, contacts_wxids)
  712. if contacts_wxids[0] in cache_wxids:
  713. # 替换已经存在的数据
  714. for i in range(len(cache)):
  715. if cache[i]['userName'] == contacts_wxids[0]:
  716. cache[i] = friends_brief[0]
  717. else:
  718. cache.extend(f for f in friends_brief if f["nickName"])
  719. friends_no_brief_wxid = [f['userName'] for f in friends_brief if not f["nickName"]]
  720. if friends_no_brief_wxid:
  721. detailed_info = self.get_detail_info(token_id, app_id, friends_no_brief_wxid)
  722. cache.extend(detailed_info)
  723. # 分批处理
  724. else:
  725. cache=[]
  726. # 缓存为空,分批处理 contacts_wxids
  727. batch_size = 100
  728. for i in range(0, len(contacts_wxids), batch_size):
  729. batch = contacts_wxids[i:i + batch_size]
  730. friends_brief = self.get_brief_info(token_id, app_id, batch)
  731. cache.extend(f for f in friends_brief if f["nickName"])
  732. friends_no_brief_wxid = [f['userName'] for f in friends_brief if not f["nickName"]]
  733. if friends_no_brief_wxid:
  734. detailed_info = self.get_detail_info(token_id, app_id, friends_no_brief_wxid)
  735. cache.extend(detailed_info)
  736. # 更新缓存
  737. redis_helper.redis_helper.update_hash_field(hash_key, "data", json.dumps(cache, ensure_ascii=False))
  738. return cache
  739. def delete_contacts_brief_from_cache(self, wxid, contacts_wxids: list):
  740. """
  741. 删除联系人信息保存到 Redis 缓存。
  742. """
  743. hash_key = f"__AI_OPS_WX__:CONTACTS_BRIEF:{wxid}"
  744. cache_str = redis_helper.redis_helper.get_hash_field(hash_key, "data")
  745. cache = json.loads(cache_str) if cache_str else []
  746. # 将 contacts_wxids 转换为集合,提高查找效率
  747. wxids_set = set(contacts_wxids)
  748. # 过滤 cache:保留 userName 不在集合中的对象
  749. filtered_cache = [contact for contact in cache if contact["userName"] not in wxids_set]
  750. # # 如果需要原地修改原 cache 列表:
  751. # cache[:] = filtered_cache
  752. redis_helper.redis_helper.update_hash_field(hash_key, "data", json.dumps(filtered_cache, ensure_ascii=False))
  753. def save_groups_info_to_cache(self, token_id, app_id, wxid, chatroom_ids: list):
  754. """
  755. 将群信息保存到 Redis 缓存。
  756. """
  757. # Redis 缓存的 key
  758. hash_key = f"__AI_OPS_WX__:GROUPS_INFO:{wxid}"
  759. # 获取当前缓存中所有的 chatroom_id
  760. existing_chatrooms = redis_helper.redis_helper.get_hash(hash_key)
  761. # 找出需要删除的 chatroom_ids
  762. chatrooms_to_delete = set(existing_chatrooms.keys()) - set(chatroom_ids)
  763. # 删除缓存中不再需要的 chatroom_id 数据
  764. for chatroom_id in chatrooms_to_delete:
  765. redis_helper.redis_helper.delete_hash_field(hash_key, chatroom_id)
  766. for chatroom_id in chatroom_ids:
  767. # 获取群信息
  768. ret, msg, data = self.get_chatroom_info(token_id, app_id, chatroom_id)
  769. if ret != 200:
  770. continue
  771. # 更新缓存
  772. redis_helper.redis_helper.update_hash_field(hash_key, chatroom_id, json.dumps(data, ensure_ascii=False))
  773. time.sleep(1)
  774. def save_groups_members_to_cache(self, token_id, app_id, wxid, chatroom_ids: list):
  775. """
  776. 将群成员保存到 Redis 缓存。
  777. """
  778. # Redis 缓存的 key
  779. hash_key = f"__AI_OPS_WX__:GROUPS_MEMBERS:{wxid}"
  780. # 获取当前缓存中所有的 chatroom_id
  781. existing_chatrooms = redis_helper.redis_helper.get_hash(hash_key)
  782. # 找出需要删除的 chatroom_ids
  783. chatrooms_to_delete = set(existing_chatrooms.keys()) - set(chatroom_ids)
  784. # 删除缓存中不再需要的 chatroom_id 数据
  785. for chatroom_id in chatrooms_to_delete:
  786. redis_helper.redis_helper.delete_hash_field(hash_key, chatroom_id)
  787. for chatroom_id in chatroom_ids:
  788. # 获取群信息
  789. ret, msg, data = self.get_group_memberlist(token_id, app_id, chatroom_id)
  790. if ret != 200:
  791. continue
  792. # 更新缓存
  793. redis_helper.redis_helper.update_hash_field(hash_key, chatroom_id, json.dumps(data, ensure_ascii=False))
  794. time.sleep(1)
  795. def update_group_members_to_cache(self, token_id, app_id, wxid, chatroom_id: str):
  796. """
  797. 更新将群信息保存到 Redis 缓存。
  798. """
  799. # Redis 缓存的 key
  800. hash_key = f"__AI_OPS_WX__:GROUPS_MEMBERS:{wxid}"
  801. # 获取群信息
  802. ret, msg, data = self.get_group_memberlist(token_id, app_id, chatroom_id)
  803. redis_helper.redis_helper.update_hash_field(hash_key, chatroom_id, json.dumps(data, ensure_ascii=False))
  804. def get_group_members_from_cache(self, wxid,chatroom_id)->dict:
  805. """
  806. 获取缓存中群成员。
  807. """
  808. hash_key = f"__AI_OPS_WX__:GROUPS_MEMBERS:{wxid}"
  809. cache = redis_helper.redis_helper.get_hash_field(hash_key,chatroom_id)
  810. groups=json.loads(cache) if cache else {}
  811. return groups
  812. def update_group_info_to_cache(self, token_id, app_id, wxid, chatroom_id: str):
  813. """
  814. 更新将群信息保存到 Redis 缓存。
  815. """
  816. # Redis 缓存的 key
  817. hash_key = f"__AI_OPS_WX__:GROUPS_INFO:{wxid}"
  818. # 获取群信息
  819. ret, msg, data = self.get_chatroom_info(token_id, app_id, chatroom_id)
  820. redis_helper.redis_helper.update_hash_field(hash_key, chatroom_id, json.dumps(data, ensure_ascii=False))
  821. def get_groups_info_from_cache(self, wxid)->list:
  822. """
  823. 获取群信息保存到 Redis 缓存。
  824. """
  825. hash_key = f"__AI_OPS_WX__:GROUPS_INFO:{wxid}"
  826. cache = redis_helper.redis_helper.get_hash(hash_key)
  827. groups=[json.loads(v) for v in cache.values()]
  828. return groups
  829. def get_group_info_from_cache(self, wxid,chatroom_id)->dict:
  830. """
  831. 获取群信息保存到 Redis 缓存。
  832. """
  833. hash_key = f"__AI_OPS_WX__:GROUPS_INFO:{wxid}"
  834. cache = redis_helper.redis_helper.get_hash_field(hash_key,chatroom_id)
  835. groups=json.loads(cache) if cache else {}
  836. return groups
  837. def get_wxchat_config_from_cache(self, wxid):
  838. """
  839. 获取配置信息
  840. """
  841. hash_key = f"__AI_OPS_WX__:WXCHAT_CONFIG"
  842. config = redis_helper.redis_helper.get_hash_field(hash_key, wxid)
  843. return json.loads(config) if config else {}
  844. def save_wxchat_config(self, wxid, config:dict):
  845. """
  846. 保存配置信息
  847. """
  848. hash_key = f"__AI_OPS_WX__:WXCHAT_CONFIG"
  849. redis_helper.redis_helper.update_hash_field(hash_key, wxid, json.dumps(config, ensure_ascii=False))
  850. def get_login_info_from_cache(self,tel):
  851. hash_key = f"__AI_OPS_WX__:LOGININFO:{tel}"
  852. cache = redis_helper.redis_helper.get_hash(hash_key)
  853. return cache
  854. def save_login_wx_captch_code_to_cache(self,token_id,captch_code):
  855. hash_key = f"__AI_OPS_WX__:WXCAPTCHCODE:{token_id}"
  856. redis_helper.redis_helper.set_hash(hash_key,{"data":captch_code},30)
  857. def get_login_wx_captch_code_from_cache(self,token_id)->str:
  858. hash_key = f"__AI_OPS_WX__:WXCAPTCHCODE:{token_id}"
  859. r=redis_helper.redis_helper.get_hash_field(hash_key,"data")
  860. return r
  861. def acquire_login_lock(self, token_id, expire_time=10):
  862. hash_key = f"__AI_OPS_WX__:LOGINLOCK:{token_id}"
  863. identifier=str(uuid.uuid4())
  864. if redis_helper.redis_helper.client.setnx(hash_key, identifier):
  865. redis_helper.redis_helper.client.expire(hash_key, expire_time)
  866. return True
  867. return False
  868. def release_login_lock(self, token_id):
  869. hash_key = f"__AI_OPS_WX__:LOGINLOCK:{token_id}"
  870. redis_helper.redis_helper.client.delete(hash_key)
  871. def save_group_add_contacts_history(self,wxid,chatroom_id,contact_wxid,history:Models.AddGroupContactsHistory):
  872. '''
  873. 保存群加好友历史
  874. '''
  875. hash_key = f"__AI_OPS_WX__:GROUPS_ADD_CONTACT_HISTORY:{wxid}:{chatroom_id}"
  876. data_str=redis_helper.redis_helper.get_hash_field(hash_key,contact_wxid)
  877. data=json.loads(data_str) if data_str else []
  878. data.append(history.model_dump())
  879. redis_helper.redis_helper.update_hash_field(hash_key, contact_wxid, json.dumps(data, ensure_ascii=False))
  880. def get_group_add_contacts_history(self,wxid,chatroom_id,contact_wxid):
  881. '''
  882. 获取群加好友历史
  883. '''
  884. hash_key = f"__AI_OPS_WX__:GROUPS_ADD_CONTACT_HISTORY:{wxid}:{chatroom_id}"
  885. data_str=redis_helper.redis_helper.get_hash_field(hash_key,contact_wxid)
  886. data=json.loads(data_str) if data_str else []
  887. #TypeAdapter.validate_python(List[Models.AddGroupContactsHistory], data)
  888. return [Models.AddGroupContactsHistory.model_validate(item) for item in data]
  889. def check_wixd_group_add_contacts_history(self,wxid,chatroom_id):
  890. '''
  891. 返回群发送好友达到2次的wxid
  892. '''
  893. hash_key = f"__AI_OPS_WX__:GROUPS_ADD_CONTACT_HISTORY:{wxid}:{chatroom_id}"
  894. cache = redis_helper.redis_helper.get_hash(hash_key)
  895. wxids = [key for key, value in cache.items() if len(json.loads(value)) == 2]
  896. return wxids
  897. def start():
  898. global wxchat
  899. # base_url = "http://192.168.88.11:2531"
  900. # wxchat = GeWeChat(base_url)
  901. wxchat = GeWeChatCom('http://api.geweapi.com/gewe')