WeChatAgents / app.py
AiAgents's picture
Upload folder using huggingface_hub
d861292 verified
raw
history blame contribute delete
No virus
15 kB
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()
@app.websocket("/ws/{wxid}/{securityCode}")
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)