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.

898 lines
35KB

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