from __future__ import annotations import logging import time from yuxi.channel.extensions.bilibili.danmaku.types import DanmakuMessage, SuperChatEvent from yuxi.channel.extensions.bilibili.types import BilibiliAccountConfig from yuxi.channel.message.models import ( GroupContext, MessageType, PeerInfo, UnifiedMessage, ) from yuxi.channel.routing.models import PeerKind logger = logging.getLogger(__name__) _GUARD_LEVEL_NAME = {1: "总督", 2: "提督", 3: "舰长"} class DanmakuEventHandler: def __init__(self, room_id: int, account, runtime, account_id: str = "default"): self._room_id = room_id self._account = account self._runtime = runtime self._account_id = account_id self._bot_uid = str(account.dedeuserid) self._room_users_seen: set[str] = set() async def handle_danmaku(self, data: dict) -> None: info = data["info"] text = info[1] user_info = info[2] uid = str(user_info[0]) uname = user_info[1] msg = DanmakuMessage( room_id=self._room_id, uid=uid, uname=uname, text=text, timestamp=time.time(), fans_medal_name=info[3][1] if len(info[3]) > 1 else "", fans_medal_level=info[3][0] if len(info[3]) > 0 else 0, ul_level=info[5][0] if len(info[5]) > 0 else 0, ) if uid == self._bot_uid: return if self._account.require_mention and not self._should_respond(msg): return await self._dispatch_to_agent(msg) async def handle_super_chat(self, data: dict) -> None: event = SuperChatEvent( room_id=self._room_id, uid=str(data["uid"]), uname=data["user_info"]["uname"], message=data["message"], price=data["price"], start_time=data["start_time"], end_time=data["end_time"], timestamp=time.time(), ) await self._dispatch_to_agent(event) async def handle_gift(self, data: dict) -> None: uname = data.get("uname", "") uid = str(data.get("uid", "")) num = data.get("num", 0) gift_name = data.get("giftName", "") price = data.get("price", 0) logger.info("直播间 %s 礼物: %s 送出 %sx %s", self._room_id, uname, num, gift_name) content = f"[礼物通知] {uname} 送出了 {num}x {gift_name}" if price: content += f" (总价 {price} 电池)" await self._dispatch_event("gift", uid, uname, content) async def handle_guard(self, data: dict) -> None: username = data.get("username", "") uid = str(data.get("uid", "")) guard_level = data.get("guard_level", 0) guard_name = _GUARD_LEVEL_NAME.get(guard_level, f"舰队Lv{guard_level}") logger.info("直播间 %s 大航海: %s 开通 %s", self._room_id, username, guard_name) content = f"[舰队通知] {username} 开通了{guard_name}!" await self._dispatch_event("guard", uid, username, content) async def handle_interact(self, data: dict) -> None: msg_type = data.get("msg_type", 0) if msg_type != 1: return uid = str(data.get("uid", "")) uname = data.get("uname", "") if uid in self._room_users_seen: return self._room_users_seen.add(uid) content = f"[进房通知] {uname} 进入了直播间" await self._dispatch_event("enter", uid, uname, content) async def handle_live(self, data: dict) -> None: logger.info("直播间 %s 开播", self._room_id) await self._dispatch_status_event("live", "[直播状态] 开播了") async def handle_preparing(self, data: dict) -> None: logger.info("直播间 %s 下播", self._room_id) await self._dispatch_status_event("preparing", "[直播状态] 下播了") async def handle_cut_off(self, data: dict) -> None: logger.warning("直播间 %s 被超管切断", self._room_id) await self._dispatch_status_event("cut_off", "[直播状态] 直播间被超管切断") async def handle_warning(self, data: dict) -> None: logger.warning("直播间 %s 收到管理员警告", self._room_id) await self._dispatch_status_event("warning", "[直播状态] 收到管理员警告") async def handle_combo_send(self, data: dict) -> None: uname = data.get("uname", "") uid = str(data.get("uid", "")) combo_num = data.get("combo_num", 0) gift_name = data.get("gift_name", "") logger.info("直播间 %s 连击: %s 连送 %s 个 %s", self._room_id, uname, combo_num, gift_name) content = f"[连击礼物] {uname} 连送 {combo_num} 个 {gift_name}" await self._dispatch_event("combo_send", uid, uname, content) async def handle_sc_delete(self, data: dict) -> None: message_id = data.get("message_id", "") logger.info("直播间 %s SC 删除: message_id=%s", self._room_id, message_id) content = f"[SC 删除] 消息 #{message_id} 已被删除" await self._dispatch_status_event("sc_delete", content) async def handle_user_toast(self, data: dict) -> None: uid = str(data.get("uid", "")) uname = data.get("username", "") toast_msg = data.get("toast_msg", "") guard_level = data.get("guard_level", 0) price = data.get("price", 0) num = data.get("num", 0) gift_name = data.get("gift_name", "") logger.info("直播间 %s Toast: %s - %s", self._room_id, uname, toast_msg) if guard_level: guard_name = _GUARD_LEVEL_NAME.get(guard_level, f"舰队Lv{guard_level}") content = f"[上舰感谢] {uname} 开通{guard_name}!" elif gift_name: content = f"[礼物感谢] {uname} 赠送 {num}x {gift_name}" if price: content += f" (总价 {price} 电池)" else: content = f"[系统通知] {toast_msg}" await self._dispatch_event("toast", uid, uname, content) async def handle_welcome_guard(self, data: dict) -> None: uid = str(data.get("uid", "")) username = data.get("username", "") guard_level = data.get("guard_level", 0) guard_name = _GUARD_LEVEL_NAME.get(guard_level, f"舰队Lv{guard_level}") logger.info("直播间 %s 舰长入场: %s (%s)", self._room_id, username, guard_name) content = f"[舰长入场] {username} ({guard_name}) 进入了直播间" await self._dispatch_event("welcome_guard", uid, username, content) async def handle_entry_effect(self, data: dict) -> None: uid = str(data.get("uid", "")) uname = data.get("uname", "") effect_id = data.get("effect_id", 0) logger.info("直播间 %s 进场特效: %s (effect_id=%s)", self._room_id, uname, effect_id) content = f"[进场特效] {uname} 进入了直播间" await self._dispatch_event("entry_effect", uid, uname, content) async def handle_super_chat_jpn(self, data: dict) -> None: event = SuperChatEvent( room_id=self._room_id, uid=str(data.get("uid", "")), uname=data.get("user_info", {}).get("uname", ""), message=data.get("message", ""), price=data.get("price", 0), start_time=data.get("start_time", 0), end_time=data.get("end_time", 0), timestamp=time.time(), ) await self._dispatch_to_agent(event) async def handle_pk_start(self, data: dict) -> None: pk_id = data.get("pk_id", "") logger.info("直播间 %s PK 开始: pk_id=%s", self._room_id, pk_id) content = "[PK通知] 大乱斗开始了!" await self._dispatch_status_event("pk_start", content) async def handle_pk_end(self, data: dict) -> None: pk_id = data.get("pk_id", "") logger.info("直播间 %s PK 结束: pk_id=%s", self._room_id, pk_id) content = "[PK通知] 大乱斗结束了" await self._dispatch_status_event("pk_end", content) async def handle_pk_process(self, data: dict) -> None: logger.debug("直播间 %s PK 进行中", self._room_id) async def handle_anchor_lot_start(self, data: dict) -> None: award_name = data.get("award_name", "") logger.info("直播间 %s 天选时刻开始: %s", self._room_id, award_name) content = f"[天选时刻] 抽奖开始 - {award_name}" await self._dispatch_status_event("anchor_lot_start", content) async def handle_anchor_lot_end(self, data: dict) -> None: logger.info("直播间 %s 天选时刻结束", self._room_id) content = "[天选时刻] 抽奖已结束" await self._dispatch_status_event("anchor_lot_end", content) async def handle_anchor_lot_award(self, data: dict) -> None: award_name = data.get("award_name", "") winners = data.get("award_users", []) winner_names = [w.get("uname", "") for w in winners] logger.info("直播间 %s 天选时刻开奖: %s -> %s", self._room_id, award_name, winner_names) content = f"[天选时刻] 恭喜 {', '.join(winner_names)} 获得 {award_name}" await self._dispatch_status_event("anchor_lot_award", content) async def handle_notice_msg(self, data: dict) -> None: msg_common = data.get("msg_common", "") business_id = data.get("business_id", "") msg_type = data.get("msg_type", 0) logger.info("直播间 %s 系统通知: type=%s business=%s", self._room_id, msg_type, business_id) content = f"[系统通知] {msg_common}" await self._dispatch_status_event("notice", content) async def handle_welcome(self, data: dict) -> None: uname = data.get("uname", "") logger.debug("直播间 %s 老爷入场: %s", self._room_id, uname) async def handle_online_rank(self, data: dict) -> None: logger.debug("直播间 %s 高能榜更新 (count=...)", self._room_id) async def handle_watched_change(self, data: dict) -> None: num = data.get("num", 0) text_large = data.get("text_large", "") logger.debug("直播间 %s 观看人数: %s (%s)", self._room_id, num, text_large) async def handle_like_info(self, data: dict) -> None: logger.debug("直播间 %s 点赞更新", self._room_id) async def handle_room_change(self, data: dict) -> None: title = data.get("title", "") area_name = data.get("area_name", "") logger.info("直播间 %s 信息变更: title=%s area=%s", self._room_id, title, area_name) async def handle_voice_join(self, data: dict) -> None: logger.debug("直播间 %s 语音连麦状态变更", self._room_id) async def handle_red_pocket_start(self, data: dict) -> None: logger.debug("直播间 %s 红包活动开始", self._room_id) async def handle_red_pocket_winner(self, data: dict) -> None: logger.debug("直播间 %s 红包中奖名单", self._room_id) def _should_respond(self, msg: DanmakuMessage) -> bool: text = msg.text.strip() keywords = self._account.bot_keywords or [] return any(kw in text for kw in keywords) or len(text) > 0 async def _dispatch_to_agent(self, msg) -> None: content = msg.text if hasattr(msg, "text") else msg.message unified = UnifiedMessage( msg_id=f"danmaku_{msg.room_id}_{msg.uid}_{int(msg.timestamp)}", channel_type="bilibili", account_id=self._account_id, content=content, sender=PeerInfo( kind=PeerKind.GROUP, id=msg.uid, display_name=msg.uname, ), group=GroupContext( id=str(msg.room_id), route_peer_kind="group", route_peer_id=str(msg.room_id), ), message_type=MessageType.TEXT, timestamp=time.time(), metadata={"source": "bilibili_danmaku"}, ) queue = getattr(self._runtime, "queue", None) if queue: await queue.put(unified) else: logger.warning("运行时队列不可用,无法分发弹幕消息") async def _dispatch_event(self, event_type: str, uid: str, uname: str, content: str) -> None: unified = UnifiedMessage( msg_id=f"{event_type}_{self._room_id}_{uid}_{int(time.time())}", channel_type="bilibili", account_id=self._account_id, content=content, sender=PeerInfo(kind=PeerKind.GROUP, id=uid, display_name=uname), group=GroupContext( id=str(self._room_id), route_peer_kind="group", route_peer_id=str(self._room_id), ), message_type=MessageType.EVENT, timestamp=time.time(), metadata={"source": "bilibili_danmaku", "event_type": event_type}, ) queue = getattr(self._runtime, "queue", None) if queue: await queue.put(unified) else: logger.warning("运行时队列不可用,无法分发 %s 事件", event_type) async def _dispatch_status_event(self, event_type: str, content: str) -> None: unified = UnifiedMessage( msg_id=f"live_status_{self._room_id}_{event_type}_{int(time.time())}", channel_type="bilibili", account_id=self._account_id, content=content, sender=PeerInfo(kind=PeerKind.GROUP, id="system", display_name="B站系统"), group=GroupContext( id=str(self._room_id), route_peer_kind="group", route_peer_id=str(self._room_id), ), message_type=MessageType.EVENT, timestamp=time.time(), metadata={"source": "bilibili_danmaku", "event_type": event_type}, ) queue = getattr(self._runtime, "queue", None) if queue: await queue.put(unified) else: logger.warning("运行时队列不可用,无法分发 %s 状态事件", event_type) def resolve_reply_target(msg: DanmakuMessage, account: BilibiliAccountConfig) -> str | None: if account.danmaku_reply_enabled: return f"danmaku:{msg.room_id}" elif account.dm_enabled: return f"dm:{msg.uid}" else: return None