"""Outbound notifications. Extracted from app/routes/users.py, where it sat between two route definitions and pulled `requests`, `logging` and the Discord bot into a module whose subject is HTTP handlers (ARCH-003). Failures here are swallowed and logged on purpose: a notification that does not reach Discord must not roll back the session it was announcing. That is a property of the caller — a Flask request whose work is already committed — not of the failure, which is why the breadth is argued for at each of the two boundaries below rather than assumed (ARCH-008 / QUA-004). The webhook branch is narrower than it was: `requests.RequestException` covers every way an HTTP call can fail, and anything else coming out of it is a defect worth seeing. """ import logging import os import requests logger = logging.getLogger(__name__) #: Either a webhook URL, or a bare Discord user id to DM instead. DISCORD_WEBHOOK_URL = os.environ.get('DISCORD_WEBHOOK_URL', '') def send_discord_notification( player_name, points, date_str, start_time_str, end_time_str, team_name, coach_name, coach_discord, coach_discord_id, request_id=None, ): """Send a Discord notification for a One on One request. Args: player_name: Who is asking. points: Free-text discussion points, possibly empty. date_str, start_time_str, end_time_str: Already formatted for display. team_name: The player's team, or None. coach_name: Who is being asked. coach_discord: The coach's Discord handle, for the webhook footer. coach_discord_id: The coach's Discord snowflake, for a direct message. request_id: OneOnOneRequest primary key, so reactions can find it back. """ if coach_discord_id: try: from app.discord_bot import send_one_on_one_dm send_one_on_one_dm( coach_name=coach_name, coach_discord_id=coach_discord_id, player_name=player_name, team_name=team_name, date_str=date_str, start_time=start_time_str, end_time=end_time_str, points=points, request_id=request_id, ) except Exception: # noqa: BLE001 — the request that booked the meeting is already committed logger.warning('Failed to hand the One on One DM to the bot', exc_info=True) if not DISCORD_WEBHOOK_URL: return # A bare snowflake here means "DM this person instead", and only when the # coach has no id of their own. Queueing it cannot raise (see _enqueue). if DISCORD_WEBHOOK_URL.isdigit(): if not coach_discord_id: from app.discord_bot import send_one_on_one_dm send_one_on_one_dm( coach_name=coach_name, coach_discord_id=DISCORD_WEBHOOK_URL, player_name=player_name, team_name=team_name, date_str=date_str, start_time=start_time_str, end_time=end_time_str, points=points, ) return embed = { "embeds": [ { "title": "One on One Request", "color": 3447003, "fields": [ {"name": "Player", "value": player_name, "inline": True}, { "name": "Team", "value": team_name or "Unknown Team", "inline": True, }, {"name": "Date", "value": date_str, "inline": True}, { "name": "Time", "value": f"{start_time_str} - {end_time_str}", "inline": True, }, { "name": "Discussion Points", "value": points or "No specific points provided", "inline": False, }, ], "footer": { "text": f"Coach: {coach_name}" + (f" (Discord: {coach_discord})" if coach_discord else ""), }, } ], } try: requests.post(DISCORD_WEBHOOK_URL, json=embed, timeout=5) except requests.RequestException as exc: logger.warning('Failed to post the One on One webhook: %s', exc)