#!/usr/bin/env python3 """ QQ 主动发文件脚本 - 后台发送 原理: 1. 立即返回(fire-and-forget),不阻塞 agent 2. 后台独立进程:复制 → 删除旧文件 → 通过 NapCat API 发文件 3. 文件发送即完成,不再额外通知 agent 用法: python3 qq_send_file.py --private YOUR_ADMIN_QQ /path/to/file python3 qq_send_file.py --group YOUR_GROUP_ID --name "报告.txt" /tmp/report.txt python3 qq_send_file.py --group YOUR_GROUP_ID --image /tmp/screenshot.png """ import shutil import os import sys import time import json import argparse import subprocess import requests TARGET_DIR = "YOUR_SHARED_DIR" CQHTTP_URL = "http://YOUR_NAPCAT_HOST:25570" HISTORY_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".send_history.json") SCRIPT_PATH = os.path.abspath(__file__) GATEWAY_URL = "http://127.0.0.1:18789" GATEWAY_TOKEN = "YOUR_GATEWAY_TOKEN" def log(msg): sys.stderr.write(f"[qq_send_file] {msg}\n") def load_history(): try: if os.path.exists(HISTORY_FILE): with open(HISTORY_FILE, "r", encoding="utf-8") as f: return json.load(f) except Exception as e: log(f"读取历史失败: {e}") return {"files": []} def save_history(history): try: with open(HISTORY_FILE, "w", encoding="utf-8") as f: json.dump(history, f, ensure_ascii=False, indent=2) except Exception as e: log(f"保存历史失败: {e}") def cleanup_old_files(history): for old_file in history.get("files", []): if os.path.exists(old_file): try: os.remove(old_file) log(f"已删除旧文件: {old_file}") except Exception as e: log(f"删除旧文件失败: {old_file} - {e}") def _fmt_size(size): for unit in ["B", "KB", "MB", "GB"]: if size < 1024: return f"{size:.1f}{unit}" size /= 1024 return f"{size:.1f}TB" def notify_agent_via_gateway(api, target_id, display_name, src_size, ok): """已禁用。文件发送完成后不再通知 agent,减少 Gateway 负载。""" pass def background_work(api, target_id, src_path, display_name, is_image): """后台进程执行的完整工作流程""" try: if not os.path.exists(src_path): notify_agent_via_gateway(api, target_id, display_name, 0, False) return 1 src_size = os.path.getsize(src_path) # 1. 清理旧文件 history = load_history() cleanup_old_files(history) # 2. 复制到共享目录 ts = int(time.time()) basename = display_name or os.path.basename(src_path) target_filename = f"{ts}_{basename}" target_path = os.path.join(TARGET_DIR, target_filename) shutil.copy2(src_path, target_path) log(f"已复制: {target_path} ({src_size} bytes)") # 3. 发送文件 file_uri = f"file:///app/napcat/share/{target_filename}" if is_image: msg = f"[CQ:image,file={file_uri}]" else: msg = f"[CQ:file,file={file_uri},title={display_name or basename}]" if api == "group": url = f"{CQHTTP_URL}/send_group_msg" payload = {"group_id": int(target_id), "message": msg} else: url = f"{CQHTTP_URL}/send_private_msg" payload = {"user_id": int(target_id), "message": msg} resp = requests.post(url, json=payload, timeout=120) data = resp.json() if data.get("status") == "ok": msg_id = data.get("data", {}).get("message_id", "unknown") log(f"发送成功, message_id={msg_id}") save_history({"files": [target_path]}) notify_agent_via_gateway(api, target_id, display_name, src_size, True) else: log(f"发送失败: {json.dumps(data, ensure_ascii=False)}") notify_agent_via_gateway(api, target_id, display_name, src_size, False) except Exception as e: log(f"后台工作异常: {e}") try: notify_agent_via_gateway(api, target_id, display_name, 0, False) except: pass return 0 def main(): parser = argparse.ArgumentParser(description="发送 QQ 文件(后台发送)") target = parser.add_mutually_exclusive_group(required=True) target.add_argument("--group", type=str, help="目标群号") target.add_argument("--private", type=str, help="目标用户 QQ 号") parser.add_argument("--name", type=str, help="显示的文件名") parser.add_argument("--image", action="store_true", help="作为图片发送") parser.add_argument("file_path", help="本地文件路径") args = parser.parse_args() if not os.path.exists(args.file_path): print(f"❌ 文件不存在: {args.file_path}") sys.exit(1) api = "group" if args.group else "private" target_id = args.group or args.private display_name = args.name or os.path.basename(args.file_path) subprocess.Popen( [sys.executable, SCRIPT_PATH, "--bgworker", api, target_id, args.file_path, display_name, str(int(args.image))], start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL, ) print("✅ 已触发后台发送") if __name__ == "__main__": if "--bgworker" in sys.argv: _, api, target_id, file_path, display_name, is_image_str = sys.argv[1:] is_image = is_image_str == "1" rc = background_work(api, target_id, file_path, display_name, is_image) sys.exit(rc) else: main()