"""Bilibili Live Danmaku Handler. Connects to a Bilibili live room and puts incoming danmaku messages into an asyncio queue. """ import asyncio import logging from typing import Optional import blivedm from blivedm.models.web import DanmakuMessage logger = logging.getLogger(__name__) DANMAKU_FORMAT = "{uname}: {msg}" class DanmakuHandler(blivedm.BaseHandler): """Handles incoming danmaku from a Bilibili live room.""" def __init__(self, queue: asyncio.Queue, message_format: str = DANMAKU_FORMAT): super().__init__() self._queue = queue self._message_format = message_format def _on_danmaku(self, client: blivedm.BLiveClient, message: DanmakuMessage) -> None: if not message.msg.strip(): return text = self._message_format.format( uname=message.uname, msg=message.msg, uid=message.uid, ) logger.info("Danmaku: %s", text) try: self._queue.put_nowait(text) except asyncio.QueueFull: logger.warning("Danmaku queue full, dropping: %s", text) def _on_gift(self, client: blivedm.BLiveClient, message) -> None: logger.debug("Gift: %s x%d from %s", message.gift_name, message.num, message.uname) def _on_interact_word_v2(self, client: blivedm.BLiveClient, message) -> None: logger.debug("Interact: %s (type=%d)", message.uname, message.msg_type) def on_client_stopped(self, client, exception: Optional[Exception]) -> None: if exception is not None: logger.error("Client stopped with exception: %s", exception) else: logger.info("Client stopped normally.") class DanmakuClient: """Manages the BLiveClient connection for a single room.""" def __init__( self, room_id: int, queue: asyncio.Queue, sessdata: str = "", message_format: str = DANMAKU_FORMAT, ): self._room_id = room_id self._queue = queue self._sessdata = sessdata self._message_format = message_format self._client: Optional[blivedm.BLiveClient] = None async def start(self) -> None: """Connect to the live room and start receiving danmaku.""" import http.cookies import aiohttp session = aiohttp.ClientSession() if self._sessdata: cookies = http.cookies.SimpleCookie() cookies["SESSDATA"] = self._sessdata cookies["SESSDATA"]["domain"] = "bilibili.com" session.cookie_jar.update_cookies(cookies) self._client = blivedm.BLiveClient( self._room_id, session=session, ) handler = DanmakuHandler(self._queue, self._message_format) self._client.set_handler(handler) self._client.start() logger.info("Connected to room %d", self._room_id) async def stop(self) -> None: """Stop the client and clean up.""" if self._client is not None: logger.info("Disconnecting from room %d...", self._room_id) await self._client.stop_and_close() self._client = None @property def room_id(self) -> int: return self._room_id