265 lines
9.3 KiB
Python
265 lines
9.3 KiB
Python
"""
|
||
退群提醒插件
|
||
|
||
当成员退出群聊时,发送提醒卡片
|
||
支持两种方式:
|
||
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 # 阻止后续处理
|