diff --git a/plugins/douyu/config.toml b/plugins/douyu/config.toml index 4fa7c8a..1708e4d 100644 --- a/plugins/douyu/config.toml +++ b/plugins/douyu/config.toml @@ -1,6 +1,17 @@ [Douyu] enable = true -command = ["斗鱼订阅", "取消斗鱼订阅", "斗鱼订阅列表"] +command = [ + "斗鱼订阅", + "取消斗鱼订阅", + "斗鱼订阅列表", + "斗鱼订阅提醒", + "取消斗鱼订阅提醒", + "订阅鱼吧", + "取消订阅鱼吧", + "鱼吧订阅列表", + "#斗鱼弹幕日报", + "斗鱼弹幕日报" +] check_interval_minutes = 5 api_url_template = "https://www.douyu.com/betard/{room_id}" user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" diff --git a/plugins/douyu/main.py b/plugins/douyu/main.py index 8c7b143..331cb16 100644 --- a/plugins/douyu/main.py +++ b/plugins/douyu/main.py @@ -420,7 +420,7 @@ class DouyuPlugin(MessagePluginInterface): self.feature = self.register_feature() self.redis_manager: Optional[DouyuRedisManager] = None self._commands = ["斗鱼订阅", "取消斗鱼订阅", "斗鱼订阅列表", "斗鱼订阅提醒", "取消斗鱼订阅提醒", - "订阅鱼吧", "取消订阅鱼吧", "鱼吧订阅列表"] + "订阅鱼吧", "取消订阅鱼吧", "鱼吧订阅列表", "#斗鱼弹幕日报", "斗鱼弹幕日报"] self._api_template = "https://www.douyu.com/betard/{room_id}" self._yuba_api = "https://yuba.douyu.com/wgapi/yubanc/api/feed/getUserFeedList" self._user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" @@ -595,6 +595,26 @@ class DouyuPlugin(MessagePluginInterface): await self.bot.send_text_message(roomid or sender, f"✅ 已取消订阅斗鱼鱼吧 {hash_id}", sender) return True, "取消成功" if ok else "取消失败" + if first_token in {"#斗鱼弹幕日报", "斗鱼弹幕日报"}: + if not roomid: + await self.bot.send_text_message(sender, "请在群聊中使用该命令", sender) + return True, "仅支持群聊" + parts = content.split() + anchor_day = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d") + if len(parts) >= 2: + day_text = parts[1].strip() + try: + anchor_day = datetime.strptime(day_text, "%Y-%m-%d").strftime("%Y-%m-%d") + except Exception: + await self.bot.send_text_message(roomid, "日期格式错误,请使用:#斗鱼弹幕日报 2026-04-07", sender) + return True, "日期格式错误" + await self.bot.send_text_message(roomid, f"⏳ 正在生成斗鱼弹幕日报:{anchor_day}", sender) + delivered = await self._send_daily_reports(anchor_day, target_group_id=roomid, force=True) + if delivered: + return True, f"斗鱼弹幕日报已发送:{anchor_day}" + await self.bot.send_text_message(roomid, f"暂无可发送的斗鱼弹幕日报:{anchor_day}", sender) + return True, "暂无日报" + return False, None async def _scheduled_check_job(self): @@ -941,6 +961,46 @@ class DouyuPlugin(MessagePluginInterface): def _daily_report_room_key(self, room_id: str, anchor_day: str) -> str: return f"{self.redis_manager.prefix}daily_report:{room_id}:{anchor_day}" + @staticmethod + def _daily_report_cache_dir() -> str: + path = os.path.join("temp", "douyu_materials") + os.makedirs(path, exist_ok=True) + return path + + def _daily_report_cache_path(self, room_id: str, anchor_day: str) -> str: + return os.path.join( + self._daily_report_cache_dir(), + f"{room_id}_{anchor_day.replace('-', '')}_daily_report_result.json", + ) + + def _load_daily_report_cache(self, room_id: str, anchor_day: str) -> Optional[Dict[str, Any]]: + cache_path = self._daily_report_cache_path(room_id, anchor_day) + if not os.path.exists(cache_path): + return None + try: + with open(cache_path, "r", encoding="utf-8") as f: + data = json.load(f) + if isinstance(data, dict): + return data + except Exception as e: + logger.warning(f"读取斗鱼每日报告缓存失败(room={room_id}, day={anchor_day}): {e}") + return None + + def _save_daily_report_cache(self, room_id: str, anchor_day: str, data: Dict[str, Any]) -> None: + cache_path = self._daily_report_cache_path(room_id, anchor_day) + try: + with open(cache_path, "w", encoding="utf-8") as f: + json.dump(data, f, ensure_ascii=False, indent=2) + except Exception as e: + logger.warning(f"保存斗鱼每日报告缓存失败(room={room_id}, day={anchor_day}): {e}") + + @staticmethod + def _resolve_existing_report_image(image_path: Optional[str]) -> Optional[str]: + path = str(image_path or "").strip() + if not path: + return None + return path if os.path.exists(path) else None + def _should_run_daily_report(self, now_dt: datetime) -> bool: time_text = str(self._daily_report_time or "").strip() try: @@ -1418,10 +1478,47 @@ class DouyuPlugin(MessagePluginInterface): ) return self._build_fallback_daily_report(payload) - async def _send_daily_reports(self, anchor_day: str): - rooms = self.redis_manager.all_subscribed_rooms() + async def _get_or_create_daily_report_result(self, room_id: str, anchor_day: str, payload: Dict[str, Any]) -> Dict[str, Any]: + cached = self._load_daily_report_cache(room_id, anchor_day) or {} + cached_image = self._resolve_existing_report_image(cached.get("report_image")) + cached_text = str(cached.get("report_text") or "").strip() + if cached_image or cached_text: + return { + "report_text": cached_text, + "report_image": cached_image, + "cached": True, + } + + report_text = await self._generate_daily_report_text(payload) + report_image = None + if self._daily_report_send_image: + report_image = await self._render_daily_report_image(payload) + + result = { + "room_id": room_id, + "anchor_day": anchor_day, + "report_text": report_text, + "report_image": report_image, + "generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + } + self._save_daily_report_cache(room_id, anchor_day, result) + result["cached"] = False + return result + + async def _send_daily_reports( + self, + anchor_day: str, + target_group_id: Optional[str] = None, + force: bool = False, + ) -> bool: + rooms = ( + set(self.redis_manager.list_group_rooms(target_group_id)) + if target_group_id + else self.redis_manager.all_subscribed_rooms() + ) + delivered_any = False for room_id in rooms: - if self.redis_manager.get_text_value(self._daily_report_room_key(room_id, anchor_day)): + if not force and self.redis_manager.get_text_value(self._daily_report_room_key(room_id, anchor_day)): continue sessions = self._load_sessions_for_anchor_day(room_id, anchor_day) if not sessions: @@ -1431,13 +1528,14 @@ class DouyuPlugin(MessagePluginInterface): payload = self._build_daily_report_payload(room_id, anchor_day, sessions) if not payload: continue - report_text = await self._generate_daily_report_text(payload) - report_image = None - if self._daily_report_send_image: - report_image = await self._render_daily_report_image(payload) - groups = self.redis_manager.groups_for_room(room_id) + report_result = await self._get_or_create_daily_report_result(room_id, anchor_day, payload) + report_text = str(report_result.get("report_text") or "").strip() + report_image = self._resolve_existing_report_image(report_result.get("report_image")) + groups = [target_group_id] if target_group_id else self.redis_manager.groups_for_room(room_id) delivered = False for gid in groups: + if not gid: + continue if GroupBotManager.get_group_permission(gid, self.feature) != PermissionStatus.ENABLED: continue try: @@ -1446,6 +1544,7 @@ class DouyuPlugin(MessagePluginInterface): else: await self.bot.send_text_message(gid, report_text) delivered = True + delivered_any = True except Exception as e: logger.error(f"发送斗鱼每日报告失败(room={room_id}, group={gid}): {e}") if delivered: @@ -1453,6 +1552,7 @@ class DouyuPlugin(MessagePluginInterface): self._daily_report_room_key(room_id, anchor_day), datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ) + return delivered_any def _start_danmu_record(self, room_id: str): recorder = self._get_danmu_recorder(room_id)