QQBot / start.sh
ffzeroHua's picture
Update start.sh
7aaa3cd verified
Raw
History Blame Contribute Delete
8.25 kB
#!/bin/bash
set -e
echo ">>> 开始自动配置 NapCat 环境..."
# 1. 确保配置目录存在并写入 NapCat 配置文件
mkdir -p /app/napcat/config
cat << 'EOF' > /app/napcat/config/onebot11_3823042923.json
{
"network": {
"httpServers": [
{
"name": "API_Server",
"enable": true,
"port": 7667,
"host": "0.0.0.0",
"enableCors": true
}
],
"httpClients": [
{
"name": "Flask_Webhook",
"enable": true,
"url": "http://127.0.0.1:7668/"
}
]
}
}
EOF
echo ">>> NapCat 配置文件已生成!"
# 2. 自动生成精简版 Bot.py
echo ">>> 正在生成 Bot.py..."
cat << 'EOF' > /app/Bot.py
import json
import random
import urllib.parse
import threading
import html
import requests
import time
import os
from flask import Flask, request
from gradio_client import Client
app = Flask('kitakaze')
SELF = '3823042923'
API_BASE = 'http://127.0.0.1:7667'
HF_SPACE_URL = "https://hf.4z.autos/"
HF_TOKEN = os.getenv('HF_TOKEN')
# 如果你的 Hugging Face 仓库是 Private 的,必须在请求头里带上 Token 才能访问 API
HEADERS = {"Authorization": f"Bearer {HF_TOKEN}"} if HF_TOKEN else {}
try:
print("正在初始化 Hugging Face 云端连接...")
if not HF_TOKEN:
print("警告: 环境变量 HF_TOKEN 未设置!")
hf_client = Client(HF_SPACE_URL, token=HF_TOKEN)
print("Hugging Face 云端连接就绪!")
except Exception as e:
print(e)
def Reply(mid): return f'[CQ:reply,id={mid}]'
def Send(msg, uid, gid=None):
if not msg: return
if isinstance(msg, list): msg = random.choice(msg)
encoded_msg = urllib.parse.quote(str(msg))
try:
if gid:
requests.get(f'{API_BASE}/send_group_msg?group_id={gid}&message={encoded_msg}')
else:
requests.get(f'{API_BASE}/send_private_msg?user_id={uid}&message={encoded_msg}')
except Exception as e:
pass
def ContainsRoundInfo(s): return '局' in s or '本场' in s
def extract_paipu_id(paipu_url_or_msg):
"""在本地计算牌谱 ID,确保生成的链接与云端保存的文件名完全一致"""
match = re.search(r'log=([\w-]+)', paipu_url_or_msg)
if match:
return match.group(1)
return hashlib.md5(paipu_url_or_msg.encode('utf-8')).hexdigest()
def start_analyze(mid, uid, gid, msg, hanchan=False):
def analyze_task():
try:
# 1. 提取或生成本次请求对应的 ID
paipu_id = extract_paipu_id(msg)
# 2. 准备 HTTP 请求,目标是我们刚才用 FastAPI 写好的兼容接口
# 注意:如果 URL 结尾有斜杠则去掉,防止拼出双斜杠
api_endpoint = HF_SPACE_URL.rstrip('/') + "/api/predict"
payload = {"data": [msg, hanchan]}
# 3. 发送纯正的 POST 请求(设置超时时间,以防云端检讨太久假死)
response = requests.post(
api_endpoint,
json=payload,
headers=HEADERS,
timeout=180
)
response.raise_for_status() # 如果是 401/404/500 等 HTTP 错误会直接跳入 except
# 4. 解析后端返回的 JSON 数据
# 后端返回格式是 {"data": ["{真正的结果JSON字符串}"]}
response_json = response.json()
result_str = response_json.get("data", ["{}"])[0]
result_data = json.loads(result_str)
# 5. 业务错误处理
if "error" in result_data:
error_msg = str(result_data['error'])[:50]
Send(Reply(mid) + f"云端检讨失败,可能牌谱链接有误\n({error_msg}...)", uid, gid)
return
# 6. 构造最终要发送给用户的消息(不再需要把 result_data 写入本地磁盘)
overall_rating = result_data.get("overall_rating", 0.0)
final_msg = f'看完啦!\nhttps://online.4z.autos/?id={paipu_id}\n总体评分 {overall_rating}'
Send(Reply(mid) + final_msg, uid, gid)
except requests.exceptions.RequestException as e:
print(f"HTTP 通信出错: {e}")
Send(Reply(mid) + '有点不懂,改日再看\n(云端连接断开或超时)', uid, gid)
except Exception as e:
print(f"解析或调度出错: {e}")
Send(Reply(mid) + '有点不懂,改日再看\n(客户端处理异常)', uid, gid)
# 启动后台线程
thread = threading.Thread(target=analyze_task)
thread.daemon = True
thread.start()
def OnPoke(uid, gid):
result = random.choice(['?', '别戳了!', '轻点!', '!!', '?!', '(躲)', '(溜了)', '不要戳!', '(盯)', '……!'])
Send(result, uid, gid)
SELF_CALL = ['北风', 'kitakaze', f'[CQ:at,qq={SELF}]']
def DealWith(msg, uid, gid, mid):
called = any(calls in msg for calls in SELF_CALL) or gid is None
if not called: return
if '检讨' in msg:
if 'http' in msg and '://' in msg:
if 'xxxxxx' in msg:
Send(Reply(mid) + '天凤牌谱需要手动复制小局内容哦', uid, gid)
else:
Send(Reply(mid) + '吾辈琢磨琢磨(思考中)……', uid, gid)
hanchan = not ContainsRoundInfo(msg)
start_analyze(mid, uid, gid, msg, hanchan)
else:
Send(Reply(mid) + '请发送带有有效 http 链接的牌谱哦!', uid, gid)
return
result = random.choice(['?', '有事吗', '何事', '。?', '。', '说', '怎么了', '1', '在', '我在?', '什么事', '有事直说', '不在', '干嘛'])
Send(Reply(mid) + result, uid, gid)
@app.route('/', methods=["POST", "GET"])
def handle_events():
data = request.json
print(data)
if not data: return '{}'
post_type = data.get('post_type')
if post_type == 'message':
message_type = data.get('message_type')
mid = data.get('message_id')
msg = html.unescape(data.get('raw_message', ''))
uid = data.get('sender', {}).get('user_id')
if message_type == 'private':
threading.Thread(target=DealWith, args=(msg, uid, None, mid)).start()
elif message_type == 'group':
gid = data.get('group_id')
threading.Thread(target=DealWith, args=(msg, uid, gid, mid)).start()
elif post_type == 'notice' and data.get('notice_type') == 'notify' and data.get('sub_type') == 'poke':
if str(data.get('target_id')) == SELF:
OnPoke(data.get('user_id'), data.get('group_id'))
return '{}'
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=7668)
EOF
echo ">>> Bot.py 文件已生成!"
echo ">>> 检查并注入本地凭证..."
if [ -f "/app/qq_session.tar.gz" ]; then
echo "发现 QQ 凭证,正在恢复到系统..."
cd ~
tar -xzf /app/qq_session.tar.gz
fi
if [ -f "/app/napcat_config.tar.gz" ]; then
echo "发现 NapCat 配置文件,正在覆盖..."
cd /app
tar -xzf /app/napcat_config.tar.gz
fi
cd /app
sed -i 's|LD_PRELOAD=./libnapcat_launcher.so qq --no-sandbox|& -q 3823042923|' launcher.sh
cat launcher.sh
# ==========================================
# 启动你的进程
# ==========================================
echo ">>> 使用 screen 在后台启动 Python 机器人进程..."
screen -dmS qqbot bash -c "python3 /app/Bot.py"
echo ">>> 使用 screen 在后台启动官方 NapCat Launcher..."
screen -dmS napcat bash -c "cd /app && bash ./launcher.sh"
echo ">>> 初始化完毕!正在拉起 Web Shell..."
echo "========================================================="
echo "提示:打开 Web Shell 终端后,你可以输入以下命令:"
echo "查看 NapCat 二维码: screen -r napcat"
echo "查看 Bot 运行日志: screen -r qqbot"
echo "退出屏幕 (切回后台): 按下 Ctrl+A,然后按 D"
echo "========================================================="
exec uvicorn app:app --host 0.0.0.0 --port 7860