Skip to content

Commit fe5e47c

Browse files
feat: post all livestream URLs upfront instead of per-session (#376)
* feat: post all livestream URLs upfront instead of per-session * Refactor: Move `post_all_livestream_urls` to the correct position This method is analogous to `notify_sessions`, so it should be located next to it, instead of between `on_ready` and `cog_load`. * Refactor: Rename `post_all_livestream_urls` to `notify_livestreams` This method is analogous to `notify_sessions`, so it should be named similarly. * Run `notify_livestreams` in a loop instead of once on startup Like the schedule, the livestream URLs can change while the program is running. That's why we fetch them regularly. So the task `notify_livestreams` should run regularly, like `notify_sessions`. * Don't trigger a fetch if no livestreams are found The livestreams are fetched regularly (every 5 minutes). Let's just wait for the regular fetch instead of triggering a second one. Also, it might be that there really are no livestreams to fetch. * Only obtain livestreams_by_room once This change is quite pedantic, but I want to get this information from the livestream_connector exactly once. Otherwise, it could (theoretically) change between the first and second access. * Restore previous livestream format Before adding this feature, the format was `Livestream: [YouTube](<URL>)`, using Markdown to display a lable instead of the plain URL. Let's keep it like that and only add the day. * Always use English weekdays `date.strftime('%A')` returns the "Weekday as locale’s full name" (quote from https://docs.python.org/3/library/datetime.html#format-codes). We always want English output, even if the bot host is configured in another locale. * ProgrammeConnector: Make current time publicly available This is necessary for the next step: Only sending livestream URLs a day in advance. (A cleaner approach would be to extract the 'current time' logic into a separate service, but this approach doesn't cause much chaos, so it's okay.) * Only post livestream URLs a day in advance Also add the headline 'Channel for room {room_name}'. --------- Co-authored-by: Niklas Mertsch <niklasmertsch@web.de>
1 parent deab252 commit fe5e47c

3 files changed

Lines changed: 54 additions & 13 deletions

File tree

‎src/europython_discord/programme_notifications/cog.py‎

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
import logging
4-
from datetime import datetime
4+
from datetime import datetime, timedelta
55

66
from discord import Client, TextChannel
77
from discord.ext import commands, tasks
@@ -15,6 +15,16 @@
1515

1616
_logger = logging.getLogger(__name__)
1717

18+
_WEEKDAYS = {
19+
0: "Monday",
20+
1: "Tuesday",
21+
2: "Wednesday",
22+
3: "Thursday",
23+
4: "Friday",
24+
5: "Saturday",
25+
6: "Sunday",
26+
}
27+
1828

1929
class ProgrammeNotificationsCog(commands.Cog):
2030
def __init__(self, bot: Client, config: ProgrammeNotificationsConfig) -> None:
@@ -40,21 +50,25 @@ async def on_ready(self) -> None:
4050
await self.purge_all_room_channels()
4151
_logger.debug(f"Simulated start time: {self.config.simulated_start_time}")
4252
_logger.debug(f"Fast mode: {self.config.fast_mode}")
53+
54+
_logger.info("Starting the livestream notifier...")
55+
self.notify_livestreams.start()
4356
_logger.info("Starting the session notifier...")
4457
self.notify_sessions.start()
4558
_logger.info("Cog 'Programme Notifications' is ready")
4659

4760
async def cog_load(self) -> None:
4861
"""Start schedule updater task."""
49-
_logger.info(
50-
"Starting the schedule updater and setting the interval for the session notifier..."
51-
)
62+
_logger.info("Starting the schedule updater and setting the interval for the notifiers...")
5263
self.fetch_schedule.start()
5364
self.fetch_livestreams.start()
65+
self.notify_livestreams.change_interval(
66+
seconds=2 if self.config.fast_mode and self.config.simulated_start_time else 60
67+
)
5468
self.notify_sessions.change_interval(
5569
seconds=2 if self.config.fast_mode and self.config.simulated_start_time else 60
5670
)
57-
_logger.info("Schedule updater started and interval set for the session notifier")
71+
_logger.info("Schedule updater started and interval set for the notifiers")
5872

5973
async def cog_unload(self) -> None:
6074
"""Stop all tasks."""
@@ -74,6 +88,36 @@ async def fetch_livestreams(self) -> None:
7488
await self.livestream_connector.fetch_livestreams()
7589
_logger.info("Finished the periodic livestream update.")
7690

91+
@tasks.loop()
92+
async def notify_livestreams(self) -> None:
93+
livestreams_by_room = self.livestream_connector.livestreams_by_room
94+
if livestreams_by_room is None:
95+
return
96+
97+
for room_name in self.config.rooms_to_channel_names:
98+
room_channel = self._get_room_channel(room_name)
99+
if room_channel is None:
100+
continue
101+
102+
_urls_by_date = livestreams_by_room.get(room_name)
103+
if not _urls_by_date:
104+
continue
105+
urls_by_date = sorted(_urls_by_date.items()) # sort by date
106+
107+
# don't notify of stream if the first stream is multiple days away
108+
today = (await self.programme_connector.get_current_time()).date()
109+
first_streaming_day = urls_by_date[0][0]
110+
if first_streaming_day - today > timedelta(days=1):
111+
topic = f"Channel for room {room_name}"
112+
else:
113+
livestreams_text = "\n".join(
114+
f"Livestream {_WEEKDAYS[day.timetuple().tm_wday]}: [YouTube]({url})"
115+
for day, url in urls_by_date
116+
)
117+
topic = f"Channel for room {room_name}\n\n{livestreams_text}"
118+
119+
await room_channel.edit(topic=topic)
120+
77121
@tasks.loop()
78122
async def notify_sessions(self) -> None:
79123
# determine sessions to send notifications for
@@ -105,11 +149,8 @@ async def notify_sessions(self) -> None:
105149
# send session notification message to room and main channel
106150
await main_notification_channel.send(embed=embed)
107151

108-
# update room's livestream URL
152+
# send session notification message to room
109153
if room_channel is not None:
110-
await room_channel.edit(
111-
topic=f"Livestream: [YouTube]({livestream_url})" if livestream_url else ""
112-
)
113154
await room_channel.send(
114155
content=f"# Starting in 5 minutes @ {session.rooms[0]}",
115156
embed=embed,

‎src/europython_discord/programme_notifications/programme_connector.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ async def _get_schedule_from_cache(self) -> dict[date, list[Session]] | None:
9797
_logger.exception("Schedule cache file not found and no schedule is already loaded.")
9898
return None
9999

100-
async def _get_now(self) -> datetime:
100+
async def get_current_time(self) -> datetime:
101101
"""Get the current time in the conference timezone."""
102102
if self._simulated_start_time:
103103
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]:
126126
async def get_upcoming_sessions(self) -> list[Session]:
127127
# upcoming sessions are those that start in 5 minutes or less
128128
# and the start time is after the current time
129-
now = await self._get_now()
129+
now = await self.get_current_time()
130130

131131
if self._simulated_start_time:
132132
_logger.debug(f"Simulated time now: {now}")

‎tests/program_notifications/test_program_connector.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,11 @@ async def test_get_now_with_simulation(programme_connector):
186186
# ensure time is ticking between start and finish of this test
187187
await asyncio.sleep(0.001)
188188

189-
assert await programme_connector._get_now() > simulated_start_time
189+
assert await programme_connector.get_current_time() > simulated_start_time
190190

191191

192192
async def test_get_now_without_simulation(programme_connector):
193-
now = await programme_connector._get_now()
193+
now = await programme_connector.get_current_time()
194194

195195
# ensure time is ticking between start and finish of this test
196196
await asyncio.sleep(0.001)

0 commit comments

Comments
 (0)