import re import time import json import asyncio import websockets as Server from typing import Tuple, Optional from src.logger import logger from src.config import global_config from src.database import BanUser, db_manager, is_identical from .qq_emoji_list import qq_face from . import NoticeType, ACCEPT_FORMAT from .message_sending import message_send_instance from .message_handler import message_handler from maim_message import FormatInfo, UserInfo, GroupInfo, Seg, BaseMessageInfo, MessageBase from src.utils import ( get_group_info, get_group_member_list, get_member_info, get_self_info, get_stranger_info, read_ban_list, ) notice_queue: asyncio.Queue[MessageBase] = asyncio.Queue(maxsize=100) unsuccessful_notice_queue: asyncio.Queue[MessageBase] = asyncio.Queue(maxsize=3) class NoticeHandler: banned_list: list[BanUser] = [] # 当前仍在禁言中的用户列表 lifted_list: list[BanUser] = [] # 已经自然解除禁言 def __init__(self): self.server_connection: Server.ServerConnection = None async def set_server_connection(self, server_connection: Server.ServerConnection) -> None: """设置Napcat连接""" self.server_connection = server_connection while self.server_connection.state != Server.State.OPEN: await asyncio.sleep(0.5) self.banned_list, self.lifted_list = await read_ban_list(self.server_connection) asyncio.create_task(self.auto_lift_detect()) asyncio.create_task(self.send_notice()) asyncio.create_task(self.handle_natural_lift()) def _ban_operation(self, group_id: int, user_id: Optional[int] = None, lift_time: Optional[int] = None) -> None: """ 将用户禁言记录添加到self.banned_list中 如果是全体禁言,则user_id为0 """ if user_id is None: user_id = 0 # 使用0表示全体禁言 lift_time = -1 ban_record = BanUser(user_id=user_id, group_id=group_id, lift_time=lift_time) for idx, record in enumerate(self.banned_list): if is_identical(record, ban_record): self.banned_list.pop(idx) self.banned_list.append(ban_record) db_manager.create_ban_record(ban_record) # 更新数据库 return self.banned_list.append(ban_record) db_manager.create_ban_record(ban_record) # 添加到数据库 def _lift_operation(self, group_id: int, user_id: Optional[int] = None) -> None: """ 从self.lifted_group_list中移除已经解除全体禁言的群 """ if user_id is None: user_id = 0 # 使用0表示全体禁言 ban_record = BanUser(user_id=user_id, group_id=group_id, lift_time=-1) if not any(is_identical(ban_record, r) for r in self.lifted_list): self.lifted_list.append(ban_record) db_manager.delete_ban_record(ban_record) # 删除数据库中的记录 async def handle_notice(self, raw_message: dict) -> None: notice_type = raw_message.get("notice_type") # message_time: int = raw_message.get("time") message_time: float = time.time() # 应可乐要求,现在是float了 group_id = raw_message.get("group_id") user_id = raw_message.get("user_id") target_id = raw_message.get("target_id") handled_message: Seg = None user_info: UserInfo = None system_notice: bool = False match notice_type: case NoticeType.friend_recall: operator_id = raw_message.get("operator_id") ts = raw_message.get("time") time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ts)) if ts else "未知时间" logger.info(f"好友 {operator_id} 撤回一条消息") logger.info(f"撤回消息ID:{raw_message.get('message_id')}, 撤回时间:{time_str}") return case NoticeType.group_recall: operator_id = raw_message.get("operator_id") ts = raw_message.get("time") time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ts)) if ts else "未知时间" logger.info(f"群内用户 {operator_id} 撤回一条消息") logger.info(f"撤回消息ID:{raw_message.get('message_id')}, 撤回时间:{time_str}") return case NoticeType.notify: sub_type = raw_message.get("sub_type") match sub_type: # 私聊输入状态(“对方正在输入...”) case "input_status": user_id = raw_message.get("user_id") group_id = raw_message.get("group_id", 0) event_type = raw_message.get("event_type") status_text = raw_message.get("status_text", "") # 仅手机私聊有效 if not group_id or group_id == 0: if status_text: logger.info(f"用户 {user_id} {status_text}") else: status_map = {1: "正在输入中", 2: "已刷新聊天页面"} logger.info(f"用户 {user_id} {status_map.get(event_type, '输入状态变更')}") return case NoticeType.Notify.poke: if global_config.chat.enable_poke and await message_handler.check_allow_to_chat( user_id, group_id, False, False ): logger.info("处理戳一戳消息") await self.handle_poke_notify(raw_message, group_id, user_id) else: logger.warning("戳一戳消息被禁用,取消戳一戳处理") return case _: logger.warning(f"不支持的notify类型: {notice_type}.{sub_type}") case NoticeType.group_ban: sub_type = raw_message.get("sub_type") match sub_type: case NoticeType.GroupBan.ban: if not await message_handler.check_allow_to_chat(user_id, group_id, True, False): return None logger.info("处理群禁言") handled_message, user_info = await self.handle_ban_notify(raw_message, group_id) system_notice = True case NoticeType.GroupBan.lift_ban: if not await message_handler.check_allow_to_chat(user_id, group_id, True, False): return None logger.info("处理解除群禁言") handled_message, user_info = await self.handle_lift_ban_notify(raw_message, group_id) system_notice = True case _: logger.warning(f"不支持的group_ban类型: {notice_type}.{sub_type}") case "group_admin": sub_type = raw_message.get("sub_type") group_id = raw_message.get("group_id") user_id = raw_message.get("user_id") self_id = raw_message.get("self_id") member_info = await get_member_info(self.server_connection, group_id, user_id) if user_id != 0 else None user_cardname = member_info.get("card") if member_info else None if user_id and user_id != 0: user_nickname = member_info.get("nickname") if member_info and member_info.get("nickname") else "QQ用户" else: user_nickname = "系统" if user_id == 0: try: member_list = await get_group_member_list(self.server_connection, group_id) if member_list and isinstance(member_list, list): owner = next((m for m in member_list if m.get("role") == "owner"), None) if owner: owner_id = owner.get("user_id") owner_name = owner.get("nickname") or owner.get("card") or str(owner.get("user_id")) text = f"群主变更为 {owner_name}" logger.info(f"群 {group_id} 群主变更为 {owner_id}") else: text = "群主变更" logger.error(f"群 {group_id} 群主变更(未找到新群主)") else: text = "群主变更" logger.error(f"群 {group_id} 查询群成员列表失败") except Exception as e: text = "群主变更" logger.error(f"群 {group_id} 查询新群主失败: {e}") elif user_id == self_id: if sub_type == "set": text = "(你)被设置为管理员" elif sub_type == "unset": text = "(你)被取消管理员" else: text = f"管理员状态变更: {sub_type}" logger.info(f"群 {group_id} Bot{text.replace('(你)', '')}") else: if sub_type == "set": text = "被设置为管理员" elif sub_type == "unset": text = "被取消管理员" else: text = f"管理员状态变更: {sub_type}" logger.info(f"群 {group_id} 用户 {user_id} {text}") handled_message = Seg(type="text", data=text) user_info = UserInfo( platform=global_config.maibot_server.platform_name, user_id=user_id, user_nickname=user_nickname, user_cardname=user_cardname, ) system_notice = True case "group_increase": sub_type = raw_message.get("sub_type") group_id = raw_message.get("group_id") user_id = raw_message.get("user_id") self_id = raw_message.get("self_id") operator_id = raw_message.get("operator_id") member_info = await get_member_info(self.server_connection, group_id, user_id) if user_id != 0 else None user_cardname = member_info.get("card") if member_info else None user_nickname = member_info.get("nickname") if member_info and member_info.get("nickname") else "QQ用户" operator_name = "系统" if operator_id and operator_id != 0: operator_info = await get_member_info(self.server_connection, group_id, operator_id) if operator_info and operator_info.get("nickname"): operator_name = operator_info.get("nickname") else: operator_name = str(operator_id) if user_id == self_id: if sub_type == "invite": text = f"(你)被 {operator_name} 邀请加入" logger.info(f"群 {group_id} Bot被 {operator_id} 邀请加入") elif sub_type == "approve": text = f"(你)通过 {operator_name} 审批加入" logger.info(f"群 {group_id} Bot通过 {operator_id} 审批加入") else: text = f"(你)加入群(方式: {sub_type})" logger.info(f"群 {group_id} Bot{text.replace('(你)', '')}") else: if sub_type == "invite": text = f"被 {operator_name} 邀请加入" logger.info(f"群 {group_id} 用户 {user_id} 被 {operator_id} 邀请加入") elif sub_type == "approve": text = f"通过 {operator_name} 审批加入" logger.info(f"群 {group_id} 用户 {user_id} 被 {operator_id} 审批加入") else: text = f"加入群(方式: {sub_type})" logger.info(f"群 {group_id} 用户 {user_id} {text}") handled_message = Seg(type="text", data=text) user_info = UserInfo( platform=global_config.maibot_server.platform_name, user_id=user_id, user_nickname=user_nickname, user_cardname=user_cardname, ) system_notice = True case "group_decrease": sub_type = raw_message.get("sub_type") group_id = raw_message.get("group_id") user_id = raw_message.get("user_id") self_id = raw_message.get("self_id") operator_id = raw_message.get("operator_id") operator_name = "系统" if operator_id and operator_id != 0: operator_info = await get_member_info(self.server_connection, group_id, operator_id) if operator_info and operator_info.get("nickname"): operator_name = operator_info.get("nickname") else: operator_name = str(operator_id) if sub_type == "disband": user_id = operator_id text = "群已被解散" logger.info(f"群 {group_id} 已被 {operator_id} 解散") elif user_id == self_id: if sub_type == "leave": text = "(你)退出了群" logger.info(f"群 {group_id} Bot退出了群") elif sub_type == "kick": text = f"(你)被 {operator_name} 移出" logger.info(f"群 {group_id} Bot被 {operator_id} 移出") else: text = f"(你)离开群(方式: {sub_type})" logger.info(f"群 {group_id} Bot离开群(方式: {sub_type})") else: if sub_type == "leave": text = f"退出了群" logger.info(f"群 {group_id} 用户 {user_id} {text}") elif sub_type == "kick": text = f"被 {operator_name} 移出" logger.info(f"群 {group_id} 用户 {user_id} 被 {operator_id} 移出") else: text = f"离开群(方式: {sub_type})" logger.info(f"群 {group_id} 用户 {user_id} {text}") member_info = await get_stranger_info(self.server_connection, user_id) if user_id != 0 else None user_cardname = member_info.get("card") if member_info else None user_nickname = member_info.get("nickname") if member_info and member_info.get("nickname") else "QQ用户" handled_message = Seg(type="text", data=text) user_info = UserInfo( platform=global_config.maibot_server.platform_name, user_id=user_id, user_nickname=user_nickname, user_cardname=user_cardname, ) system_notice = True case "essence": self_id = raw_message.get("self_id") group_id = raw_message.get("group_id") sub_type = raw_message.get("sub_type") message_id = raw_message.get("message_id") operator_id = raw_message.get("operator_id") sender_id = raw_message.get("sender_id", 0) user_id = raw_message.get("user_id", 0) member_info = await get_stranger_info(self.server_connection, user_id) if user_id != 0 else None user_cardname = member_info.get("card") if member_info else None operator_name = "系统" if operator_id and operator_id != 0: operator_info = await get_member_info(self.server_connection, group_id, operator_id) if operator_info: operator_name = operator_info.get("nickname") or operator_info.get("card") or str(operator_id) sender_name = "QQ用户" if sender_id != 0: sender_info = await get_member_info(self.server_connection, group_id, sender_id) if sender_info: sender_name = sender_info.get("nickname") or sender_info.get("card") or str(sender_id) if sub_type == "add": if sender_id == 0: if message_id: ID = f"(ID: {message_id})" else: ID = " " text = f"将 一条消息{ID}设为精华" logger.info(f"群 {group_id} 一条消息{ID}被 {operator_id} 设为精华") else: if user_id == self_id: text = f"将 {sender_name}(你)的消息设为精华" logger.info(f"群 {group_id} Bot的消息被 {operator_id} 设为精华") else: text = f"将 {sender_name} 的消息设为精华" logger.info(f"群 {group_id} 用户 {sender_id} 的消息被 {operator_id} 设为精华") else: text = f"精华消息事件:{sub_type}" handled_message = Seg(type="text", data=text) user_info = UserInfo( platform=global_config.maibot_server.platform_name, user_id=operator_id, user_nickname=operator_name, user_cardname=user_cardname, ) system_notice = True case "group_card": group_id = raw_message.get("group_id") user_id = raw_message.get("user_id") self_id = raw_message.get("self_id") member_info = await get_member_info(self.server_connection, group_id, user_id) if user_id != 0 else None user_nickname = member_info.get("nickname") if member_info and member_info.get("nickname") else str(user_id) old_card = raw_message.get("card_old") or user_nickname new_card = raw_message.get("card_new") or "(默认)" if new_card == "(默认)" or new_card == user_nickname: return if user_id == self_id: text = f"(你)群卡片被修改为:{old_card} → {new_card}" logger.info(f"群 {group_id} Bot的群名片被修改为:{old_card} → {new_card}") else: text = f"群卡片被修改为:{old_card} → {new_card}" logger.info(f"群 {group_id} 用户 {user_id} 的群名片被修改为:{old_card} → {new_card}") handled_message = Seg(type="text", data=text) user_info = UserInfo( platform=global_config.maibot_server.platform_name, user_id=user_id, user_nickname=user_nickname, user_cardname=new_card, ) system_notice = True case "group_msg_emoji_like": group_id = raw_message.get("group_id") user_id = raw_message.get("user_id") likes = raw_message.get("likes", []) member_info = await get_member_info(self.server_connection, group_id, user_id) if user_id != 0 else None user_cardname = member_info.get("card") if member_info else None user_nickname = member_info.get("nickname") if member_info and member_info.get("nickname") else str(user_id) emoji_list = [] for like in likes: emoji_id = str(like.get("emoji_id")) count = like.get("count", 1) if emoji_id in qq_face: emoji_text = qq_face[emoji_id] else: logger.warning(f"不支持的表情:{emoji_id}") return None emoji_list.append(f"{emoji_text} ×{count}") emoji_text_joined = ",".join(emoji_list) if emoji_list else "无" text = f"回应了你的消息:{emoji_text_joined}" logger.info(f"群 {group_id} 用户 {user_id} 回应了你的消息:{emoji_text_joined}") handled_message = Seg(type="text", data=text) user_info = UserInfo( platform=global_config.maibot_server.platform_name, user_id=user_id, user_nickname=user_nickname, user_cardname=user_cardname, ) system_notice = True case _: logger.warning(f"不支持的notice类型: {notice_type}") return None if not handled_message or not user_info: logger.warning("notice处理失败或不支持") return None group_info: GroupInfo = None if group_id: fetched_group_info = await get_group_info(self.server_connection, group_id) group_name: str = None if fetched_group_info: group_name = fetched_group_info.get("group_name") else: logger.warning("无法获取notice消息所在群的名称") group_info = GroupInfo( platform=global_config.maibot_server.platform_name, group_id=group_id, group_name=group_name, ) message_info: BaseMessageInfo = BaseMessageInfo( platform=global_config.maibot_server.platform_name, message_id="notice", time=message_time, user_info=user_info, group_info=group_info, template_info=None, format_info=FormatInfo( content_format=["text", "notify"], accept_format=ACCEPT_FORMAT, ), additional_config={"target_id": target_id}, # 在这里塞了一个target_id,方便mmc那边知道被戳的人是谁 ) message_base: MessageBase = MessageBase( message_info=message_info, message_segment=handled_message, raw_message=json.dumps(raw_message), ) if system_notice: await self.put_notice(message_base) else: logger.info("发送到Maibot处理通知信息") await message_send_instance.message_send(message_base) async def handle_poke_notify( self, raw_message: dict, group_id: int, user_id: int ) -> Tuple[Seg | None, UserInfo | None]: # sourcery skip: merge-comparisons, merge-duplicate-blocks, remove-redundant-if, remove-unnecessary-else, swap-if-else-branches self_info: dict = await get_self_info(self.server_connection) if not self_info: logger.error("自身信息获取失败") return None, None self_id = raw_message.get("self_id") target_id = raw_message.get("target_id") sender_id = raw_message.get("sender_id") or user_id raw_info: list = raw_message.get("raw_info") if group_id: sender_info = await get_member_info(self.server_connection, group_id, sender_id) target_info = await get_member_info(self.server_connection, group_id, target_id) else: sender_info = await get_stranger_info(self.server_connection, sender_id) target_info = await get_stranger_info(self.server_connection, target_id) target_name = target_info.get("nickname") if target_info and target_info.get("nickname") else "未知目标" sender_name = sender_info.get("nickname") if sender_info and sender_info.get("nickname") else "QQ用户" text_str = "" if isinstance(raw_info, list) and len(raw_info) > 0: try: parts = [] for item in raw_info: if isinstance(item, dict): val = (item.get("txt") or item.get("name") or "").strip() if val and not all(ch in " \n\t" for ch in val): parts.append(val) text_str = "".join(parts).strip() except Exception as e: logger.warning(f"原始 raw_info 解析失败: {e}") else: text_str = "" # --------------------- # 识别动作(仅识别开头的、前后相同汉字的 “X了X”) # 例:拍了拍、戳了戳、摸了摸;不会识别“用了它”、“看了看他” # --------------------- # 只在文本最前面查找一次动作 m = re.match(r'^\s*([\u4e00-\u9fa5])了\1', text_str) if m: action = m.group(0) # 如 “拍了拍” has_action = True text_str = text_str[len(action):].strip() # 移除前缀动作 else: action = "拍了拍" has_action = False # 剩余部分作为描述性后缀 suffix = text_str.strip() # 生成标准格式句子 text_str = f"{action} {target_name}{(' ' + suffix) if suffix else ''}".strip() text_str = " ".join(text_str.split()) # 构造消息段和用户信息 seg_data: Seg = Seg(type="text", data=text_str) user_info: UserInfo = UserInfo( platform=global_config.maibot_server.platform_name, user_id=sender_id, user_nickname=sender_name, user_cardname=sender_info.get("card") if sender_info else None, ) # 提前过滤事件(空对象防御) if not seg_data or not user_info: return None, None # 判断是否需要发送(别人戳Bot / Bot戳别人 / 别人戳别人) send_group_info = None if group_id: fetched_group_info = await get_group_info(self.server_connection, group_id) group_name = fetched_group_info.get("group_name") if fetched_group_info else None send_group_info = GroupInfo( platform=global_config.maibot_server.platform_name, group_id=group_id, group_name=group_name, ) await message_send_instance.message_send( MessageBase( message_info=BaseMessageInfo( platform=global_config.maibot_server.platform_name, message_id="poke", time=time.time(), user_info=user_info, group_info=send_group_info, template_info=None, format_info=FormatInfo( content_format=["text"], accept_format=ACCEPT_FORMAT, ), ), message_segment=seg_data, raw_message=json.dumps(raw_message), ) ) return seg_data, user_info async def handle_ban_notify(self, raw_message: dict, group_id: int) -> Tuple[Seg, UserInfo] | Tuple[None, None]: if not group_id: logger.error("群ID不能为空,无法处理禁言通知") return None, None # 计算user_info self_id = raw_message.get("self_id") operator_id = raw_message.get("operator_id") operator_nickname: str = None operator_cardname: str = None member_info: dict = await get_member_info(self.server_connection, group_id, operator_id) if member_info: operator_nickname = member_info.get("nickname") operator_cardname = member_info.get("card") else: logger.warning("无法获取禁言执行者的昵称,消息可能会无效") operator_nickname = "QQ用户" operator_info: UserInfo = UserInfo( platform=global_config.maibot_server.platform_name, user_id=operator_id, user_nickname=operator_nickname, user_cardname=operator_cardname, ) # 计算Seg user_id = raw_message.get("user_id") banned_user_info: UserInfo = None user_nickname: str = "QQ用户" user_cardname: str = None sub_type: str = None duration = raw_message.get("duration") if duration is None: logger.error("禁言时长不能为空,无法处理禁言通知") return None, None if user_id == 0: # 为全体禁言 sub_type: str = "whole_ban" self._ban_operation(group_id) else: # 为单人禁言 # 获取被禁言人的信息 sub_type: str = "ban" fetched_member_info: dict = await get_member_info(self.server_connection, group_id, user_id) if fetched_member_info: user_nickname = fetched_member_info.get("nickname") user_cardname = fetched_member_info.get("card") banned_user_info: UserInfo = UserInfo( platform=global_config.maibot_server.platform_name, user_id=user_id, user_nickname=user_nickname, user_cardname=user_cardname, ) self._ban_operation(group_id, user_id, int(time.time() + duration)) seg_data: Seg = Seg( type="notify", data={ "sub_type": sub_type, "duration": duration, "banned_user_info": banned_user_info.to_dict() if banned_user_info else None, }, ) fetched_group_info = await get_group_info(self.server_connection, group_id) group_name = fetched_group_info.get("group_name") if fetched_group_info else None # 全体禁言时不显示时间 if duration == -1 or user_id == 0: human_time = "" else: # 格式化时长(天 / 时 / 分 / 秒) days = duration // 86400 hours = (duration % 86400) // 3600 mins = (duration % 3600) // 60 secs = duration % 60 parts = [] if days > 0: parts.append(f"{days} 天") if hours > 0: parts.append(f"{hours} 小时") if mins > 0: parts.append(f"{mins} 分钟") if secs > 0: parts.append(f"{secs} 秒") human_time = " ".join(parts) if parts else "0 秒" display_target = banned_user_info.user_nickname if banned_user_info else None if user_id == self_id: display_target = f"{display_target} (我)" user = "Bot" else: user = f"用户 {user_id}" if user_id == 0: text_str = "开启了群禁言" logger.info(f"群 {group_id} {text_str}") elif human_time: text_str = f"禁言了 {display_target}({human_time})" logger.info(f"群 {group_id} {user} 被 {operator_id} 禁言({human_time})") else: text_str = f"禁言了 {display_target}" logger.info(f"群 {group_id} {user} 被 {operator_id} 禁言") text_seg = Seg(type="text", data=text_str) notify_seg = seg_data return Seg(type="seglist", data=[notify_seg, text_seg]), operator_info async def handle_lift_ban_notify( self, raw_message: dict, group_id: int ) -> Tuple[Seg, UserInfo] | Tuple[None, None]: if not group_id: logger.error("群ID不能为空,无法处理解除禁言通知") return None, None # 计算user_info self_id = raw_message.get("self_id") operator_id = raw_message.get("operator_id") operator_nickname: str = None operator_cardname: str = None member_info: dict = await get_member_info(self.server_connection, group_id, operator_id) if member_info: operator_nickname = member_info.get("nickname") operator_cardname = member_info.get("card") else: logger.warning("无法获取解除禁言执行者的昵称,消息可能会无效") operator_nickname = "QQ用户" operator_info: UserInfo = UserInfo( platform=global_config.maibot_server.platform_name, user_id=operator_id, user_nickname=operator_nickname, user_cardname=operator_cardname, ) # 计算Seg sub_type: str = None user_nickname: str = "QQ用户" user_cardname: str = None lifted_user_info: UserInfo = None user_id = raw_message.get("user_id") if user_id == 0: # 全体禁言解除 sub_type = "whole_lift_ban" self._lift_operation(group_id) else: # 单人禁言解除 sub_type = "lift_ban" # 获取被解除禁言人的信息 fetched_member_info: dict = await get_member_info(self.server_connection, group_id, user_id) if fetched_member_info: user_nickname = fetched_member_info.get("nickname") user_cardname = fetched_member_info.get("card") else: logger.warning("无法获取解除禁言消息发送者的昵称,消息可能会无效") lifted_user_info: UserInfo = UserInfo( platform=global_config.maibot_server.platform_name, user_id=user_id, user_nickname=user_nickname, user_cardname=user_cardname, ) self._lift_operation(group_id, user_id) # 防止自然检测重复触发:从 banned_list 中移除该用户 self.banned_list = [r for r in self.banned_list if not (r.user_id == user_id and r.group_id == group_id)] # 避免 lifted_list 重复添加 self.lifted_list = [r for r in self.lifted_list if not (r.user_id == user_id and r.group_id == group_id)] seg_data: Seg = Seg( type="notify", data={ "sub_type": sub_type, "lifted_user_info": lifted_user_info.to_dict() if lifted_user_info else None, }, ) fetched_group_info = await get_group_info(self.server_connection, group_id) group_name = fetched_group_info.get("group_name") if fetched_group_info else None display_target = lifted_user_info.user_nickname if lifted_user_info else None if display_target and user_id == self_id: display_target = f"{display_target} (我)" if not display_target: text_str = "解除了群禁言" logger.info(f"群 {group_id} {text_str}") else: text_str = f"{display_target} 已被解除禁言" logger.info(f"群 {group_id} 已解除 {user_id} 的禁言") text_seg = Seg(type="text", data=text_str) notify_seg = seg_data return Seg(type="seglist", data=[notify_seg, text_seg]), operator_info async def put_notice(self, message_base: MessageBase) -> None: """ 将处理后的通知消息放入通知队列 """ if notice_queue.full() or unsuccessful_notice_queue.full(): logger.warning("通知队列已满,可能是多次发送失败,消息丢弃") else: await notice_queue.put(message_base) async def handle_natural_lift(self) -> None: while True: if len(self.lifted_list) != 0: lift_record = self.lifted_list.pop(0) group_id = lift_record.group_id user_id = lift_record.user_id # 防御:防止重复解除 if any(r.user_id == user_id and r.group_id == group_id for r in self.lifted_list): if user_id == 0: logger.debug(f"检测到重复解除群禁言请求(群{group_id}),跳过。") else: logger.debug(f"检测到重复解除禁言请求(群{group_id} 用户{user_id}),跳过。") continue # 检查是否解除禁言 if not any(r.user_id == user_id and r.group_id == group_id for r in db_manager.get_ban_records()): if user_id == 0: logger.info(f"群 {group_id} 已解除群禁言。") else: logger.info(f"群 {group_id} 用户 {user_id} 已被解除禁言。") continue db_manager.delete_ban_record(lift_record) # 从数据库中删除禁言记录 seg_message: Seg = await self.natural_lift(group_id, user_id) fetched_group_info = await get_group_info(self.server_connection, group_id) group_name: str = None if fetched_group_info: group_name = fetched_group_info.get("group_name") else: logger.warning("无法获取notice消息所在群的名称") group_info = GroupInfo( platform=global_config.maibot_server.platform_name, group_id=group_id, group_name=group_name, ) system_user = UserInfo( platform=global_config.maibot_server.platform_name, user_id=0, user_nickname="系统", user_cardname=None, ) message_info: BaseMessageInfo = BaseMessageInfo( platform=global_config.maibot_server.platform_name, message_id="notice", time=time.time(), user_info=system_user, # 自然解除禁言没有操作者 group_info=group_info, template_info=None, format_info=None, ) message_base: MessageBase = MessageBase( message_info=message_info, message_segment=seg_message, raw_message=json.dumps( { "post_type": "notice", "notice_type": "group_ban", "sub_type": "lift_ban", "group_id": group_id, "user_id": user_id, "operator_id": None, # 自然解除禁言没有操作者 } ), ) await self.put_notice(message_base) await asyncio.sleep(0.5) # 确保队列处理间隔 else: await asyncio.sleep(5) # 每5秒检查一次 async def natural_lift(self, group_id: int, user_id: int) -> Seg | None: if not group_id: logger.error("群ID不能为空,无法处理解除禁言通知") return None if user_id == 0: # 理论上永远不会触发 return Seg( type="notify", data={ "sub_type": "whole_lift_ban", "lifted_user_info": None, }, ) user_nickname: str = "QQ用户" user_cardname: str = None fetched_member_info: dict = await get_member_info(self.server_connection, group_id, user_id) if fetched_member_info: user_nickname = fetched_member_info.get("nickname") user_cardname = fetched_member_info.get("card") self_info = await get_self_info(self.server_connection) self_id = self_info.get("user_id") if self_info else None if self_id and user_id == self_id: user_nickname = f"{user_nickname} (我)" lifted_user_info: UserInfo = UserInfo( platform=global_config.maibot_server.platform_name, user_id=user_id, user_nickname=user_nickname, user_cardname=user_cardname, ) fetched_group_info = await get_group_info(self.server_connection, group_id) group_name = fetched_group_info.get("group_name") if fetched_group_info else None notify_seg = Seg( type="notify", data={ "sub_type": "lift_ban", "lifted_user_info": lifted_user_info.to_dict(), }, ) text_seg = Seg(type="text", data=f"解除了 {user_nickname} 的禁言") logger.info(f"群 {group_id} 用户 {user_id} 的禁言被自然解除") return Seg(type="seglist", data=[notify_seg, text_seg]) async def auto_lift_detect(self) -> None: while True: self_info = await get_self_info(self.server_connection) self_id = self_info.get("user_id") if self_info else None if len(self.banned_list) == 0: await asyncio.sleep(5) continue for ban_record in list(self.banned_list): if ban_record.user_id == 0 or ban_record.lift_time == -1: continue if ban_record.lift_time <= int(time.time()): if ban_record not in self.lifted_list: self.lifted_list.append(ban_record) if ban_record.user_id == self_id: logger.info(f"检测到 Bot 自身在群 {ban_record.group_id} 的禁言已解除") else: logger.info(f"检测到用户 {ban_record.user_id} 在群 {ban_record.group_id} 的禁言已解除") self.banned_list.remove(ban_record) await asyncio.sleep(5) async def send_notice(self) -> None: """ 发送通知消息到Napcat """ while True: try: if not unsuccessful_notice_queue.empty(): to_be_send: MessageBase = await unsuccessful_notice_queue.get() try: send_status = await message_send_instance.message_send(to_be_send) if send_status: unsuccessful_notice_queue.task_done() else: await unsuccessful_notice_queue.put(to_be_send) except Exception as e: logger.error(f"发送通知消息失败: {str(e)}") await unsuccessful_notice_queue.put(to_be_send) await asyncio.sleep(1) continue to_be_send: MessageBase = await notice_queue.get() try: send_status = await message_send_instance.message_send(to_be_send) if send_status: notice_queue.task_done() else: await unsuccessful_notice_queue.put(to_be_send) except Exception as e: logger.error(f"发送通知消息失败: {str(e)}") await unsuccessful_notice_queue.put(to_be_send) await asyncio.sleep(1) except asyncio.CancelledError: logger.warning("send_notice 任务被取消,安全退出循环") break except Exception as e: logger.error(f"通知循环出现异常: {e}") await asyncio.sleep(3) notice_handler = NoticeHandler()