🔴 致命(5个): - Bug1: onebot11.json 格式错误(旧 {http:{},ws:{}} → network.httpServers/httppClients) - Bug2: 端口映射错位(WebUI 6099, OneBot API 6097 独立端口) - Bug3: 变量名含空格 NAP CAT_HOST → NAPCAT_HOST(14脚本+2SKILL+README) - Bug4: heredoc 单引号阻止变量展开 - Bug5: process.py 缺失 _get_high_risk_words() 方法 🟡 重要(3个): - Bug6: onebot11.json 默认不存在(cat > 而非 edit) - Bug7: API 须登录后才响应(新增扫码引导步骤) - Bug8: qqrebot systemd 依赖 napcat 服务 🟢 中等(3个): - Bug9: Skills 占位符缺 YOUR_NAPCAT_HOST sed 覆盖 - Bug10: venv 不可用需降级方案 - Bug11: 端口 25580 冲突处理 已验证全链路:NapCat + qqrebot + 插件 + OpenClaw Gateway + AI 回复
206 lines
7.4 KiB
Python
206 lines
7.4 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
QQ 群管理动作 — 通过 NapCat (OneBot) HTTP API
|
||
|
||
支持操作:
|
||
- 退群(leave group)
|
||
- 按群号加群(join group)
|
||
- 接受进群邀请(approve invite)
|
||
- 拒绝进群邀请(reject invite)
|
||
- 处理加群请求(approve/reject group request)
|
||
|
||
注意:
|
||
- 进群/退群操作 **必须** 先问老板,老板同意才能执行
|
||
- 紧急情况(如检测到攻击性信息)除外
|
||
- 退群操作不可逆,执行前二次确认
|
||
- 加群需要有人邀请或群主通过群链接/名片形式邀请,无法通过纯 API 直接加入
|
||
|
||
用法:
|
||
python3 qq_group_action.py --leave YOUR_BOT_QQ # 退出群
|
||
python3 qq_group_action.py --join YOUR_BOT_QQ # 按群号尝试加群(需有邀请)
|
||
python3 qq_group_action.py --approve-invite <flag> # 接受进群邀请
|
||
python3 qq_group_action.py --reject-invite <flag> # 拒绝进群邀请
|
||
python3 qq_group_action.py --list-groups # 列出当前群列表
|
||
"""
|
||
|
||
import json
|
||
import sys
|
||
import argparse
|
||
import requests
|
||
|
||
CQHTTP_URL = "http://YOUR_NAPCAT_HOST:25570"
|
||
|
||
|
||
def get_group_list():
|
||
"""获取当前已加入的群列表"""
|
||
resp = requests.post(f"{CQHTTP_URL}/get_group_list", json={}, timeout=10)
|
||
data = resp.json()
|
||
if data.get("status") == "ok":
|
||
return data.get("data", [])
|
||
raise Exception(f"API 异常: {json.dumps(data, ensure_ascii=False)}")
|
||
|
||
|
||
def get_group_system_msgs():
|
||
"""获取系统消息,包括待处理的进群邀请"""
|
||
resp = requests.post(f"{CQHTTP_URL}/get_group_system_msg", json={}, timeout=10)
|
||
data = resp.json()
|
||
if data.get("status") == "ok":
|
||
return data.get("data", {})
|
||
return {}
|
||
|
||
|
||
def leave_group(group_id: int) -> dict:
|
||
"""退出指定群聊。不可逆!"""
|
||
resp = requests.post(f"{CQHTTP_URL}/set_group_leave",
|
||
json={"group_id": group_id}, timeout=10)
|
||
return resp.json()
|
||
|
||
|
||
def join_group_via_invite(group_id: int) -> str:
|
||
"""
|
||
尝试通过群号加群。
|
||
|
||
策略:
|
||
1. 检查是否已在群里 → 直接返回
|
||
2. 检查是否有该群的待处理邀请 → 接受邀请
|
||
3. 没有邀请 → 告知需要通过邀请方式加群
|
||
"""
|
||
# 1. 检查是否已在群
|
||
groups = get_group_list()
|
||
for g in groups:
|
||
if g.get("group_id") == group_id:
|
||
return f"✅ 已经在群里: {g.get('group_name', '未知')} ({group_id})"
|
||
|
||
# 2. 检查待处理邀请
|
||
sys_msgs = get_group_system_msgs()
|
||
invites = sys_msgs.get("invited_requests", [])
|
||
for inv in invites:
|
||
if inv.get("group_id") == group_id:
|
||
# 接受邀请
|
||
flag = str(inv.get("request_id", ""))
|
||
resp = requests.post(f"{CQHTTP_URL}/set_group_add_request", json={
|
||
"flag": flag,
|
||
"sub_type": "invite",
|
||
"approve": True,
|
||
}, timeout=10)
|
||
data = resp.json()
|
||
if data.get("status") == "ok":
|
||
return f"✅ 已接受群邀请,成功加入: {inv.get('group_name', '?')} ({group_id})"
|
||
else:
|
||
return f"❌ 接受邀请失败: {data.get('message', '未知错误')}"
|
||
|
||
# 3. 没有待处理的邀请
|
||
group_name = "(未知)"
|
||
try:
|
||
resp = requests.post(f"{CQHTTP_URL}/get_group_info", json={"group_id": group_id}, timeout=5)
|
||
d = resp.json()
|
||
if d.get("status") == "ok" and d.get("data"):
|
||
group_name = d["data"].get("group_name", group_name)
|
||
except:
|
||
pass
|
||
|
||
return (
|
||
f"⚠️ 无法自动加入群 {group_name} ({group_id})\n"
|
||
f" QQ 协议不提供通过 API 直接加群的能力。\n"
|
||
f" 需要有人邀请 bot 进群,bot 收到邀请后自动处理。\n"
|
||
f" 请让群管理用 QQ 客户端邀请 bot(QQ号: YOUR_ADMIN_QQ),\n"
|
||
f" 或发送群邀请链接/二维码给 bot,bot 会自动接受。"
|
||
)
|
||
|
||
|
||
def handle_group_request(flag: str, approve: bool, reason: str = "") -> dict:
|
||
"""处理加群请求 / 群邀请"""
|
||
params = {
|
||
"flag": flag,
|
||
"sub_type": "invite" if "invite" in flag else "add",
|
||
"approve": approve,
|
||
}
|
||
if not approve and reason:
|
||
params["reason"] = reason
|
||
resp = requests.post(f"{CQHTTP_URL}/set_group_add_request",
|
||
json=params, timeout=10)
|
||
return resp.json()
|
||
|
||
|
||
def format_group_info(groups: list) -> str:
|
||
"""格式化群列表为可读文本"""
|
||
if not groups:
|
||
return "当前没有加入任何群"
|
||
lines = [f"共 {len(groups)} 个群\n"]
|
||
for g in groups:
|
||
gid = g.get("group_id", "?")
|
||
name = g.get("group_name", "?")
|
||
mc = g.get("member_count", "?")
|
||
mm = g.get("max_member_count", "?")
|
||
lines.append(f"• {name} ({gid}) — {mc}/{mm} 人")
|
||
return "\n".join(lines)
|
||
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(description="QQ 群管理操作")
|
||
parser.add_argument("--leave", type=int, help="退出指定群(输入群号)")
|
||
parser.add_argument("--join", type=int, help="尝试加入指定群(输入群号)")
|
||
parser.add_argument("--approve-invite", type=str, help="接受进群邀请(输入 flag)")
|
||
parser.add_argument("--reject-invite", type=str, help="拒绝进群邀请(输入 flag)")
|
||
parser.add_argument("--reason", type=str, default="", help="拒绝理由(可选)")
|
||
parser.add_argument("--list-groups", action="store_true", help="列出当前加入的群")
|
||
parser.add_argument("--json", action="store_true", help="JSON 格式输出")
|
||
args = parser.parse_args()
|
||
|
||
try:
|
||
if args.list_groups:
|
||
groups = get_group_list()
|
||
if args.json:
|
||
print(json.dumps(groups, ensure_ascii=False, indent=2))
|
||
else:
|
||
print(format_group_info(groups))
|
||
return
|
||
|
||
if args.leave:
|
||
result = leave_group(args.leave)
|
||
if result.get("status") == "ok":
|
||
print(f"✅ 已退出群 {args.leave}")
|
||
else:
|
||
print(f"❌ 退群失败: {result.get('message', '未知错误')}")
|
||
sys.exit(1)
|
||
return
|
||
|
||
if args.join:
|
||
result = join_group_via_invite(args.join)
|
||
success = result.startswith("✅")
|
||
print(result)
|
||
sys.exit(0 if success else 1)
|
||
return
|
||
|
||
if args.approve_invite:
|
||
result = handle_group_request(args.approve_invite, approve=True)
|
||
if result.get("status") == "ok":
|
||
print(f"✅ 已接受群邀请 {args.approve_invite}")
|
||
else:
|
||
print(f"❌ 接受邀请失败: {result.get('message', '未知错误')}")
|
||
sys.exit(1)
|
||
return
|
||
|
||
if args.reject_invite:
|
||
result = handle_group_request(args.reject_invite, approve=False,
|
||
reason=args.reason)
|
||
if result.get("status") == "ok":
|
||
print(f"✅ 已拒绝群邀请 {args.reject_invite}")
|
||
else:
|
||
print(f"❌ 拒绝邀请失败: {result.get('message', '未知错误')}")
|
||
sys.exit(1)
|
||
return
|
||
|
||
parser.print_help()
|
||
|
||
except requests.exceptions.ConnectionError:
|
||
print(f"❌ 连接失败: 无法连接到 NapCat ({CQHTTP_URL})")
|
||
sys.exit(1)
|
||
except Exception as e:
|
||
print(f"❌ 操作失败: {e}")
|
||
sys.exit(1)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|