""" 禁言插件 提供智能禁言功能的群聊管理插件。 功能特性: - 智能LLM判定:根据聊天内容智能判断是否需要禁言 - 灵活的时长管理:支持自定义禁言时长限制 - 模板化消息:支持自定义禁言提示消息 - 参数验证:完整的输入参数验证和错误处理 - 配置文件支持:所有设置可通过配置文件调整 - 权限管理:支持用户权限和群组权限控制 包含组件: - 智能禁言Action - 基于LLM判断是否需要禁言(支持群组权限控制) - 禁言命令Command - 手动执行禁言操作(支持用户权限控制) """ from typing import List, Tuple, Type, Optional import random # 导入新插件系统 from src.plugin_system.base.base_plugin import BasePlugin from src.plugin_system.base.base_plugin import register_plugin from src.plugin_system.base.base_action import BaseAction from src.plugin_system.base.base_command import BaseCommand from src.plugin_system.base.component_types import ComponentInfo, ActionActivationType, ChatMode from src.plugin_system.base.config_types import ConfigField from src.common.logger import get_logger # 导入配置API(可选的简便方法) from src.plugin_system.apis import person_api, generator_api logger = get_logger("mute_plugin") # ===== Action组件 ===== class MuteAction(BaseAction): """智能禁言Action - 基于LLM智能判断是否需要禁言""" # 激活设置 focus_activation_type = ActionActivationType.LLM_JUDGE # Focus模式使用LLM判定,确保谨慎 normal_activation_type = ActionActivationType.KEYWORD # Normal模式使用关键词激活,快速响应 mode_enable = ChatMode.ALL parallel_action = False # 动作基本信息 action_name = "mute" action_description = "智能禁言系统,基于LLM判断是否需要禁言" # 关键词设置(用于Normal模式) activation_keywords = ["禁言", "mute", "ban", "silence"] keyword_case_sensitive = False # LLM判定提示词(用于Focus模式) llm_judge_prompt = """ 判定是否需要使用禁言动作的严格条件: 使用禁言的情况: 1. 用户发送明显违规内容(色情、暴力、政治敏感等) 2. 恶意刷屏或垃圾信息轰炸 3. 用户主动明确要求被禁言("禁言我"等) 4. 严重违反群规的行为 5. 恶意攻击他人或群组管理 绝对不要使用的情况: 2. 情绪化表达但无恶意 3. 开玩笑或调侃,除非过分 4. 单纯的意见分歧或争论 """ # 动作参数定义 action_parameters = { "target": "禁言对象,必填,输入你要禁言的对象的名字,请仔细思考不要弄错禁言对象", "duration": "禁言时长,必填,输入你要禁言的时长(秒),单位为秒,必须为数字", "reason": "禁言理由,可选", } # 动作使用场景 action_require = [ "当有人违反了公序良俗的内容", "当有人刷屏时使用", "当有人发了擦边,或者色情内容时使用", "当有人要求禁言自己时使用", "如果某人已经被禁言了,就不要再次禁言了,除非你想追加时间!!", ] # 关联类型 associated_types = ["text", "command"] def _check_group_permission(self) -> Tuple[bool, Optional[str]]: """检查当前群是否有禁言动作权限 Returns: Tuple[bool, Optional[str]]: (是否有权限, 错误信息) """ # 如果不是群聊,直接返回False if not self.is_group: return False, "禁言动作只能在群聊中使用" # 获取权限配置 allowed_groups = self.get_config("permissions.allowed_groups", []) # 如果配置为空,表示不启用权限控制 if not allowed_groups: logger.info(f"{self.log_prefix} 群组权限未配置,允许所有群使用禁言动作") return True, None # 检查当前群是否在允许列表中 current_group_key = f"{self.platform}:{self.group_id}" for allowed_group in allowed_groups: if allowed_group == current_group_key: logger.info(f"{self.log_prefix} 群组 {current_group_key} 有禁言动作权限") return True, None logger.warning(f"{self.log_prefix} 群组 {current_group_key} 没有禁言动作权限") return False, "当前群组没有使用禁言动作的权限" async def execute(self) -> Tuple[bool, Optional[str]]: """执行智能禁言判定""" logger.info(f"{self.log_prefix} 执行智能禁言动作") # 首先检查群组权限 has_permission, permission_error = self._check_group_permission() if not has_permission: logger.error(f"{self.log_prefix} 权限检查失败: {permission_error}") # 不发送错误消息,静默拒绝 return False, permission_error # 获取参数 target = self.action_data.get("target") duration = self.action_data.get("duration") reason = self.action_data.get("reason", "违反群规") # 参数验证 if not target: error_msg = "禁言目标不能为空" logger.error(f"{self.log_prefix} {error_msg}") await self.send_text("没有指定禁言对象呢~") return False, error_msg if not duration: error_msg = "禁言时长不能为空" logger.error(f"{self.log_prefix} {error_msg}") await self.send_text("没有指定禁言时长呢~") return False, error_msg # 获取时长限制配置 min_duration = self.get_config("mute.min_duration", 60) max_duration = self.get_config("mute.max_duration", 2592000) # 验证时长格式并转换 try: duration_int = int(duration) if duration_int <= 0: error_msg = "禁言时长必须大于0" logger.error(f"{self.log_prefix} {error_msg}") await self.send_text("禁言时长必须是正数哦~") return False, error_msg # 限制禁言时长范围 if duration_int < min_duration: duration_int = min_duration logger.info(f"{self.log_prefix} 禁言时长过短,调整为{min_duration}秒") elif duration_int > max_duration: duration_int = max_duration logger.info(f"{self.log_prefix} 禁言时长过长,调整为{max_duration}秒") except (ValueError, TypeError): error_msg = f"禁言时长格式无效: {duration}" logger.error(f"{self.log_prefix} {error_msg}") # await self.send_text("禁言时长必须是数字哦~") return False, error_msg # 获取用户ID person_id = person_api.get_person_id_by_name(target) user_id = await person_api.get_person_value(person_id, "user_id") if not user_id: error_msg = f"未找到用户 {target} 的ID" await self.send_text(f"找不到 {target} 这个人呢~") logger.error(f"{self.log_prefix} {error_msg}") return False, error_msg # 格式化时长显示 enable_formatting = self.get_config("mute.enable_duration_formatting", True) time_str = self._format_duration(duration_int) if enable_formatting else f"{duration_int}秒" # 获取模板化消息 message = self._get_template_message(target, time_str, reason) result_status, result_message = await generator_api.rewrite_reply( chat_stream=self.chat_stream, reply_data={ "raw_reply": message, "reason": reason, }, ) if result_status: for reply_seg in result_message: data = reply_seg[1] await self.send_text(data) # 发送群聊禁言命令 success = await self.send_command( command_name="GROUP_BAN", args={"qq_id": str(user_id), "duration": str(duration_int)}, storage_message=False ) if success: logger.info(f"{self.log_prefix} 成功发送禁言命令,用户 {target}({user_id}),时长 {duration_int} 秒") # 存储动作信息 await self.store_action_info( action_build_into_prompt=True, action_prompt_display=f"尝试禁言了用户 {target},时长 {time_str},原因:{reason}", action_done=True, ) return True, f"成功禁言 {target},时长 {time_str}" else: error_msg = "发送禁言命令失败" logger.error(f"{self.log_prefix} {error_msg}") await self.send_text("执行禁言动作失败") return False, error_msg def _get_template_message(self, target: str, duration_str: str, reason: str) -> str: """获取模板化的禁言消息""" templates = self.get_config("mute.templates") template = random.choice(templates) return template.format(target=target, duration=duration_str, reason=reason) def _format_duration(self, seconds: int) -> str: """将秒数格式化为可读的时间字符串""" if seconds < 60: return f"{seconds}秒" elif seconds < 3600: minutes = seconds // 60 remaining_seconds = seconds % 60 if remaining_seconds > 0: return f"{minutes}分{remaining_seconds}秒" else: return f"{minutes}分钟" elif seconds < 86400: hours = seconds // 3600 remaining_minutes = (seconds % 3600) // 60 if remaining_minutes > 0: return f"{hours}小时{remaining_minutes}分钟" else: return f"{hours}小时" else: days = seconds // 86400 remaining_hours = (seconds % 86400) // 3600 if remaining_hours > 0: return f"{days}天{remaining_hours}小时" else: return f"{days}天" # ===== Command组件 ===== class MuteCommand(BaseCommand): """禁言命令 - 手动执行禁言操作""" # Command基本信息 command_name = "mute_command" command_description = "禁言命令,手动执行禁言操作" command_pattern = r"^/mute\s+(?P\S+)\s+(?P\d+)(?:\s+(?P.+))?$" command_help = "禁言指定用户,用法:/mute <用户名> <时长(秒)> [理由]" command_examples = ["/mute 用户名 300", "/mute 张三 600 刷屏", "/mute @某人 1800 违规内容"] intercept_message = True # 拦截消息处理 def _check_user_permission(self) -> Tuple[bool, Optional[str]]: """检查当前用户是否有禁言命令权限 Returns: Tuple[bool, Optional[str]]: (是否有权限, 错误信息) """ # 获取当前用户信息 chat_stream = self.message.chat_stream if not chat_stream: return False, "无法获取聊天流信息" current_platform = chat_stream.platform current_user_id = str(chat_stream.user_info.user_id) # 获取权限配置 allowed_users = self.get_config("permissions.allowed_users", []) # 如果配置为空,表示不启用权限控制 if not allowed_users: logger.info(f"{self.log_prefix} 用户权限未配置,允许所有用户使用禁言命令") return True, None # 检查当前用户是否在允许列表中 current_user_key = f"{current_platform}:{current_user_id}" for allowed_user in allowed_users: if allowed_user == current_user_key: logger.info(f"{self.log_prefix} 用户 {current_user_key} 有禁言命令权限") return True, None logger.warning(f"{self.log_prefix} 用户 {current_user_key} 没有禁言命令权限") return False, "你没有使用禁言命令的权限" async def execute(self) -> Tuple[bool, Optional[str]]: """执行禁言命令""" try: # 首先检查用户权限 has_permission, permission_error = self._check_user_permission() if not has_permission: logger.error(f"{self.log_prefix} 权限检查失败: {permission_error}") await self.send_text(f"❌ {permission_error}") return False, permission_error target = self.matched_groups.get("target") duration = self.matched_groups.get("duration") reason = self.matched_groups.get("reason", "管理员操作") if not all([target, duration]): await self.send_text("❌ 命令参数不完整,请检查格式") return False, "参数不完整" # 获取时长限制配置 min_duration = self.get_config("mute.min_duration", 60) max_duration = self.get_config("mute.max_duration", 2592000) # 验证时长 try: duration_int = int(duration) if duration_int <= 0: await self.send_text("❌ 禁言时长必须大于0") return False, "时长无效" # 限制禁言时长范围 if duration_int < min_duration: duration_int = min_duration await self.send_text(f"⚠️ 禁言时长过短,调整为{min_duration}秒") elif duration_int > max_duration: duration_int = max_duration await self.send_text(f"⚠️ 禁言时长过长,调整为{max_duration}秒") except ValueError: await self.send_text("❌ 禁言时长必须是数字") return False, "时长格式错误" # 获取用户ID person_id = person_api.get_person_id_by_name(target) user_id = person_api.get_person_value(person_id, "user_id") if not user_id: error_msg = f"未找到用户 {target} 的ID" await self.send_text(f"❌ 找不到用户: {target}") logger.error(f"{self.log_prefix} {error_msg}") return False, error_msg # 格式化时长显示 enable_formatting = self.get_config("mute.enable_duration_formatting", True) time_str = self._format_duration(duration_int) if enable_formatting else f"{duration_int}秒" logger.info(f"{self.log_prefix} 执行禁言命令: {target}({user_id}) -> {time_str}") # 发送群聊禁言命令 success = await self.send_command( command_name="GROUP_BAN", args={"qq_id": str(user_id), "duration": str(duration_int)}, display_message=f"禁言了 {target} {time_str}", ) if success: # 获取并发送模板化消息 message = self._get_template_message(target, time_str, reason) await self.send_text(message) logger.info(f"{self.log_prefix} 成功禁言 {target}({user_id}),时长 {duration_int} 秒") return True, f"成功禁言 {target},时长 {time_str}" else: await self.send_text("❌ 发送禁言命令失败") return False, "发送禁言命令失败" except Exception as e: logger.error(f"{self.log_prefix} 禁言命令执行失败: {e}") await self.send_text(f"❌ 禁言命令错误: {str(e)}") return False, str(e) def _get_template_message(self, target: str, duration_str: str, reason: str) -> str: """获取模板化的禁言消息""" templates = self.get_config("mute.templates") template = random.choice(templates) return template.format(target=target, duration=duration_str, reason=reason) def _format_duration(self, seconds: int) -> str: """将秒数格式化为可读的时间字符串""" if seconds < 60: return f"{seconds}秒" elif seconds < 3600: minutes = seconds // 60 remaining_seconds = seconds % 60 if remaining_seconds > 0: return f"{minutes}分{remaining_seconds}秒" else: return f"{minutes}分钟" elif seconds < 86400: hours = seconds // 3600 remaining_minutes = (seconds % 3600) // 60 if remaining_minutes > 0: return f"{hours}小时{remaining_minutes}分钟" else: return f"{hours}小时" else: days = seconds // 86400 remaining_hours = (seconds % 86400) // 3600 if remaining_hours > 0: return f"{days}天{remaining_hours}小时" else: return f"{days}天" # ===== 插件主类 ===== @register_plugin class MutePlugin(BasePlugin): """禁言插件 提供智能禁言功能: - 智能禁言Action:基于LLM判断是否需要禁言(支持群组权限控制) - 禁言命令Command:手动执行禁言操作(支持用户权限控制) """ # 插件基本信息 plugin_name = "mute_plugin" # 内部标识符 enable_plugin = True config_file_name = "config.toml" # 配置节描述 config_section_descriptions = { "plugin": "插件基本信息配置", "components": "组件启用控制", "permissions": "权限管理配置", "mute": "核心禁言功能配置", "smart_mute": "智能禁言Action的专属配置", "mute_command": "禁言命令Command的专属配置", "logging": "日志记录相关配置", } # 配置Schema定义 config_schema = { "plugin": { "enabled": ConfigField(type=bool, default=False, description="是否启用插件"), "config_version": ConfigField(type=str, default="0.0.2", description="配置文件版本"), }, "components": { "enable_smart_mute": ConfigField(type=bool, default=True, description="是否启用智能禁言Action"), "enable_mute_command": ConfigField(type=bool, default=False, description="是否启用禁言命令Command"), }, "permissions": { "allowed_users": ConfigField( type=list, default=[], description="允许使用禁言命令的用户列表,格式:['platform:user_id'],如['qq:123456789']。空列表表示不启用权限控制", ), "allowed_groups": ConfigField( type=list, default=[], description="允许使用禁言动作的群组列表,格式:['platform:group_id'],如['qq:987654321']。空列表表示不启用权限控制", ), }, "mute": { "min_duration": ConfigField(type=int, default=60, description="最短禁言时长(秒)"), "max_duration": ConfigField(type=int, default=2592000, description="最长禁言时长(秒),默认30天"), "default_duration": ConfigField(type=int, default=300, description="默认禁言时长(秒),默认5分钟"), "enable_duration_formatting": ConfigField( type=bool, default=True, description="是否启用人性化的时长显示(如 '5分钟' 而非 '300秒')" ), "log_mute_history": ConfigField(type=bool, default=True, description="是否记录禁言历史(未来功能)"), "templates": ConfigField( type=list, default=[ "好的,禁言 {target} {duration},理由:{reason}", "收到,对 {target} 执行禁言 {duration},因为{reason}", "明白了,禁言 {target} {duration},原因是{reason}", "哇哈哈哈哈哈,已禁言 {target} {duration},理由:{reason}", "哎呦我去,对 {target} 执行禁言 {duration},因为{reason}", "{target},你完蛋了,我要禁言你 {duration} 秒,原因:{reason}", ], description="成功禁言后发送的随机消息模板", ), "error_messages": ConfigField( type=list, default=[ "没有指定禁言对象呢~", "没有指定禁言时长呢~", "禁言时长必须是正数哦~", "禁言时长必须是数字哦~", "找不到 {target} 这个人呢~", "查找用户信息时出现问题~", ], description="执行禁言过程中发生错误时发送的随机消息模板", ), }, "smart_mute": { "strict_mode": ConfigField(type=bool, default=True, description="LLM判定的严格模式"), "keyword_sensitivity": ConfigField( type=str, default="normal", description="关键词激活的敏感度", choices=["low", "normal", "high"] ), "allow_parallel": ConfigField(type=bool, default=False, description="是否允许并行执行(暂未启用)"), }, "mute_command": { "max_batch_size": ConfigField(type=int, default=5, description="最大批量禁言数量(未来功能)"), "cooldown_seconds": ConfigField(type=int, default=3, description="命令冷却时间(秒)"), }, "logging": { "level": ConfigField( type=str, default="INFO", description="日志记录级别", choices=["DEBUG", "INFO", "WARNING", "ERROR"] ), "prefix": ConfigField(type=str, default="[MutePlugin]", description="日志记录前缀"), "include_user_info": ConfigField(type=bool, default=True, description="日志中是否包含用户信息"), "include_duration_info": ConfigField(type=bool, default=True, description="日志中是否包含禁言时长信息"), }, } def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]: """返回插件包含的组件列表""" # 从配置获取组件启用状态 enable_smart_mute = self.get_config("components.enable_smart_mute", True) enable_mute_command = self.get_config("components.enable_mute_command", True) components = [] # 添加智能禁言Action if enable_smart_mute: components.append((MuteAction.get_action_info(), MuteAction)) # 添加禁言命令Command if enable_mute_command: components.append((MuteCommand.get_command_info(), MuteCommand)) return components