|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470 |
- import requests
- import json
- import base64
- import io
- import json
- import os
- import threading
- import time
- import requests
-
- from io import BytesIO
- from PIL import Image
- from common import redis_helper
-
- wxchat=None
-
-
- class GeWeChatCom:
- def __init__(self, base_url):
- self.base_url = base_url
-
- ############################### 登录模块 ###############################
- def check_login(self, token_id, app_id, uuid):
- '''
- 执行登录(步骤3)
-
- 获取到登录二维码后需每间隔5s调用本接口来判断是否登录成功
-
- 新设备登录平台,次日凌晨会掉线一次,重新登录时需调用获取二维码且传appId取码,登录成功后则可以长期在线
-
- 登录成功后请保存appId与wxid的对应关系,后续接口中会用到
-
- '''
- api_url = f"{self.base_url}/v2/api/login/checkLogin"
- headers = {
- 'X-GEWE-TOKEN': token_id,
- 'Content-Type': 'application/json'
- }
- data = {
- "appId": app_id,
- "uuid": uuid
- }
- response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
- response_data = response.json()
- print(response_data)
- return response_data.get('data')
-
- def get_login_qr_code(self, token_id,app_id=""):
- '''
- 获取登录二维码(步骤2)
-
-
- appId参数为设备ID,首次登录传空,会自动触发创建设备,掉线后重新登录则必须传接口返回的appId,注意同一个号避免重复创建设备,以免触发官方风控
-
- 取码时传的appId需要与上次登录扫码的微信一致,否则会导致登录失败
-
- 响应结果中的qrImgBase64为微信二维码图片的base64,前端需要将二维码图片展示给用户并进行手机扫码操作(PS: 扫码后调用步骤2,手机上才显示登录)。
- (或使用响应结果中的qrData生成二维码)
- '''
- api_url = f"{self.base_url}/v2/api/login/getLoginQrCode"
- headers = {
- 'X-GEWE-TOKEN': token_id,
- 'Content-Type': 'application/json'
- }
- if app_id=="":
-
- data = {
- "appId": app_id
- }
- else:
- data = {
- "appId": app_id,
- "regionId":"440000"
- }
- response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
- response_data = response.json()
- return response_data.get('data')
-
- def qrCallback(self,uuid, base64_string):
- try:
- from PIL import Image
- base64_string = base64_string.split(',')[1]
- img_data = base64.b64decode(base64_string)
- img = Image.open(io.BytesIO(img_data))
- _thread = threading.Thread(target=img.show, args=("QRCode",))
- _thread.setDaemon(True)
- _thread.start()
- except Exception as e:
- pass
-
- import qrcode
-
- # url = f"https://login.weixin.qq.com/l/{uuid}"
- # http://weixin.qq.com/x/4b7fY2d93zNCXhHFkNk8
-
- url = f"http://weixin.qq.com/x/{uuid}"
-
- qr_api1 = "https://api.isoyu.com/qr/?m=1&e=L&p=20&url={}".format(url)
- qr_api2 = "https://api.qrserver.com/v1/create-qr-code/?size=400×400&data={}".format(url)
- qr_api3 = "https://api.pwmqr.com/qrcode/create/?url={}".format(url)
- qr_api4 = "https://my.tv.sohu.com/user/a/wvideo/getQRCode.do?text={}".format(url)
- print("You can also scan QRCode in any website below:")
- print(qr_api3)
- print(qr_api4)
- print(qr_api2)
- print(qr_api1)
- # _send_qr_code([qr_api3, qr_api4, qr_api2, qr_api1])
- qr = qrcode.QRCode(border=1)
- qr.add_data(url)
- qr.make(fit=True)
- qr.print_ascii(invert=True)
- ############################### 账号管理 ###############################
- def reconnection(self,token_id,app_id):
- '''
- 断线重连
-
- 当系统返回账号已离线,但是手机顶部还显示ipad在线,可用此接口尝试重连,若返回错误/失败则必须重新调用步骤一登录
-
- 本接口非常用接口,可忽略
-
- '''
- api_url = f"{self.base_url}/v2/api/login/reconnection"
- headers = {
- 'X-GEWE-TOKEN': token_id,
- 'Content-Type': 'application/json'
- }
- data = {
- "appId": app_id
- }
- response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
- response = response.json()
- print(response)
- return response
-
- def logout(self,token_id,app_id):
- '''
- 退出
- '''
-
- api_url = f"{self.base_url}/v2/api/login/logout"
- headers = {
- 'X-GEWE-TOKEN': token_id,
- 'Content-Type': 'application/json'
- }
- data = {
- "appId": app_id
- }
- response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
- response_data = response.json()
- print(response_data)
- return response_data.get('data')
-
- def check_online(self,token_id,app_id):
- '''
- 检查是否在线
-
- 响应结果的data=true则是在线,反之为离线
- '''
-
- api_url = f"{self.base_url}/v2/api/login/checkOnline"
- headers = {
- 'X-GEWE-TOKEN': token_id,
- 'Content-Type': 'application/json'
- }
- data = {
- "appId": app_id
- }
- response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
- response_data = response.json()
- print(response_data)
- return response_data.get('data')
-
-
- ############################### 联系人模块 ###############################
- def fetch_contacts_list(self, token_id, app_id):
- '''
- 获取通讯录列表
-
- 本接口为长耗时接口,耗时时间根据好友数量递增,若接口返回超时可通过获取通讯录列表缓存接口获取响应结果
-
- 本接口返回的群聊仅为保存到通讯录中的群聊,若想获取会话列表中的所有群聊,需要通过消息订阅做二次处理。
- 原因:当未获取的群有成员在群内发消息的话会有消息回调, 开发者此刻调用获取群详情接口查询群信息入库保存即可,
- 比如说手机上三年前不说话的群,侧滑删除了,用户手机上也不会看到被删除的群聊的 ,但是有群成员说了话他会显示,
- 原理就是各个终端(Android、IOS、桌面版微信)取得了消息回调,又去获取群详情信息,本地数据库缓存了下来,显示的手机群聊,让用户感知的。
- '''
-
- api_url = f"{self.base_url}/v2/api/contacts/fetchContactsList"
- headers = {
- 'X-GEWE-TOKEN': token_id,
- 'Content-Type': 'application/json'
- }
- data = {
- "appId": app_id
- }
- response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
- response_data = response.json()
- print(response_data)
- return response_data.get('data')
-
- def fetch_contacts_list_cache(self, token_id, app_id):
- '''
- 获取通讯录列表缓存
-
- 通讯录列表数据缓存10分钟,超时则需要重新调用获取通讯录列表接口
- '''
-
- api_url = f"{self.base_url}/v2/api/contacts/fetchContactsListCache"
- headers = {
- 'X-GEWE-TOKEN': token_id,
- 'Content-Type': 'application/json'
- }
- data = {
- "appId": app_id
- }
- response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
- response_data = response.json()
- print(response_data)
- return response_data.get('data')
-
- def get_brief_info(self,token_id, app_id,wxids):
- '''
- 获取群/好友简要信息
- 1<= wxids <=100
- '''
- api_url = f"{self.base_url}/v2/api/contacts/getBriefInfo"
- headers = {
- 'X-GEWE-TOKEN': token_id,
- 'Content-Type': 'application/json'
- }
- data = {
- "appId": app_id,
- "wxids":wxids # list 1<= wxids <=100
- }
- response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
- response_data = response.json()
- print(response_data)
- return response_data.get('data')
-
- def get_detail_info(self,token_id, app_id,wxids):
- '''
- 获取群/好友详细信息
- 1<= wxids <=20
- '''
- api_url = f"{self.base_url}/v2/api/contacts/getDetailInfo"
- headers = {
- 'X-GEWE-TOKEN': token_id,
- 'Content-Type': 'application/json'
- }
- data = {
- "appId": app_id,
- "wxids":wxids # list 1<= wxids <=20
- }
- response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
- response_data = response.json()
- print(response_data)
- return response_data.get('data')
-
- ############################### 消息模块 ###############################
- def post_text(self,token_id,app_id,to_wxid,content):
- api_url = f"{self.base_url}/v2/api/message/postText"
- headers = {
- 'X-GEWE-TOKEN': token_id,
- 'Content-Type': 'application/json'
- }
- data = {
- "appId": app_id,
- "toWxid": to_wxid,
- "content": content
- }
- response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
- response_object = response.json()
- return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
-
- def post_image(self,token_id,app_id,to_wxid,img_url):
- api_url = f"{self.base_url}/v2/api/message/postImage"
- headers = {
- 'X-GEWE-TOKEN': token_id,
- 'Content-Type': 'application/json'
- }
- data = {
- "appId": app_id,
- "toWxid": to_wxid,
- "imgUrl": img_url
- }
- response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
- response_object = response.json()
- return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
-
- def post_voice(self,token_id,app_id,to_wxid,voice_url,voice_duration):
- api_url = f"{self.base_url}/v2/api/message/postVoice"
- headers = {
- 'X-GEWE-TOKEN': token_id,
- 'Content-Type': 'application/json'
- }
- data = {
- "appId": app_id,
- "toWxid": to_wxid,
- "voiceUrl": voice_url,
- "voiceDuration":voice_duration
- }
- response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
- response_object = response.json()
- return response_object.get('ret',None),response_object.get('msg',None),response_object.get('data',None)
-
- def forward_image(self,token_id,app_id,to_wxid,aeskey,cdnthumburl,cdnthumblength,cdnthumbheight,cdnthumbwidth,length,md5):
- api_url = f"{self.base_url}/v2/api/message/forwardImage"
- headers = {
- 'X-GEWE-TOKEN': token_id,
- 'Content-Type': 'application/json'
- }
- data = {
- "appId": app_id,
- "toWxid": to_wxid,
- "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>"
-
- }
- response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
- response_object = response.json()
- return response_object.get('data',None),response_object.get('ret',None),response_object.get('msg',None)
-
- def add_contacts(self,token_id:str,app_id:str,scene:int,option:int,v3:str,v4:str,content:str):
- api_url = f"{self.base_url}/v2/api/contacts/addContacts"
- headers = {
- 'X-GEWE-TOKEN': token_id,
- 'Content-Type': 'application/json'
- }
- data = {
- "appId": app_id,
- "scene": scene,
- "option": option,
- "v3":v3,
- "v4":v4,
- "content":content
- }
- response = requests.post(url=api_url, headers=headers, data=json.dumps(data))
- response_object = response.json()
- print(response_object)
- return response_object.get('ret',None),response_object.get('msg',None)
-
-
-
- ############################### 下载模块 ###############################
- def download_audio_msg(self,token_id:str,app_id:str,msg_id: int, xml: str):
- data = {
- "appId": app_id,
- "msgId": msg_id,
- "xml": xml
- }
- print(json.dumps(data))
- headers = {
- 'X-GEWE-TOKEN': token_id,
- 'Content-Type': 'application/json'
- }
- # http://api.geweapi.com/gewe/v2/api/gewe/v2/api/message/downloadVoice
- # response = requests.post(f"{self.base_url}/v2/api/gewe/v2/api/message/downloadVoice", json=data, headers=headers)
- url='http://api.geweapi.com/gewe/v2/api/message/downloadVoice'
- # url='http://api.geweapi.com/gewe/v2/api/gewe/v2/api/message/downloadVoice'
- response = requests.post(f"{url}", json=data, headers=headers)
-
- if response.ok:
- data = response.json()
- print(data)
- if data['ret'] == 200:
- print("Gewe download audio msg successfully.")
- print(data['data']['fileUrl'])
- return data['data']['fileUrl']
- else:
- print("Gewe download audio msg in error.")
- return False
- else:
- return False
-
- def download_image_msg(self,token_id:str,app_id:str,xml: str):
- data = {
- "appId": app_id,
- "type": 2,
- "xml": xml
- }
- print(json.dumps(data))
-
- headers = {
- 'X-GEWE-TOKEN': token_id,
- 'Content-Type': 'application/json'
- }
-
- response = requests.post(f"{self.base_url}/v2/api/message/downloadImage", json=data, headers=headers)
- if response.ok:
- data = response.json()
- # print(data)
- if data['ret'] == 200:
- print("Gewe download image msg successfully.")
- print(data['data']['fileUrl'])
- return data['data']['fileUrl']
- else:
- print("Gewe download image msg in error.")
- return False
- else:
- return False
-
- def download_audio_file(fileUrl: str, file_name: str):
- # 定义保存文件的本地路径和文件名
- local_filename = f'./silk/{file_name}.silk'
-
- # 使用requests库的get方法获取文件内容
- response = requests.get(fileUrl, stream=True)
-
- # 检查请求是否成功
- if response.status_code == 200:
- # 打开文件以二进制写入模式
- with open(local_filename, 'wb') as f:
- # 逐块写入文件,通常使用1024字节的块大小
- for chunk in response.iter_content(1024):
- f.write(chunk)
- print(f"文件已成功下载到 {local_filename}")
- else:
- print(f"请求失败,状态码: {response.status_code}")
-
- ############################### 其他 ###############################
-
- def save_contacts_brief_to_cache(self, token_id, app_id, wxid, contacts_wxids: list):
- """
- 将联系人信息保存到 Redis 缓存。
- """
- # Redis 缓存的 key
- hash_key = f"__AI_OPS_WX__:CONTACTS_BRIEF:{wxid}"
-
- # 获取缓存中的数据
- cache_str = redis_helper.redis_helper.get_hash_field(hash_key, "data")
- cache = json.loads(cache_str) if cache_str else []
-
- if not cache:
- # 缓存为空,分批处理 contacts_wxids
- batch_size = 100
- for i in range(0, len(contacts_wxids), batch_size):
- batch = contacts_wxids[i:i + batch_size]
- friends_brief = self.get_brief_info(token_id, app_id, batch)
-
- friends_no_brief_wxid = [f['userName'] for f in friends_brief if not f["nickName"]]
- cache.extend(f for f in friends_brief if f["nickName"])
-
- if friends_no_brief_wxid:
- detailed_info = self.get_detail_info(token_id, app_id, friends_no_brief_wxid)
- cache.extend(detailed_info)
-
- # 更新缓存
- redis_helper.redis_helper.update_hash_field(hash_key, "data", json.dumps(cache, ensure_ascii=False))
- return
-
- # 缓存已存在,检查新联系人
- existing_usernames = {contact['userName'] for contact in cache}
- new_contacts_wxids = [wxid for wxid in contacts_wxids if wxid not in existing_usernames]
-
- # 如果有新联系人,分批获取详细信息并更新缓存
- if new_contacts_wxids:
- batch_size = 20
- for i in range(0, len(new_contacts_wxids), batch_size):
- batch = new_contacts_wxids[i:i + batch_size]
- detailed_info = self.get_detail_info(token_id, app_id, batch)
- cache.extend(detailed_info)
-
- redis_helper.redis_helper.update_hash_field(hash_key, "data", json.dumps(cache, ensure_ascii=False))
-
-
-
- def start():
- global wxchat
- # base_url = "http://192.168.88.11:2531"
- # wxchat = GeWeChat(base_url)
- wxchat = GeWeChatCom('http://api.geweapi.com/gewe')
-
|