Spaces:
Running
Running
from distributed_websocket import WebSocketProxy | |
from fastapi import FastAPI, WebSocket | |
import uvicorn | |
import io | |
import os | |
from PIL import Image | |
import numpy as np | |
import gradio as gr | |
import requests | |
import time | |
import json | |
import base64 | |
from websockets.sync.client import connect | |
from websockets.exceptions import InvalidStatusCode | |
SERCIVE_HOST = os.environ.get('SERCIVE_HOST') | |
GETQRCODE_ENDPOINT = os.environ.get('GETQRCODE_ENDPOINT') | |
PUSHLOGIN_ENDPOINT = os.environ.get('PUSHLOGIN_ENDPOINT') | |
app = FastAPI() | |
async def websocket_endpoint(websocket: WebSocket, wxid: str, securityCode: str): | |
try: | |
uri = "ws://{}/ws?wxid={}&securityCode={}".format( | |
SERCIVE_HOST, wxid, securityCode) | |
await websocket.accept() | |
ws_proxy = WebSocketProxy(websocket, uri) | |
await ws_proxy() | |
except InvalidStatusCode as e: | |
if e.status_code == 400: | |
await websocket.close(reason="安全码校验失败") | |
except Exception as e: | |
print("Exception", e) | |
def get_login_qrcode(proxy_input, wxid_input): | |
if proxy_input == "": | |
gr.Warning("代理不能为空") | |
return None, None | |
"""Fetches and Login QRCode""" | |
try: | |
response = requests.get( | |
GETQRCODE_ENDPOINT.format(SERCIVE_HOST, proxy_input)) | |
except Exception as e: | |
print("Exception", e) | |
gr.Warning("服务器响应错误") | |
return None, None | |
if response.status_code == 404: | |
gr.Warning("服务器响应错误") | |
return None, None | |
resp = response.json() | |
Ret = resp["CgiBaseResponse"]["Ret"] | |
if Ret != 0: | |
gr.Warning(resp["CgiBaseResponse"]['ErrMsg']) | |
return None, None | |
ImgBuf = resp["ResponseData"]["ImgBuf"] | |
ImgBuf = base64.standard_b64decode(ImgBuf) | |
image = Image.open(io.BytesIO(ImgBuf)) | |
Uuid = resp["ResponseData"]["Uuid"] | |
yield image, {"__type__": "update", "info": "成功获取二维码,请在2分钟内完成扫码", "value": ""} | |
uri = "ws://{}/ws?uuid={}".format(SERCIVE_HOST, Uuid) | |
with connect(uri) as websocket: | |
while True: | |
message = websocket.recv() | |
print(f"<{message}") | |
EventJson = json.loads(message) | |
EventName = EventJson['CurrentPacket']['Data']['EventName'] | |
if EventName == 'ON_EVENT_LOGIN_QRCODE_STATUS': | |
QRcodeStatus = EventJson['CurrentPacket']['Data']['QRcodeStatus'] | |
if QRcodeStatus['Status'] == 1: # 已经扫码 | |
yield QRcodeStatus['HeadImgUrl'], {"__type__": "update", "info": "用户, {} 已扫码,未确认!".format(QRcodeStatus['NickName'])} | |
if QRcodeStatus['Status'] == 4: # 已取消扫码 | |
yield QRcodeStatus['HeadImgUrl'], {"__type__": "update", "info": "用户, {} 已经消扫码!".format(QRcodeStatus['NickName'])} | |
break | |
if QRcodeStatus['Status'] == -2007: # 二维码已超时 | |
yield None, {"__type__": "update", "info": "二维码已超时,请重新获取二维码!"} | |
break | |
if QRcodeStatus['Status'] == 2: # 二维码确认登录 | |
yield QRcodeStatus['HeadImgUrl'], {"__type__": "update", "info": "用户, {} 已扫码,已确认!".format(QRcodeStatus['NickName']), "value": QRcodeStatus['Username']} | |
break | |
def push_login_qrcode(wxid_input): | |
if wxid_input == "": | |
gr.Warning("Bot Wxid不能为空") | |
return None | |
try: | |
response = requests.get( | |
PUSHLOGIN_ENDPOINT.format(SERCIVE_HOST, wxid_input)) | |
except Exception as e: | |
print("Exception", e) | |
gr.Warning("服务器响应错误") | |
return None | |
if response.status_code == 404: | |
gr.Warning("服务器响应错误") | |
return None | |
resp = response.json() | |
if resp == None: | |
gr.Warning("服务器响应错误,请检查代理") | |
return None, None | |
Ret = resp["CgiBaseResponse"]["Ret"] | |
if Ret == 0: | |
gr.Info("推送登录成功!请查看手机确认") | |
return None | |
return None | |
with gr.Blocks(css=".center-container{display:flex;justify-content:center;align-items:center;}.row-container{width:300px;}", theme=gr.themes.Soft(), title="WeChatAgents") as demo: | |
gr.Markdown("# ☁️Cloud☁️ 🔮WeChatAgents🔮", | |
elem_classes=["center-container"]) | |
with gr.Tab("介绍"): | |
gr.Markdown(f""" | |
### 为Bot爱好者,搭建免费的微信Bot Agents平台 ,万物皆Agent! | |
请合理合法使用,简单托管,无任何复杂配置环境,配置好`SOCKS5代理`登录成功后,`WebSocket`对接实时消息和调用功能。 | |
尽量不要用自己的主号、或大号,追风号!S5只是绕过风控的一个环节,且S5必须要运行在用户的本地环境,云服务不可🙅 | |
Gradio App 开源,后端核心,闭源。开源有开源的好处,比如:共同协作,Bug追踪。闭源可以躲避Ai风控追踪,代码审查,等等。不喜勿用。用户无需担心隐私问题,风控问题。 | |
当然我们也不会,采集用户隐私,仅提供安全,稳定的Bot服务,开放必要的API能力, | |
提供发送✅音频✅的能力,暂不提供❌发送小视频❌的能力(预防动作片的导演)。 | |
### 特点 :全平台运行,只要有Python 环境即可,无需复杂配置。 | |
## 登录流程 | |
<div align="left"> | |
<img src="https://z.wiki/autoupload/20240714/7P3R/1542X1461/Untitled.png?type=ha" width="30%" alt=""/> | |
</div> | |
--- | |
## 开放能力 | |
| 平台版本 | 支持情况 | 登录类型 | 支持情况 | 消息结构 | 支持情况 | 开放能力 | 支持情况 | 开放事件 | 支持情况 | | |
| ------- | :------: | ----------------- | :------: | :--------------- | :------: | :--------------- | :------: | :--------------- | :------: | | |
| Windows | 🟢 | 扫码登录 | 🟢 | 接收文字 | 🟢 | 发送文字 | ✅ | 收到好友请求 | 🟢 | | |
| macOS | 🔴 | 推送登录 | 🟢 | 接收表情 | 🟢 | 发送表情 | ✅ | 收到拍一拍 | 🟢 | | |
| Linux | 🔴 | 账号密码登录 | 🔴 | 接收图片 | 🟢 | 发送图片 | ✅ | 机器人离线 | 🟢 | | |
| | | | | 接收APP | 🟢 | 发送APP | ✅ | 实时消息事件 | 🟢 | | |
| | | | | 接收语音 | 🟢 | 发送语音 | ✅ | 实时朋友圈事件 | 🟢 | | |
| | | | | 接收视频 | 🟢 | ~~通过好友请求~~ | ❌ | 邀请进群事件 | 🟢 | | |
| | | | | | | ~~邀请成员入群~~ | ❌ | | | | |
| | | | | | | ~~发送视频~~ | ❌ | | | | |
| | | | | | | 发送CDN图片 | ✅ | | | | |
| | | | | | | 发送CDN文件 | ✅ | | | | |
| | | | | | | 下载CDN图片 | ✅ | | | | |
| | | | | | | 下载CDN文件 | ✅ | | | | |
| | | | | | | ~~小程序~~ | ❌ | | | | |
| | | | | | | 撤回消息 | ✅ | | | | |
| | | | | | | 拍一拍 | ✅ | | | | |
""") | |
pass | |
with gr.Tab("风控须知"): | |
gr.Markdown(f""" | |
### 异地风控 | |
- 账号登录地和注册地不符,登录本地搭建S5代理服务解决,登录成功后可释放代理。 | |
### 新设备风控 | |
- 账号不在常用设备登录,新设备会触发24小时强制下线的风控,次日 **推送登录** 成功后可,稳定长期在线。 | |
### 频率风控 | |
- 发消息频率控制,尽量拟人操作。 | |
""") | |
with gr.Tab("搭建流程"): | |
gr.Markdown(f""" | |
### 1️⃣ SOCKS5代理搭建 | |
通过FRP搭建**本地**SOCKS5代理,云服务器搭建无效且容易风控。[[下载对应平台二进制文件](https://github.com/fatedier/frp/releases)] | |
<br>将下面的配置命名为frpc.ini和下载好的二进制程序放到同一个目录。 | |
<br>尽量用国内的FRP节点 | |
<br>假设公益FRP 服务器地址为 `frp.104300.xyz` 链接token为www.126126.xyz | |
```ini | |
[common] | |
server_addr = frp.104300.xyz | |
server_port = 7000 | |
token = www.126126.xyz | |
[plugin_socks5] #随机服务名 不可冲突 | |
type = tcp | |
remote_port = 16005 #远端端口 随机 不冲突即可 范围 10001 - 50000 | |
plugin = socks5 | |
plugin_user = agents | |
plugin_passwd = agents | |
use_encryption = true | |
use_compression = true | |
``` | |
<br>[[公益FRP](https://frp.104300.xyz/)] 自行搜索公益FRP有很多需要注册的,用几分钟就完事了。 | |
Windows 执行cmd命令 | |
```shell | |
frpc.exe -c =frpc.ini | |
``` | |
Linux/macOS 执行cmd命令 | |
```shell | |
chmod +x ./frpc && ./frpc -c =frpc.ini | |
``` | |
输出 **start proxy success**即为启用本地代理成功 S5 代理IP frp.104300.xyz:16005 **用户名密码固定为agents** 16005远端端口在要求的范围内即可10001 - 50000 只要不冲突就可以。 | |
<br>将代理IP frp.104300.xyz:16005 填入到登录界面的 SOCKS5代理文本框即可,登录成功可释放代理,结束进程即可。 | |
### 2️⃣ 首次登录 | |
- 首次登录 填入代理IP后点击 **获取二维码** 出现二维码成功后,说明代理成功可用,可以确认扫码并登录。 | |
### 3️⃣ 获得安全码 | |
- 安全码用于WebSocket连接,Bot登录成功后30~60秒 回复 **文件传输助手** 指令 `SecurityCode` 即可获得9位安全码 | |
<div align="left"> | |
<img src="https://z.wiki/autoupload/20240717/X93s/591X1280/photo_2024-07-17_08-15-56.jpg?type=ha" width="20%" alt=""/> | |
</div> | |
### 🟣 推送登录 | |
- 只要登录成功后,后续风控或掉线离线,将自己的Bot Wxid 填入登录界面的登录信息中,并点击推送登录,手机会收到弹窗,确认即可使机器人再次上线。24小时后掉线就用此接口重新使机器人上线。 | |
- 推送登录用的好就可以使机器人,持续在线,实践证明 新设备上号,仅需一次(上来就封的号除外)推送登录就可以使账号长时间在线。 | |
- 推送登录只是为了正确的找到之前登录成功过的设备信息。 | |
### 🔰 收发实时消息 | |
- 稳定在线后,可以通过 **WebSocket** 进行收发消息和功能调用了 | |
- 连接端点 **wss://aiagents-wechatagents.hf.space/ws/wxid_n5kf2grjaxm110/123-321-087** ws后面的参数为Bot的Wxid/安全码 | |
""") | |
with gr.Tab("登录"): | |
with gr.Row() as row: | |
with gr.Column(elem_classes=["center-container"]): | |
with gr.Row(elem_classes=["row-container"]) as row: | |
proxy_input = gr.Textbox( | |
label="SOCKS5代理", info="必填", max_lines=1) | |
wxid_input = gr.Textbox( | |
label="登录信息", placeholder="推送登录Wxid不为空", info="info", max_lines=1, elem_id="my-textbox", show_copy_button=True) | |
image_input = gr.Image(show_label=False, show_download_button=False, sources=None, | |
elem_id="qr_image") | |
with gr.Row(): | |
get_qrcode_button = gr.Button( | |
"获取二维", min_width=0, variant="primary") | |
push_login_button = gr.Button( | |
"推送登录", min_width=0, variant="primary") | |
with gr.Tab("Demo"): | |
gr.Markdown(f""" | |
#### 自行查阅 [[demo.py](https://huggingface.co/spaces/AiAgents/WeChatAgents/blob/main/demo.py)] 自行完善同步请求,和其他一些细节,比如自动重连等等。 | |
""") | |
with gr.Tab("开放Api"): | |
gr.Markdown(f""" | |
# Websocket 端点 wss://aiagents-wechatagents.hf.space/ws/wxid/安全码 | |
""") | |
gr.Markdown(""" | |
{} | |
""".format(open("Api.md").read())) | |
get_qrcode_button.click( | |
get_login_qrcode, inputs=[proxy_input, wxid_input], outputs=[image_input, wxid_input]) | |
push_login_button.click( | |
push_login_qrcode, inputs=[wxid_input], outputs=[image_input]) | |
# if __name__ == "__main__": | |
# demo.queue(max_size=1000, default_concurrency_limit=1000).launch( | |
# inbrowser=True) | |
if __name__ == '__main__': | |
demo.queue(max_size=1000, default_concurrency_limit=1000) | |
app = gr.mount_gradio_app(app, demo, path="/") | |
uvicorn.run(app, host='0.0.0.0', port=7860) | |