新增 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 入口,完整架构说明和部署步骤
592 lines
24 KiB
Python
592 lines
24 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
QQ 群综合管理 — 通过 NapCat (OneBot v11) HTTP API
|
||
|
||
支持操作分类:
|
||
[信息查询] group-list | group-info | member-list | member-info | at-all-remain
|
||
[成员管理] set-card | set-admin | set-title | kick | ban | unban
|
||
[群设置] rename | mute-all | set-portrait
|
||
[文件操作] list-files | folder-create | file-url
|
||
[消息操作] msg-history | recall | pin-msg
|
||
[系统操作] leave | pending-requests
|
||
|
||
用法:
|
||
python3 qq_group_manage.py group-list
|
||
python3 qq_group_manage.py group-info --gid 123456
|
||
python3 qq_group_manage.py member-list --gid 123456
|
||
python3 qq_group_manage.py member-info --gid 123456 --uid 789
|
||
python3 qq_group_manage.py set-card --gid 123456 --uid 789 --card "新昵称"
|
||
python3 qq_group_manage.py kick --gid 123456 --uid 789
|
||
python3 qq_group_manage.py ban --gid 123456 --uid 789 --minutes 10
|
||
python3 qq_group_manage.py unban --gid 123456 --uid 789
|
||
python3 qq_group_manage.py at-all-remain --gid 123456
|
||
python3 qq_group_manage.py msg-history --gid 123456 --count 10
|
||
python3 qq_group_manage.py recall --mid 123456
|
||
python3 qq_group_manage.py pin-msg --mid 123456
|
||
python3 qq_group_manage.py list-files --gid 123456
|
||
python3 qq_group_manage.py list-files --gid 123456 --folder_id xxx
|
||
python3 qq_group_manage.py file-url --gid 123456 --file_id xxx
|
||
python3 qq_group_manage.py rename --gid 123456 --name "新群名"
|
||
python3 qq_group_manage.py mute-all --gid 123456 --enable true
|
||
python3 qq_group_manage.py leave --gid 123456
|
||
python3 qq_group_manage.py pending-requests
|
||
|
||
输出: 所有操作可用 --json 参数输出 JSON 格式
|
||
"""
|
||
|
||
import json
|
||
import sys
|
||
import os
|
||
import argparse
|
||
import requests
|
||
from datetime import datetime
|
||
|
||
CQHTTP_URL = "http://YOUR_NAP CAT_HOST:25570"
|
||
|
||
|
||
def api_call(endpoint: str, params: dict = None) -> dict:
|
||
"""调用 NapCat HTTP API"""
|
||
if params is None:
|
||
params = {}
|
||
resp = requests.post(f"{CQHTTP_URL}/{endpoint}", json=params, timeout=15)
|
||
data = resp.json()
|
||
return data
|
||
|
||
|
||
def api_ok(data: dict) -> bool:
|
||
"""检查 API 返回是否成功"""
|
||
return data.get("status") == "ok" and data.get("retcode") == 0
|
||
|
||
|
||
def handle_api_error(data: dict, endpoint: str) -> str:
|
||
"""格式化 API 错误信息"""
|
||
msg = data.get("message", "") or data.get("wording", "") or "未知错误"
|
||
return f"❌ {endpoint} 失败: {msg}"
|
||
|
||
|
||
# ==================== 信息查询 ====================
|
||
|
||
def cmd_group_list(json_output: bool):
|
||
"""列出所有已加入的群"""
|
||
data = api_call("get_group_list")
|
||
if not api_ok(data):
|
||
return {"ok": False, "error": handle_api_error(data, "get_group_list")}
|
||
groups = data.get("data", [])
|
||
if json_output:
|
||
return {"ok": True, "data": groups}
|
||
lines = [f"📋 共 {len(groups)} 个群\n"]
|
||
for g in sorted(groups, key=lambda x: x.get("group_id", 0)):
|
||
gid = g.get("group_id", "?")
|
||
name = g.get("group_name", "?")
|
||
mc = g.get("member_count", "?")
|
||
mm = g.get("max_member_count", "?")
|
||
remark = g.get("group_remark", "")
|
||
remark_str = f" [{remark}]" if remark else ""
|
||
lines.append(f" [{gid}] {name}{remark_str} — {mc}/{mm} 人")
|
||
return {"ok": True, "text": "\n".join(lines)}
|
||
|
||
|
||
def cmd_group_info(gid: int, json_output: bool):
|
||
"""获取群详细信息"""
|
||
data = api_call("get_group_info", {"group_id": gid})
|
||
if not api_ok(data):
|
||
return {"ok": False, "error": handle_api_error(data, "get_group_info")}
|
||
info = data.get("data", {})
|
||
if json_output:
|
||
return {"ok": True, "data": info}
|
||
shutdown = "🟢 全员禁言中" if info.get("group_all_shut") else "🔊 全员可发言"
|
||
lines = [
|
||
f"📊 群信息",
|
||
f" 群名: {info.get('group_name', '?')}",
|
||
f" 群号: {info.get('group_id', '?')} ({info.get('group_remark', '')})",
|
||
f" 成员: {info.get('member_count', '?')}/{info.get('max_member_count', '?')}",
|
||
f" 状态: {shutdown}",
|
||
]
|
||
return {"ok": True, "text": "\n".join(lines)}
|
||
|
||
|
||
def cmd_member_list(gid: int, json_output: bool):
|
||
"""列出群成员"""
|
||
data = api_call("get_group_member_list", {"group_id": gid})
|
||
if not api_ok(data):
|
||
return {"ok": False, "error": handle_api_error(data, "get_group_member_list")}
|
||
members = data.get("data", [])
|
||
if json_output:
|
||
return {"ok": True, "data": members}
|
||
# 按角色排序: owner > admin > member
|
||
role_order = {"owner": 0, "admin": 1, "member": 2}
|
||
members.sort(key=lambda m: (role_order.get(m.get("role", "member"), 3), m.get("user_id", 0)))
|
||
lines = [f"👥 群成员 ({len(members)} 人)\n"]
|
||
role_icons = {"owner": "👑", "admin": "🛡️", "member": "👤"}
|
||
for m in members:
|
||
uid = m.get("user_id", "?")
|
||
name = m.get("card", "") or m.get("nickname", "")
|
||
role = m.get("role", "member")
|
||
icon = role_icons.get(role, "👤")
|
||
lines.append(f" {icon} {name} ({uid})")
|
||
return {"ok": True, "text": "\n".join(lines)}
|
||
|
||
|
||
def cmd_member_info(gid: int, uid: int, json_output: bool):
|
||
"""获取单个成员信息"""
|
||
data = api_call("get_group_member_info", {"group_id": gid, "user_id": uid})
|
||
if not api_ok(data):
|
||
return {"ok": False, "error": handle_api_error(data, "get_group_member_info")}
|
||
info = data.get("data", {})
|
||
if json_output:
|
||
return {"ok": True, "data": info}
|
||
role_names = {"owner": "群主", "admin": "管理员", "member": "成员"}
|
||
join_time = datetime.fromtimestamp(info.get("join_time", 0)).strftime("%Y-%m-%d %H:%M")
|
||
lines = [
|
||
f"👤 成员信息",
|
||
f" QQ: {info.get('user_id', '?')}",
|
||
f" 昵称: {info.get('nickname', '?')}",
|
||
f" 群名片: {info.get('card') or '无'}",
|
||
f" 角色: {role_names.get(info.get('role', 'member'), '?')}",
|
||
f" 头衔: {info.get('title') or '无'}",
|
||
f" 入群时间: {join_time}",
|
||
f" 禁言至: {'是' if info.get('shut_up_timestamp', 0) > 0 else '否'}",
|
||
]
|
||
return {"ok": True, "text": "\n".join(lines)}
|
||
|
||
|
||
def cmd_at_all_remain(gid: int, json_output: bool):
|
||
"""查询 @全体成员 剩余次数"""
|
||
data = api_call("get_group_at_all_remain", {"group_id": gid})
|
||
if not api_ok(data):
|
||
return {"ok": False, "error": handle_api_error(data, "get_group_at_all_remain")}
|
||
info = data.get("data", {})
|
||
if json_output:
|
||
return {"ok": True, "data": info}
|
||
can = "可" if info.get("can_at_all") else "不可"
|
||
lines = [
|
||
f"📢 @全体成员 剩余",
|
||
f" 状态: {can}使用",
|
||
f" 群剩余: {info.get('remain_at_all_count_for_group', '?')} 次",
|
||
f" 你剩余: {info.get('remain_at_all_count_for_uin', '?')} 次",
|
||
]
|
||
return {"ok": True, "text": "\n".join(lines)}
|
||
|
||
|
||
# ==================== 成员管理 ====================
|
||
|
||
def cmd_set_card(gid: int, uid: int, card: str, json_output: bool):
|
||
"""设置群名片(管理员或群主权限)"""
|
||
data = api_call("set_group_card", {"group_id": gid, "user_id": uid, "card": card})
|
||
if not api_ok(data):
|
||
return {"ok": False, "error": handle_api_error(data, "set_group_card")}
|
||
if json_output:
|
||
return {"ok": True, "data": data}
|
||
return {"ok": True, "text": f"✅ 已设置 {uid} 的群名片为: {card}"}
|
||
|
||
|
||
def cmd_set_admin(gid: int, uid: int, enable: bool, json_output: bool):
|
||
"""设置/取消管理员(仅群主有权限)"""
|
||
data = api_call("set_group_admin", {"group_id": gid, "user_id": uid, "enable": enable})
|
||
if not api_ok(data):
|
||
return {"ok": False, "error": handle_api_error(data, "set_group_admin")}
|
||
action = "设为管理员" if enable else "取消管理员"
|
||
if json_output:
|
||
return {"ok": True, "data": data}
|
||
return {"ok": True, "text": f"✅ 已{action} ({uid})"}
|
||
|
||
|
||
def cmd_set_title(gid: int, uid: int, title: str, json_output: bool):
|
||
"""设置群头衔(管理员或群主权限)"""
|
||
data = api_call("set_group_special_title",
|
||
{"group_id": gid, "user_id": uid, "special_title": title})
|
||
if not api_ok(data):
|
||
return {"ok": False, "error": handle_api_error(data, "set_group_special_title")}
|
||
if json_output:
|
||
return {"ok": True, "data": data}
|
||
return {"ok": True, "text": f"✅ 已设置 {uid} 的头衔为: {title}"}
|
||
|
||
|
||
def cmd_kick(gid: int, uid: int, reject_add: bool, json_output: bool):
|
||
"""踢出群成员(管理员或群主权限)"""
|
||
data = api_call("set_group_kick",
|
||
{"group_id": gid, "user_id": uid, "reject_add_request": reject_add})
|
||
if not api_ok(data):
|
||
return {"ok": False, "error": handle_api_error(data, "set_group_kick")}
|
||
add_str = "并拒绝加群申请" if reject_add else ""
|
||
if json_output:
|
||
return {"ok": True, "data": data}
|
||
return {"ok": True, "text": f"✅ 已踢出 {uid}{add_str}"}
|
||
|
||
|
||
def cmd_ban(gid: int, uid: int, minutes: int, json_output: bool):
|
||
"""禁言成员(管理员或群主权限)。minutes=0 解禁"""
|
||
data = api_call("set_group_ban",
|
||
{"group_id": gid, "user_id": uid, "duration": minutes * 60})
|
||
if not api_ok(data):
|
||
return {"ok": False, "error": handle_api_error(data, "set_group_ban")}
|
||
if minutes <= 0:
|
||
return {"ok": True, "text": f"✅ 已解除 {uid} 的禁言"}
|
||
mins_str = f"{minutes}分钟"
|
||
if minutes >= 1440:
|
||
mins_str = f"{minutes//1440}天{minutes%1440//60}小时"
|
||
elif minutes >= 60:
|
||
mins_str = f"{minutes//60}小时{minutes%60}分钟"
|
||
if json_output:
|
||
return {"ok": True, "data": data}
|
||
return {"ok": True, "text": f"✅ 已禁言 {uid} ({mins_str})"}
|
||
|
||
|
||
# ==================== 群设置 ====================
|
||
|
||
def cmd_rename(gid: int, name: str, json_output: bool):
|
||
"""修改群名称(管理员或群主权限)"""
|
||
data = api_call("set_group_name", {"group_id": gid, "group_name": name})
|
||
if not api_ok(data):
|
||
return {"ok": False, "error": handle_api_error(data, "set_group_name")}
|
||
if json_output:
|
||
return {"ok": True, "data": data}
|
||
return {"ok": True, "text": f"✅ 群名已改为: {name}"}
|
||
|
||
|
||
def cmd_mute_all(gid: int, enable: bool, json_output: bool):
|
||
"""全员禁言(管理员或群主权限)"""
|
||
data = api_call("set_group_whole_ban", {"group_id": gid, "enable": enable})
|
||
if not api_ok(data):
|
||
return {"ok": False, "error": handle_api_error(data, "set_group_whole_ban")}
|
||
action = "全员禁言" if enable else "解除全员禁言"
|
||
if json_output:
|
||
return {"ok": True, "data": data}
|
||
return {"ok": True, "text": f"✅ 已{action}"}
|
||
|
||
|
||
def cmd_set_portrait(gid: int, file_path: str, json_output: bool):
|
||
"""设置群头像(管理员或群主权限)"""
|
||
data = api_call("set_group_portrait", {"group_id": gid, "file": file_path})
|
||
if not api_ok(data):
|
||
return {"ok": False, "error": handle_api_error(data, "set_group_portrait")}
|
||
if json_output:
|
||
return {"ok": True, "data": data}
|
||
return {"ok": True, "text": f"✅ 群头像已更换"}
|
||
|
||
|
||
# ==================== 文件操作 ====================
|
||
|
||
def cmd_list_files(gid: int, folder_id: str, json_output: bool):
|
||
"""列出群文件"""
|
||
params = {"group_id": gid}
|
||
if folder_id:
|
||
params["folder_id"] = folder_id
|
||
data = api_call("get_group_files_by_folder", params)
|
||
if not api_ok(data):
|
||
return {"ok": False, "error": handle_api_error(data, "get_group_files_by_folder")}
|
||
info = data.get("data", {})
|
||
files = info.get("files", [])
|
||
folders = info.get("folders", [])
|
||
if json_output:
|
||
return {"ok": True, "data": info}
|
||
lines = [f"📁 群文件 "]
|
||
if folder_id:
|
||
lines[0] += f"(文件夹: {folder_id})"
|
||
if not files and not folders:
|
||
lines.append(" (空)")
|
||
if folders:
|
||
lines.append(f"\n📂 文件夹 ({len(folders)}):")
|
||
for f in folders:
|
||
fid = f.get("folder_id", "?")
|
||
fname = f.get("folder_name", "?")
|
||
fcnt = f.get("total_file_count", 0)
|
||
lines.append(f" 📁 {fname} (id:{fid[:12]}..., {fcnt}文件)")
|
||
if files:
|
||
lines.append(f"\n📄 文件 ({len(files)}):")
|
||
for f in files:
|
||
fn = f.get("file_name", "?")
|
||
fs = f.get("file_size", 0)
|
||
fsize = f"{fs/1024/1024:.1f}MB" if fs > 1024*1024 else f"{fs/1024:.1f}KB"
|
||
fid = f.get("file_id", "?")
|
||
lines.append(f" 📄 {fn} ({fsize}) id:{fid[:12]}...")
|
||
return {"ok": True, "text": "\n".join(lines)}
|
||
|
||
|
||
def cmd_file_url(gid: int, file_id: str, json_output: bool):
|
||
"""获取文件下载链接"""
|
||
data = api_call("get_group_file_url", {"group_id": gid, "file_id": file_id})
|
||
if not api_ok(data):
|
||
return {"ok": False, "error": handle_api_error(data, "get_group_file_url")}
|
||
info = data.get("data", {})
|
||
if json_output:
|
||
return {"ok": True, "data": info}
|
||
url = info.get("url", "?")
|
||
return {"ok": True, "text": f"🔗 下载链接: {url}"}
|
||
|
||
|
||
def cmd_create_folder(gid: int, name: str, parent: str, json_output: bool):
|
||
"""创建群文件文件夹(管理员或群主权限)"""
|
||
data = api_call("create_group_file_folder",
|
||
{"group_id": gid, "name": name, "parent_folder_id": parent})
|
||
if not api_ok(data):
|
||
# 检查业务错误
|
||
result = data.get("data", {}).get("result", {})
|
||
msg = result.get("clientWording", "")
|
||
if msg:
|
||
return {"ok": False, "error": f"❌ {msg}"}
|
||
return {"ok": False, "error": handle_api_error(data, "create_group_file_folder")}
|
||
if json_output:
|
||
return {"ok": True, "data": data}
|
||
return {"ok": True, "text": f"✅ 文件夹「{name}」已创建"}
|
||
|
||
|
||
# ==================== 消息操作 ====================
|
||
|
||
def cmd_msg_history(gid: int, count: int, json_output: bool):
|
||
"""获取群消息历史"""
|
||
data = api_call("get_group_msg_history",
|
||
{"group_id": gid, "count": min(count, 50)})
|
||
if not api_ok(data):
|
||
return {"ok": False, "error": handle_api_error(data, "get_group_msg_history")}
|
||
msgs = data.get("data", {}).get("messages", [])
|
||
if json_output:
|
||
return {"ok": True, "data": msgs}
|
||
if not msgs:
|
||
return {"ok": True, "text": "📭 最近没有消息"}
|
||
lines = [f"📜 最近 {len(msgs)} 条消息\n"]
|
||
for m in reversed(msgs):
|
||
uid = m.get("user_id", "?")
|
||
text = m.get("message", "")
|
||
ts = datetime.fromtimestamp(m.get("time", 0)).strftime("%H:%M")
|
||
# 简化消息内容
|
||
if isinstance(text, list):
|
||
parts = []
|
||
for item in text:
|
||
if isinstance(item, dict):
|
||
t = item.get("type", "")
|
||
d = item.get("data", {})
|
||
if t == "text":
|
||
parts.append(d.get("text", ""))
|
||
elif t == "image":
|
||
parts.append("[图片]")
|
||
elif t == "face":
|
||
parts.append("[表情]")
|
||
elif t == "at":
|
||
parts.append(f"@{d.get('qq', '?')}")
|
||
elif t == "reply":
|
||
parts.append(f"[回复:{d.get('id','?')}]")
|
||
else:
|
||
parts.append(f"[{t}]")
|
||
else:
|
||
parts.append(str(item))
|
||
text = "".join(parts)
|
||
lines.append(f" [{ts}] {uid}: {str(text)[:80]}")
|
||
return {"ok": True, "text": "\n".join(lines)}
|
||
|
||
|
||
def cmd_recall(mid: int, json_output: bool):
|
||
"""撤回消息"""
|
||
data = api_call("delete_msg", {"message_id": mid})
|
||
if not api_ok(data):
|
||
return {"ok": False, "error": handle_api_error(data, "delete_msg")}
|
||
if json_output:
|
||
return {"ok": True, "data": data}
|
||
return {"ok": True, "text": f"✅ 已撤回消息 {mid}"}
|
||
|
||
|
||
def cmd_pin_msg(mid: int, json_output: bool):
|
||
"""精华消息(需消息 + 是群主/管理员)"""
|
||
data = api_call("set_essence_msg", {"message_id": mid})
|
||
if not api_ok(data):
|
||
return {"ok": False, "error": handle_api_error(data, "set_essence_msg")}
|
||
if json_output:
|
||
return {"ok": True, "data": data}
|
||
return {"ok": True, "text": f"✅ 已设 {mid} 为精华消息"}
|
||
|
||
|
||
# ==================== 系统操作 ====================
|
||
|
||
def cmd_leave(gid: int, json_output: bool):
|
||
"""退出群聊(不可逆!必须先问老板)"""
|
||
data = api_call("set_group_leave", {"group_id": gid})
|
||
if not api_ok(data):
|
||
return {"ok": False, "error": handle_api_error(data, "set_group_leave")}
|
||
if json_output:
|
||
return {"ok": True, "data": data}
|
||
return {"ok": True, "text": f"✅ 已退出群 {gid}"}
|
||
|
||
|
||
def cmd_pending_requests(json_output: bool):
|
||
"""查看待处理的加群请求/群邀请"""
|
||
data = api_call("get_group_system_msg")
|
||
if not api_ok(data):
|
||
return {"ok": False, "error": handle_api_error(data, "get_group_system_msg")}
|
||
info = data.get("data", {})
|
||
if json_output:
|
||
return {"ok": True, "data": info}
|
||
lines = ["📬 待处理请求"]
|
||
has_pending = False
|
||
for key, label in [("invited_requests", "群邀请"),
|
||
("join_requests", "加群请求")]:
|
||
items = info.get(key, [])
|
||
if items:
|
||
has_pending = True
|
||
lines.append(f"\n {label} ({len(items)}):")
|
||
for item in items:
|
||
gid = item.get("group_id", "?")
|
||
uid = item.get("user_id", "?")
|
||
nick = item.get("nickname", "?")
|
||
flag = item.get("request_id", "?")
|
||
lines.append(f" [{gid}] {nick}({uid}) — flag:{str(flag)[:20]}")
|
||
if not has_pending:
|
||
lines.append(" 暂无待处理的请求")
|
||
return {"ok": True, "text": "\n".join(lines)}
|
||
|
||
|
||
# ==================== 主入口 ====================
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(
|
||
description="QQ 群综合管理工具")
|
||
parser.add_argument("command", nargs="?", help="操作命令")
|
||
parser.add_argument("--gid", type=int, help="群号")
|
||
parser.add_argument("--uid", type=int, help="用户 QQ")
|
||
parser.add_argument("--mid", type=int, help="消息 ID")
|
||
parser.add_argument("--card", type=str, help="群名片")
|
||
parser.add_argument("--title", type=str, help="群头衔")
|
||
parser.add_argument("--name", type=str, help="群名称")
|
||
parser.add_argument("--file_path", type=str, help="本地文件路径")
|
||
parser.add_argument("--file_id", type=str, help="文件 ID")
|
||
parser.add_argument("--folder_id", type=str, help="文件夹 ID", default="")
|
||
parser.add_argument("--parent", type=str, help="父文件夹 ID", default="/")
|
||
parser.add_argument("--count", type=int, default=10, help="消息数量")
|
||
parser.add_argument("--minutes", type=int, default=10, help="禁言分钟数(0=解禁)")
|
||
parser.add_argument("--enable", type=str, choices=["true", "false"], help="启用/禁用")
|
||
parser.add_argument("--reject_add", action="store_true", help="踢出时拒绝加群申请")
|
||
parser.add_argument("--json", action="store_true", help="JSON 格式输出")
|
||
args = parser.parse_args()
|
||
|
||
if not args.command:
|
||
parser.print_help()
|
||
sys.exit(1)
|
||
|
||
try:
|
||
cmd = args.command
|
||
jo = args.json
|
||
|
||
# 信息查询
|
||
if cmd == "group-list":
|
||
result = cmd_group_list(jo)
|
||
elif cmd == "group-info":
|
||
if not args.gid:
|
||
return fail("需要 --gid")
|
||
result = cmd_group_info(args.gid, jo)
|
||
elif cmd == "member-list":
|
||
if not args.gid:
|
||
return fail("需要 --gid")
|
||
result = cmd_member_list(args.gid, jo)
|
||
elif cmd == "member-info":
|
||
if not args.gid or not args.uid:
|
||
return fail("需要 --gid 和 --uid")
|
||
result = cmd_member_info(args.gid, args.uid, jo)
|
||
elif cmd == "at-all-remain":
|
||
if not args.gid:
|
||
return fail("需要 --gid")
|
||
result = cmd_at_all_remain(args.gid, jo)
|
||
|
||
# 成员管理
|
||
elif cmd == "set-card":
|
||
if not args.gid or not args.uid or args.card is None:
|
||
return fail("需要 --gid, --uid, --card")
|
||
result = cmd_set_card(args.gid, args.uid, args.card, jo)
|
||
elif cmd == "set-admin":
|
||
if not args.gid or not args.uid or args.enable is None:
|
||
return fail("需要 --gid, --uid, --enable(true/false)")
|
||
result = cmd_set_admin(args.gid, args.uid, args.enable == "true", jo)
|
||
elif cmd == "set-title":
|
||
if not args.gid or not args.uid or args.title is None:
|
||
return fail("需要 --gid, --uid, --title")
|
||
result = cmd_set_title(args.gid, args.uid, args.title, jo)
|
||
elif cmd == "kick":
|
||
if not args.gid or not args.uid:
|
||
return fail("需要 --gid 和 --uid")
|
||
result = cmd_kick(args.gid, args.uid, args.reject_add, jo)
|
||
elif cmd in ("ban", "unban"):
|
||
if not args.gid or not args.uid:
|
||
return fail("需要 --gid 和 --uid")
|
||
mins = 0 if cmd == "unban" else args.minutes
|
||
result = cmd_ban(args.gid, args.uid, mins, jo)
|
||
|
||
# 群设置
|
||
elif cmd == "rename":
|
||
if not args.gid or not args.name:
|
||
return fail("需要 --gid 和 --name")
|
||
result = cmd_rename(args.gid, args.name, jo)
|
||
elif cmd == "mute-all":
|
||
if not args.gid or args.enable is None:
|
||
return fail("需要 --gid 和 --enable(true/false)")
|
||
result = cmd_mute_all(args.gid, args.enable == "true", jo)
|
||
elif cmd == "set-portrait":
|
||
if not args.gid or not args.file_path:
|
||
return fail("需要 --gid 和 --file_path")
|
||
result = cmd_set_portrait(args.gid, args.file_path, jo)
|
||
|
||
# 文件操作
|
||
elif cmd == "list-files":
|
||
if not args.gid:
|
||
return fail("需要 --gid")
|
||
result = cmd_list_files(args.gid, args.folder_id, jo)
|
||
elif cmd == "file-url":
|
||
if not args.gid or not args.file_id:
|
||
return fail("需要 --gid 和 --file_id")
|
||
result = cmd_file_url(args.gid, args.file_id, jo)
|
||
elif cmd == "folder-create":
|
||
if not args.gid or not args.name:
|
||
return fail("需要 --gid 和 --name")
|
||
result = cmd_create_folder(args.gid, args.name, args.parent, jo)
|
||
|
||
# 消息操作
|
||
elif cmd == "msg-history":
|
||
if not args.gid:
|
||
return fail("需要 --gid")
|
||
result = cmd_msg_history(args.gid, args.count, jo)
|
||
elif cmd == "recall":
|
||
if not args.mid:
|
||
return fail("需要 --mid(消息ID)")
|
||
result = cmd_recall(args.mid, jo)
|
||
elif cmd == "pin-msg":
|
||
if not args.mid:
|
||
return fail("需要 --mid(消息ID)")
|
||
result = cmd_pin_msg(args.mid, jo)
|
||
|
||
# 系统操作
|
||
elif cmd == "leave":
|
||
if not args.gid:
|
||
return fail("需要 --gid")
|
||
result = cmd_leave(args.gid, jo)
|
||
elif cmd == "pending-requests":
|
||
result = cmd_pending_requests(jo)
|
||
|
||
else:
|
||
print(f"❌ 未知命令: {cmd}")
|
||
sys.exit(1)
|
||
|
||
# 输出
|
||
if jo:
|
||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||
else:
|
||
if result.get("text"):
|
||
print(result["text"])
|
||
elif result.get("ok"):
|
||
print("✅ 完成")
|
||
else:
|
||
print(result.get("error", "❌ 操作失败"))
|
||
sys.exit(0 if result.get("ok") else 1)
|
||
|
||
except requests.exceptions.ConnectionError:
|
||
print(f"❌ 连接失败: 无法连接到 NapCat ({CQHTTP_URL})")
|
||
sys.exit(1)
|
||
except Exception as e:
|
||
print(f"❌ 异常: {e}")
|
||
sys.exit(1)
|
||
|
||
|
||
def fail(msg: str):
|
||
print(f"❌ {msg}")
|
||
sys.exit(1)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|