Files
team-tryouts/app/services/notifications.py
T
cedrick2711 e53bc3b80d
CI - Security, Lint & Tests / validate (push) Failing after 25m59s
first cleanup iter
2026-09-09 08:26:47 -04:00

128 lines
4.4 KiB
Python

"""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 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 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)