Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ schedule_cache.json
pretix_cache.json
*.egg-info/
livestreams.toml
youtube_urls.json
3 changes: 3 additions & 0 deletions sample_youtube_urls.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"NLGURP": "https://www.youtube.com/watch?v=SXMCMY"
}
54 changes: 43 additions & 11 deletions src/europython_discord/program_notifications/program_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def __init__(
) -> None:
self._api_url = api_url
self._cache_file = cache_file
self._youtube_config_file = cache_file.parent / "youtube_urls.json"

# time travel parameters for testing
self._simulated_start_time = simulated_start_time
Expand All @@ -50,6 +51,45 @@ async def parse_schedule(self, schedule: dict) -> dict[date, list[Session]]:

return sessions_by_day

async def _load_youtube_url_mappings(self) -> dict[str, str]:
"""Load YouTube URL mappings from configuration file."""
try:
async with aiofiles.open(self._youtube_config_file) as f:
content = await f.read()
return json.loads(content)
except FileNotFoundError:
_logger.info(f"YouTube URL configuration file not found at {self._youtube_config_file}")
return {}
except json.JSONDecodeError:
_logger.warning(
f"Invalid JSON in YouTube URL configuration file {self._youtube_config_file}"
)
return {}

async def _enhance_schedule_with_youtube_urls(self, schedule: dict) -> dict:
"""Add YouTube URLs to schedule events based on session codes.

Example of youtube_urls.json
{
"SXMCMY": "https://www.youtube.com/watch?v=123",
"9YAHXS": "https://www.youtube.com/watch?v=456",
}
"""
youtube_mappings = await self._load_youtube_url_mappings()

if not youtube_mappings:
_logger.info("No YouTube URL mappings available")
return schedule

for day_schedule in schedule.get("days", {}).values():
for event in day_schedule.get("events", []):
session_code = event.get("code")
if session_code and session_code in youtube_mappings:
event["youtube_url"] = youtube_mappings[session_code]

_logger.info("Added YouTube URLs to schedule.")
return schedule

async def fetch_schedule(self) -> None:
"""Fetch schedule data from the Program API and write it to a file as backup."""
async with self._fetch_lock:
Expand All @@ -74,24 +114,16 @@ async def fetch_schedule(self) -> None:

_logger.info("Schedule fetched successfully.")

# Enhance schedule with YouTube URLs
schedule = await self._enhance_schedule_with_youtube_urls(schedule)

# write schedule to file in case the API goes down
_logger.info(f"Writing schedule to {self._cache_file}...")
Path(self._cache_file).parent.mkdir(exist_ok=True, parents=True)
async with aiofiles.open(self._cache_file, "w") as f:
await f.write(json.dumps(schedule, indent=2))
_logger.info("Schedule written to cache file.")

# TODO PyLadiesCon: Here we need to modify the fetched schedule file
# and add the new field of each session 'youtube_url' from a local
# configuration file that needs to be provided once the videos are scheduled
# The file needs to have a map with 'Session Code' and the 'Youtube URL',
# for example:
# {
# 'XSRQD': 'https://youtube.com/adasdsdsad',
# }
# so later we can go to the schedule.json and find the 'code'
# field in each item inside 'events', and add it.

self.sessions_by_day = await self.parse_schedule(schedule)
_logger.info("Schedule parsed and loaded.")

Expand Down
44 changes: 44 additions & 0 deletions tests/program_notifications/test_program_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@ def cache_file(tmp_path):
return tmp_path / "cache.json"


@pytest.fixture
def mock_youtube_urls_file(tmp_path):
youtube_mappings = {
"TZWRJN": "https://www.youtube.com/watch?v=test-announcement",
"8FY9BC": "https://www.youtube.com/watch?v=test-keynote",
}
youtube_config_file = tmp_path / "youtube_urls.json"

with Path.open(youtube_config_file, "w") as f:
f.write(json.dumps(youtube_mappings))

return youtube_config_file


@pytest.fixture
async def program_connector(cache_file):
return ProgramConnector(api_url="http://test.api/schedule", cache_file=cache_file)
Expand Down Expand Up @@ -205,3 +219,33 @@ async def test_get_now_without_simulation(program_connector):
await asyncio.sleep(0.001)

assert datetime.now(tz=UTC) > now


@pytest.mark.asyncio
async def test_enhance_schedule_with_youtube_urls(
program_connector, mock_schedule, mock_youtube_urls_file
):
program_connector._youtube_config_file = mock_youtube_urls_file

# Enhance the schedule
enhanced_schedule = await program_connector._enhance_schedule_with_youtube_urls(mock_schedule)
sessions_by_day = await program_connector.parse_schedule(enhanced_schedule)

# Check that the YouTube URLs have been added correctly
july_10_sessions = sessions_by_day[date(2024, 7, 10)]
assert july_10_sessions[0].youtube_url == "https://www.youtube.com/watch?v=test-announcement"
assert july_10_sessions[1].youtube_url is None


@pytest.mark.asyncio
async def test_enhance_schedule_with_youtube_urls_no_config(program_connector, mock_schedule):
program_connector._youtube_config_file = Path("non_existent_file.json")

# Enhance the schedule
enhanced_schedule = await program_connector._enhance_schedule_with_youtube_urls(mock_schedule)
sessions_by_day = await program_connector.parse_schedule(enhanced_schedule)

# Check that no YouTube URLs have been added
for day_sessions in sessions_by_day.values():
for session in day_sessions:
assert session.youtube_url is None
4 changes: 2 additions & 2 deletions tests/program_notifications/test_session_to_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,15 @@ def test_embed_fields_room(session: Session) -> None:
"""Test the 'Room' field of the embed."""
session.rooms = ["Exhibit Hall"]
embed = session_to_embed.create_session_embed(session, None)
assert embed.fields[1].name == "Room"
assert embed.fields[1].name == "Stream"
assert embed.fields[1].value == "Exhibit Hall"


def test_embed_fields_room_multiple(session: Session) -> None:
"""Test the 'Room' field of the embed with multiple rooms."""
session.rooms = ["Exhibit Hall", "Forum Hall", "South Hall"]
embed = session_to_embed.create_session_embed(session, None)
assert embed.fields[1].name == "Room"
assert embed.fields[1].name == "Stream"
assert embed.fields[1].value == "Exhibit Hall, Forum Hall, South Hall"


Expand Down
Loading