Files
WechatHookBot/plugins/GroupLeave/main.py
2025-12-03 15:48:44 +08:00

265 lines
9.3 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.
"""
退群提醒插件
当成员退出群聊时,发送提醒卡片
支持两种方式:
1. type=11099 群成员删除事件(如果 API 支持)
2. type=11058 系统消息解析(通用方式)
"""
import tomllib
from pathlib import Path
from loguru import logger
from utils.plugin_base import PluginBase
from utils.decorators import on_text_message
# 定义事件装饰器
def on_chatroom_member_remove(priority=50):
"""群成员删除装饰器"""
def decorator(func):
setattr(func, '_event_type', 'chatroom_member_remove')
setattr(func, '_priority', min(max(priority, 0), 99))
return func
return decorator
def on_system_message(priority=50):
"""系统消息装饰器"""
def decorator(func):
setattr(func, '_event_type', 'system_message')
setattr(func, '_priority', min(max(priority, 0), 99))
return func
return decorator
def on_chatroom_info_change(priority=50):
"""群信息变化装饰器"""
def decorator(func):
setattr(func, '_event_type', 'chatroom_info_change')
setattr(func, '_priority', min(max(priority, 0), 99))
return func
return decorator
class GroupLeave(PluginBase):
"""退群提醒插件"""
# 插件元数据
description = "成员退群时发送提醒卡片"
author = "ShiHao"
version = "1.0.0"
def __init__(self):
super().__init__()
self.config = None
# 群成员缓存:{room_wxid: {wxid: nickname}}
self.member_cache = {}
async def async_init(self):
"""插件异步初始化"""
# 读取配置
config_path = Path(__file__).parent / "config.toml"
with open(config_path, "rb") as f:
self.config = tomllib.load(f)
logger.info("退群提醒插件已加载")
@on_chatroom_member_remove(priority=50)
async def on_chatroom_member_remove(self, bot, message: dict):
"""处理群成员删除事件type=11099"""
logger.info(f"[GroupLeave] 收到群成员删除事件,原始消息: {message}")
# 检查是否启用
if not self.config["behavior"]["enabled"]:
logger.warning("[GroupLeave] 插件未启用,跳过处理")
return
room_wxid = message.get("RoomWxid", "")
member_list = message.get("MemberList", [])
logger.info(f"[GroupLeave] 解析结果 - 群ID: {room_wxid}, 成员列表: {member_list}")
# 检查群聊过滤
if not self._should_notify(room_wxid):
logger.info(f"[GroupLeave] 群 {room_wxid} 不在提醒列表中,跳过")
return
logger.success(f"[GroupLeave] 群 {room_wxid} 有成员退出: {len(member_list)}")
await self._process_leave_members(bot, room_wxid, member_list)
@on_system_message(priority=50)
async def on_system_message(self, bot, message: dict):
"""处理系统消息type=11058解析退群通知"""
# 检查是否启用
if not self.config["behavior"]["enabled"]:
return
raw_msg = message.get("Content", "")
room_wxid = message.get("FromWxid", "")
# 只处理群聊系统消息
if not room_wxid.endswith("@chatroom"):
return
logger.info(f"[GroupLeave] 收到系统消息: room={room_wxid}, msg={raw_msg}")
# 解析退群消息
# 格式1: "你将xxxx移出了群聊"
# 格式2: "xxxx将xxxx移出了群聊"
# 格式3: "xxxx退出了群聊"
import re
# 匹配退群消息
patterns = [
r"(.+?)退出了群聊", # 主动退群
r"(?:你|.+?)将(.+?)移出了群聊", # 被踢出群
]
for pattern in patterns:
match = re.search(pattern, raw_msg)
if match:
nickname = match.group(1).strip()
logger.success(f"[GroupLeave] 从系统消息解析到退群成员: {nickname}")
# 检查群聊过滤
if not self._should_notify(room_wxid):
logger.info(f"[GroupLeave] 群 {room_wxid} 不在提醒列表中,跳过")
return
# 构造成员列表(只有昵称,没有 wxid
member_list = [{"wxid": "", "nickname": nickname}]
await self._process_leave_members(bot, room_wxid, member_list)
break
@on_chatroom_info_change(priority=50)
async def on_chatroom_info_change(self, bot, message: dict):
"""处理群信息变化事件type=11100检测退群"""
if not self.config["behavior"]["enabled"]:
return
room_wxid = message.get("RoomWxid", "")
if not self._should_notify(room_wxid):
return
try:
# 获取当前群成员列表
current_members = await bot.get_chatroom_members(room_wxid)
if not current_members:
return
# 构建当前成员字典 {wxid: nickname}
current_member_dict = {m.get("wxid"): m.get("nickname", "未知成员") for m in current_members if m.get("wxid")}
# 首次遇到该群,初始化缓存
if room_wxid not in self.member_cache:
logger.info(f"[GroupLeave] 首次记录群成员: {room_wxid}, 成员数: {len(current_member_dict)}")
self.member_cache[room_wxid] = current_member_dict
return
# 对比找出退群成员
cached_member_dict = self.member_cache[room_wxid]
left_wxids = set(cached_member_dict.keys()) - set(current_member_dict.keys())
if left_wxids:
logger.success(f"[GroupLeave] 检测到退群成员: {len(left_wxids)}")
# 构造退群成员列表(使用缓存中的昵称)
left_members = [{"wxid": wxid, "nickname": cached_member_dict.get(wxid, "未知成员")} for wxid in left_wxids]
# 更新缓存
self.member_cache[room_wxid] = current_member_dict
# 处理退群成员
await self._process_leave_members(bot, room_wxid, left_members)
else:
# 更新缓存
self.member_cache[room_wxid] = current_member_dict
except Exception as e:
logger.error(f"[GroupLeave] 处理群信息变化失败: {e}")
async def _process_leave_members(self, bot, room_wxid: str, member_list: list):
"""处理退群成员列表"""
from datetime import datetime
# 为每个退出的成员发送提醒
for member in member_list:
nickname = member.get("nickname", "某成员")
try:
# 获取当前时间
leave_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 发送文字提醒
await self._send_leave_message(bot, room_wxid, nickname, leave_time)
except Exception as e:
logger.error(f"处理退群成员 {nickname} 提醒失败: {e}")
async def _send_leave_message(self, bot, room_wxid: str, nickname: str, leave_time: str):
"""发送退群提醒文字消息"""
try:
message = f"【退群提醒】\n成员:{nickname}\n时间:{leave_time}"
await bot.send_text(room_wxid, message)
logger.success(f"已发送退群提醒: {nickname}")
except Exception as e:
logger.error(f"发送退群提醒失败: {e}")
def _should_notify(self, room_wxid: str) -> bool:
"""判断是否应该发送提醒"""
enabled_groups = self.config["behavior"]["enabled_groups"]
disabled_groups = self.config["behavior"]["disabled_groups"]
# 如果在禁用列表中,不提醒
if room_wxid in disabled_groups:
return False
# 如果启用列表为空,对所有群生效
if not enabled_groups:
return True
# 否则只对启用列表中的群生效
return room_wxid in enabled_groups
@on_text_message(priority=90)
async def handle_test_command(self, bot, message: dict):
"""处理测试命令"""
content = message.get("Content", "").strip()
from_wxid = message.get("FromWxid", "")
is_group = message.get("IsGroup", False)
# 只处理群聊消息
if not is_group:
return
# 检查是否是测试退群命令
if content.startswith("/测试退群"):
parts = content.split()
if len(parts) < 2:
await bot.send_text(from_wxid, "用法: /测试退群 wxid")
return False
test_wxid = parts[1].strip()
logger.info(f"收到测试退群命令: wxid={test_wxid}")
try:
# 模拟群成员删除事件
test_message = {
"RoomWxid": from_wxid,
"MemberList": [{"wxid": test_wxid, "nickname": "测试用户"}],
}
await self.on_chatroom_member_remove(bot, test_message)
await bot.send_text(from_wxid, f"已触发退群提醒测试: {test_wxid}")
except Exception as e:
logger.error(f"测试退群提醒失败: {e}")
await bot.send_text(from_wxid, f"测试失败: {e}")
return False # 阻止后续处理