Files
chatrebot_aireply_plug/scripts/qq_group_action.py
root 9dff73e7bc fix: 通过本地完整测试发现的 11 个 bug
🔴 致命(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 回复
2026-05-04 19:47:26 +08:00

206 lines
7.4 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 群管理动作 — 通过 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 客户端邀请 botQQ号: YOUR_ADMIN_QQ\n"
f" 或发送群邀请链接/二维码给 botbot 会自动接受。"
)
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()