Files
chat_rebot-connect-with-one…/scripts/qq_send_file.py
Claw 4068869217 feat(plugin): 集成 OpenClaw Bridge + QQ 操作技能套件
新增 openclaw_bridge 插件,实现 QQ ↔ OpenClaw Gateway 桥接:

src/process.py:
  - QQ 消息自动转发 OpenClaw Gateway
  - 内置安全检测(高危词、管理员白名单、MC指令过滤)
  - 高危词库外部化到 config.toml [security] section
  - 群昵称自动解析
  - Session 隔离(按群×用户)

config/openclawbridge/config.toml:
  - 插件配置模板(已脱敏:占位符替代个人信息)

scripts/qq_*.py (×15):
  - 消息发送/文件传输/群管理/好友管理
  - 信息查询/历史回溯/OCR/点赞
  - 全部脱敏(QQ号→YOUR_ADMIN_QQ, IP→YOUR_NAPCAT_HOST等)

skills/*/SKILL.md (×8):
  - AgentSkills 使用指导
  - qq-messenger / qq-management / qq-resolver / qq-napcat-extras
  - browser / file-process / mc-query / nix-helper

SKILL.md:
  - AgentSkill 入口,完整架构说明和部署步骤
2026-05-04 16:28:12 +08:00

177 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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_NAP CAT_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/files/{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()