| #!/bin/bash |
| set -e |
|
|
| echo ">>> 开始自动配置 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 配置文件已生成!" |
|
|
| |
| 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') |
|
|
| |
| 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: |
| |
| paipu_id = extract_paipu_id(msg) |
| |
| |
| |
| api_endpoint = HF_SPACE_URL.rstrip('/') + "/api/predict" |
| payload = {"data": [msg, hanchan]} |
| |
| |
| response = requests.post( |
| api_endpoint, |
| json=payload, |
| headers=HEADERS, |
| timeout=180 |
| ) |
| response.raise_for_status() |
| |
| |
| |
| response_json = response.json() |
| result_str = response_json.get("data", ["{}"])[0] |
| result_data = json.loads(result_str) |
| |
| |
| if "error" in result_data: |
| error_msg = str(result_data['error'])[:50] |
| Send(Reply(mid) + f"云端检讨失败,可能牌谱链接有误\n({error_msg}...)", uid, gid) |
| return |
| |
| |
| 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 |