完整 AgentSkill:词库外部化 + 全部脚本/Skill 脱敏上传

- 高危词库从 process.py 抽到 config.toml [security] section
- process.py 改为 _get_high_risk_words() 动态加载(配置优先)
- 新增 15 个 QQ 操作脚本(已脱敏:QQ号/IP/Token → 占位符)
- 新增 8 个 SKILL.md(qq-messenger/management/resolver/napcat-extras 等)
- 新增 SKILL.md 入口(完整部署方案文档)
- 保留 SDK 框架(plugin_modules.py, file_store_api.py, package.py 等)
This commit is contained in:
Claw
2026-05-04 16:23:59 +08:00
parent d7d33c39a4
commit e1fbd31c64
35 changed files with 5058 additions and 276 deletions

176
scripts/qq_send_file.py Normal file
View File

@ -0,0 +1,176 @@
#!/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()