feat:初版
This commit is contained in:
1
plugins/GroupLeave/__init__.py
Normal file
1
plugins/GroupLeave/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# GroupLeave Plugin
|
||||
264
plugins/GroupLeave/main.py
Normal file
264
plugins/GroupLeave/main.py
Normal file
@@ -0,0 +1,264 @@
|
||||
"""
|
||||
退群提醒插件
|
||||
|
||||
当成员退出群聊时,发送提醒卡片
|
||||
支持两种方式:
|
||||
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 # 阻止后续处理
|
||||
Reference in New Issue
Block a user