feat: add idempotent douyu daily report command
This commit is contained in:
@@ -1,6 +1,17 @@
|
|||||||
[Douyu]
|
[Douyu]
|
||||||
enable = true
|
enable = true
|
||||||
command = ["斗鱼订阅", "取消斗鱼订阅", "斗鱼订阅列表"]
|
command = [
|
||||||
|
"斗鱼订阅",
|
||||||
|
"取消斗鱼订阅",
|
||||||
|
"斗鱼订阅列表",
|
||||||
|
"斗鱼订阅提醒",
|
||||||
|
"取消斗鱼订阅提醒",
|
||||||
|
"订阅鱼吧",
|
||||||
|
"取消订阅鱼吧",
|
||||||
|
"鱼吧订阅列表",
|
||||||
|
"#斗鱼弹幕日报",
|
||||||
|
"斗鱼弹幕日报"
|
||||||
|
]
|
||||||
check_interval_minutes = 5
|
check_interval_minutes = 5
|
||||||
api_url_template = "https://www.douyu.com/betard/{room_id}"
|
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"
|
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"
|
||||||
|
|||||||
@@ -420,7 +420,7 @@ class DouyuPlugin(MessagePluginInterface):
|
|||||||
self.feature = self.register_feature()
|
self.feature = self.register_feature()
|
||||||
self.redis_manager: Optional[DouyuRedisManager] = None
|
self.redis_manager: Optional[DouyuRedisManager] = None
|
||||||
self._commands = ["斗鱼订阅", "取消斗鱼订阅", "斗鱼订阅列表", "斗鱼订阅提醒", "取消斗鱼订阅提醒",
|
self._commands = ["斗鱼订阅", "取消斗鱼订阅", "斗鱼订阅列表", "斗鱼订阅提醒", "取消斗鱼订阅提醒",
|
||||||
"订阅鱼吧", "取消订阅鱼吧", "鱼吧订阅列表"]
|
"订阅鱼吧", "取消订阅鱼吧", "鱼吧订阅列表", "#斗鱼弹幕日报", "斗鱼弹幕日报"]
|
||||||
self._api_template = "https://www.douyu.com/betard/{room_id}"
|
self._api_template = "https://www.douyu.com/betard/{room_id}"
|
||||||
self._yuba_api = "https://yuba.douyu.com/wgapi/yubanc/api/feed/getUserFeedList"
|
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"
|
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)
|
await self.bot.send_text_message(roomid or sender, f"✅ 已取消订阅斗鱼鱼吧 {hash_id}", sender)
|
||||||
return True, "取消成功" if ok else "取消失败"
|
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
|
return False, None
|
||||||
|
|
||||||
async def _scheduled_check_job(self):
|
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:
|
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}"
|
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:
|
def _should_run_daily_report(self, now_dt: datetime) -> bool:
|
||||||
time_text = str(self._daily_report_time or "").strip()
|
time_text = str(self._daily_report_time or "").strip()
|
||||||
try:
|
try:
|
||||||
@@ -1418,10 +1478,47 @@ class DouyuPlugin(MessagePluginInterface):
|
|||||||
)
|
)
|
||||||
return self._build_fallback_daily_report(payload)
|
return self._build_fallback_daily_report(payload)
|
||||||
|
|
||||||
async def _send_daily_reports(self, anchor_day: str):
|
async def _get_or_create_daily_report_result(self, room_id: str, anchor_day: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
rooms = self.redis_manager.all_subscribed_rooms()
|
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:
|
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
|
continue
|
||||||
sessions = self._load_sessions_for_anchor_day(room_id, anchor_day)
|
sessions = self._load_sessions_for_anchor_day(room_id, anchor_day)
|
||||||
if not sessions:
|
if not sessions:
|
||||||
@@ -1431,13 +1528,14 @@ class DouyuPlugin(MessagePluginInterface):
|
|||||||
payload = self._build_daily_report_payload(room_id, anchor_day, sessions)
|
payload = self._build_daily_report_payload(room_id, anchor_day, sessions)
|
||||||
if not payload:
|
if not payload:
|
||||||
continue
|
continue
|
||||||
report_text = await self._generate_daily_report_text(payload)
|
report_result = await self._get_or_create_daily_report_result(room_id, anchor_day, payload)
|
||||||
report_image = None
|
report_text = str(report_result.get("report_text") or "").strip()
|
||||||
if self._daily_report_send_image:
|
report_image = self._resolve_existing_report_image(report_result.get("report_image"))
|
||||||
report_image = await self._render_daily_report_image(payload)
|
groups = [target_group_id] if target_group_id else self.redis_manager.groups_for_room(room_id)
|
||||||
groups = self.redis_manager.groups_for_room(room_id)
|
|
||||||
delivered = False
|
delivered = False
|
||||||
for gid in groups:
|
for gid in groups:
|
||||||
|
if not gid:
|
||||||
|
continue
|
||||||
if GroupBotManager.get_group_permission(gid, self.feature) != PermissionStatus.ENABLED:
|
if GroupBotManager.get_group_permission(gid, self.feature) != PermissionStatus.ENABLED:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
@@ -1446,6 +1544,7 @@ class DouyuPlugin(MessagePluginInterface):
|
|||||||
else:
|
else:
|
||||||
await self.bot.send_text_message(gid, report_text)
|
await self.bot.send_text_message(gid, report_text)
|
||||||
delivered = True
|
delivered = True
|
||||||
|
delivered_any = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"发送斗鱼每日报告失败(room={room_id}, group={gid}): {e}")
|
logger.error(f"发送斗鱼每日报告失败(room={room_id}, group={gid}): {e}")
|
||||||
if delivered:
|
if delivered:
|
||||||
@@ -1453,6 +1552,7 @@ class DouyuPlugin(MessagePluginInterface):
|
|||||||
self._daily_report_room_key(room_id, anchor_day),
|
self._daily_report_room_key(room_id, anchor_day),
|
||||||
datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
)
|
)
|
||||||
|
return delivered_any
|
||||||
|
|
||||||
def _start_danmu_record(self, room_id: str):
|
def _start_danmu_record(self, room_id: str):
|
||||||
recorder = self._get_danmu_recorder(room_id)
|
recorder = self._get_danmu_recorder(room_id)
|
||||||
|
|||||||
Reference in New Issue
Block a user