diff --git a/src/europython_discord/programme_notifications/cog.py b/src/europython_discord/programme_notifications/cog.py index cc9160ce..8bdb93bd 100644 --- a/src/europython_discord/programme_notifications/cog.py +++ b/src/europython_discord/programme_notifications/cog.py @@ -1,7 +1,7 @@ from __future__ import annotations import logging -from datetime import datetime +from datetime import datetime, timedelta from discord import Client, TextChannel from discord.ext import commands, tasks @@ -15,6 +15,16 @@ _logger = logging.getLogger(__name__) +_WEEKDAYS = { + 0: "Monday", + 1: "Tuesday", + 2: "Wednesday", + 3: "Thursday", + 4: "Friday", + 5: "Saturday", + 6: "Sunday", +} + class ProgrammeNotificationsCog(commands.Cog): def __init__(self, bot: Client, config: ProgrammeNotificationsConfig) -> None: @@ -40,21 +50,25 @@ async def on_ready(self) -> None: await self.purge_all_room_channels() _logger.debug(f"Simulated start time: {self.config.simulated_start_time}") _logger.debug(f"Fast mode: {self.config.fast_mode}") + + _logger.info("Starting the livestream notifier...") + self.notify_livestreams.start() _logger.info("Starting the session notifier...") self.notify_sessions.start() _logger.info("Cog 'Programme Notifications' is ready") async def cog_load(self) -> None: """Start schedule updater task.""" - _logger.info( - "Starting the schedule updater and setting the interval for the session notifier..." - ) + _logger.info("Starting the schedule updater and setting the interval for the notifiers...") self.fetch_schedule.start() self.fetch_livestreams.start() + self.notify_livestreams.change_interval( + seconds=2 if self.config.fast_mode and self.config.simulated_start_time else 60 + ) self.notify_sessions.change_interval( seconds=2 if self.config.fast_mode and self.config.simulated_start_time else 60 ) - _logger.info("Schedule updater started and interval set for the session notifier") + _logger.info("Schedule updater started and interval set for the notifiers") async def cog_unload(self) -> None: """Stop all tasks.""" @@ -74,6 +88,36 @@ async def fetch_livestreams(self) -> None: await self.livestream_connector.fetch_livestreams() _logger.info("Finished the periodic livestream update.") + @tasks.loop() + async def notify_livestreams(self) -> None: + livestreams_by_room = self.livestream_connector.livestreams_by_room + if livestreams_by_room is None: + return + + for room_name in self.config.rooms_to_channel_names: + room_channel = self._get_room_channel(room_name) + if room_channel is None: + continue + + _urls_by_date = livestreams_by_room.get(room_name) + if not _urls_by_date: + continue + urls_by_date = sorted(_urls_by_date.items()) # sort by date + + # don't notify of stream if the first stream is multiple days away + today = (await self.programme_connector.get_current_time()).date() + first_streaming_day = urls_by_date[0][0] + if first_streaming_day - today > timedelta(days=1): + topic = f"Channel for room {room_name}" + else: + livestreams_text = "\n".join( + f"Livestream {_WEEKDAYS[day.timetuple().tm_wday]}: [YouTube]({url})" + for day, url in urls_by_date + ) + topic = f"Channel for room {room_name}\n\n{livestreams_text}" + + await room_channel.edit(topic=topic) + @tasks.loop() async def notify_sessions(self) -> None: # determine sessions to send notifications for @@ -105,11 +149,8 @@ async def notify_sessions(self) -> None: # send session notification message to room and main channel await main_notification_channel.send(embed=embed) - # update room's livestream URL + # send session notification message to room if room_channel is not None: - await room_channel.edit( - topic=f"Livestream: [YouTube]({livestream_url})" if livestream_url else "" - ) await room_channel.send( content=f"# Starting in 5 minutes @ {session.rooms[0]}", embed=embed, diff --git a/src/europython_discord/programme_notifications/programme_connector.py b/src/europython_discord/programme_notifications/programme_connector.py index 3fc339c6..ad7d8191 100644 --- a/src/europython_discord/programme_notifications/programme_connector.py +++ b/src/europython_discord/programme_notifications/programme_connector.py @@ -97,7 +97,7 @@ async def _get_schedule_from_cache(self) -> dict[date, list[Session]] | None: _logger.exception("Schedule cache file not found and no schedule is already loaded.") return None - async def _get_now(self) -> datetime: + async def get_current_time(self) -> datetime: """Get the current time in the conference timezone.""" if self._simulated_start_time: elapsed = datetime.now(tz=UTC) - self._real_start_time @@ -126,7 +126,7 @@ async def get_sessions_by_date(self, date_now: date) -> list[Session]: async def get_upcoming_sessions(self) -> list[Session]: # upcoming sessions are those that start in 5 minutes or less # and the start time is after the current time - now = await self._get_now() + now = await self.get_current_time() if self._simulated_start_time: _logger.debug(f"Simulated time now: {now}") diff --git a/tests/program_notifications/test_program_connector.py b/tests/program_notifications/test_program_connector.py index a329915e..599cb2cb 100644 --- a/tests/program_notifications/test_program_connector.py +++ b/tests/program_notifications/test_program_connector.py @@ -186,11 +186,11 @@ async def test_get_now_with_simulation(programme_connector): # ensure time is ticking between start and finish of this test await asyncio.sleep(0.001) - assert await programme_connector._get_now() > simulated_start_time + assert await programme_connector.get_current_time() > simulated_start_time async def test_get_now_without_simulation(programme_connector): - now = await programme_connector._get_now() + now = await programme_connector.get_current_time() # ensure time is ticking between start and finish of this test await asyncio.sleep(0.001)