style: formater le depot avec ruff format

QUA-002, premiere moitie. **Ce commit ne fait que reformater** : aucun
changement de comportement, aucune ligne de logique touchee. 72 fichiers,
4 restaient deja conformes. Il est isole exprès, pour que `git log -p` sur
les commits voisins reste lisible.

`quote-style = "preserve"` etait deja pose dans pyproject.toml, ce qui
evite le brassage guillemets simples / doubles : le diff porte sur les
retours a la ligne, l indentation des appels longs et les virgules
finales, pas sur le style de chaine.

Verification : 263 tests passent avant et apres, ruff check propre.

L activation en CI arrive dans le commit suivant, separement, pour que ce
diff-ci ne contienne rien d autre.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
GGThed
2026-08-08 15:53:10 -04:00
co-authored by Claude Opus 5
parent 2f40290f00
commit 7cec18c139
72 changed files with 2658 additions and 1449 deletions
+281 -181
View File
@@ -28,19 +28,21 @@ DISCORD_BOT_TOKEN = os.getenv('DISCORD_BOT_TOKEN')
logger = logging.getLogger(__name__)
# File for persisting pending requests across bot restarts
PENDING_FILE = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'discord_pending.json')
PENDING_FILE = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'discord_pending.json'
)
# Emoji constants
CHECK_EMOJI = '' # Green checkmark
CROSS_EMOJI = '' # Red X
CROSS_EMOJI = '' # Red X
class TeamTryoutsBot(commands.Bot):
"""Unified Discord bot for Team Tryouts notifications.
Handles One on One requests, schedule additions, and daily reminders.
"""
def __init__(self, flask_app=None):
# Only what the bot actually reads. `members` — the privileged
# GUILD_MEMBERS intent — was requested and never used: nothing here
@@ -62,7 +64,7 @@ class TeamTryoutsBot(commands.Bot):
self.message_queue = Queue() # Thread-safe queue for messages from Flask
self.scheduler = AsyncIOScheduler()
self.timezone = ZoneInfo('America/Toronto') # EDT timezone
def _load_pending(self):
"""Load pending requests from the JSON file."""
try:
@@ -71,12 +73,14 @@ class TeamTryoutsBot(commands.Bot):
data = json.load(f)
# Convert string keys back to int
self.pending_requests = {int(k): v for k, v in data.items()}
logger.info(f"Loaded {len(self.pending_requests)} pending requests from {PENDING_FILE}")
logger.info(
f"Loaded {len(self.pending_requests)} pending requests from {PENDING_FILE}"
)
else:
logger.info("No pending requests file found, starting fresh.")
except Exception as e:
logger.error(f"Error loading pending requests: {e}")
def _save_pending(self):
"""Save pending requests to the JSON file."""
try:
@@ -84,12 +88,12 @@ class TeamTryoutsBot(commands.Bot):
json.dump(self.pending_requests, f, indent=2)
except Exception as e:
logger.error(f"Error saving pending requests: {e}")
async def setup_hook(self):
"""Called when the bot is ready."""
self._load_pending()
logger.info(f'TeamTryoutsBot logged in as {self.user}')
async def on_ready(self):
"""Log when the bot is ready and start background tasks."""
try:
@@ -97,13 +101,13 @@ class TeamTryoutsBot(commands.Bot):
logger.info(f'TeamTryoutsBot is ready! Logged in as {self.user} | Guilds: {guilds}')
except Exception:
logger.info(f'TeamTryoutsBot is ready! Logged in as {self.user}')
# Start the queue processing task
self.loop.create_task(self.process_queue())
# Start the daily reminder scheduler
self.loop.create_task(self.start_scheduler())
async def start_scheduler(self):
"""Start the APScheduler for daily reminders."""
try:
@@ -111,13 +115,13 @@ class TeamTryoutsBot(commands.Bot):
self.send_daily_reminders,
trigger=CronTrigger(hour=18, minute=0, timezone=self.timezone),
id='daily_reminders',
replace_existing=True
replace_existing=True,
)
self.scheduler.start()
logger.info('Daily reminder scheduler started (18:00 EDT)')
except Exception as e:
logger.error(f'Error starting scheduler: {e}')
async def process_queue(self):
"""Process messages from the queue (runs continuously)."""
while True:
@@ -127,7 +131,7 @@ class TeamTryoutsBot(commands.Bot):
except Empty:
await asyncio.sleep(0.5)
continue
if item.get('type') == 'one_on_one_request':
await self._send_one_on_one_dm(**item['data'])
elif item.get('type') == 'schedule_addition':
@@ -138,91 +142,116 @@ class TeamTryoutsBot(commands.Bot):
await self._send_schedule_notification(**item['data'])
elif item.get('type') == 'one_on_one_response':
await self._send_one_on_one_response_dm(**item['data'])
except Exception as e:
logger.error(f"Error processing queue: {e}\n{traceback.format_exc()}")
await asyncio.sleep(0.1)
def is_dm_channel(self, channel) -> bool:
"""Check if a channel is a DM channel."""
return hasattr(channel, 'recipient') or hasattr(channel, 'recipients')
async def on_raw_reaction_add(self, payload):
"""Handle when a reaction is added to a message (works even after bot restart)."""
# Ignore bot's own reactions
if payload.user_id == self.user.id:
return
# Check if this is a pending request we're tracking
if payload.message_id not in self.pending_requests:
return
# Fetch the channel and check if it's a DM
try:
channel = await self.fetch_channel(payload.channel_id)
except Exception:
return
if not self.is_dm_channel(channel):
return
# Fetch the user who reacted
try:
user = await self.fetch_user(payload.user_id)
except Exception:
return
if user is None:
return
request_info = self.pending_requests[payload.message_id]
emoji_str = str(payload.emoji)
handler_type = request_info.get('type')
request_id = request_info.get('id')
if emoji_str == CHECK_EMOJI:
if handler_type == 'one_on_one':
if self.flask_app:
with self.flask_app.app_context():
await self.handle_one_on_one_approve(user, payload.message_id, request_id, channel)
await self.handle_one_on_one_approve(
user, payload.message_id, request_id, channel
)
else:
await self.handle_one_on_one_approve(user, payload.message_id, request_id, channel)
await self.handle_one_on_one_approve(
user, payload.message_id, request_id, channel
)
elif handler_type == 'schedule_addition':
if self.flask_app:
with self.flask_app.app_context():
await self.handle_attendance_confirm(user, payload.message_id, request_id, channel)
await self.handle_attendance_confirm(
user, payload.message_id, request_id, channel
)
else:
await self.handle_attendance_confirm(user, payload.message_id, request_id, channel)
await self.handle_attendance_confirm(
user, payload.message_id, request_id, channel
)
elif emoji_str == CROSS_EMOJI:
if handler_type == 'one_on_one':
if self.flask_app:
with self.flask_app.app_context():
await self.handle_one_on_one_reject(user, payload.message_id, request_id, channel)
await self.handle_one_on_one_reject(
user, payload.message_id, request_id, channel
)
else:
await self.handle_one_on_one_reject(user, payload.message_id, request_id, channel)
await self.handle_one_on_one_reject(
user, payload.message_id, request_id, channel
)
elif handler_type == 'schedule_addition':
if self.flask_app:
with self.flask_app.app_context():
await self.handle_attendance_decline(user, payload.message_id, request_id, channel)
await self.handle_attendance_decline(
user, payload.message_id, request_id, channel
)
else:
await self.handle_attendance_decline(user, payload.message_id, request_id, channel)
async def _send_one_on_one_dm(self, coach_name: str, coach_discord_id: str, player_name: str,
team_name: str, date_str: str, start_time: str, end_time: str,
points: str, request_id: int) -> int:
await self.handle_attendance_decline(
user, payload.message_id, request_id, channel
)
async def _send_one_on_one_dm(
self,
coach_name: str,
coach_discord_id: str,
player_name: str,
team_name: str,
date_str: str,
start_time: str,
end_time: str,
points: str,
request_id: int,
) -> int:
"""Send a One on One request DM to a coach with reactions."""
try:
user_id = int(coach_discord_id)
except (ValueError, TypeError):
logger.warning(f"Invalid coach_discord_id '{coach_discord_id}'")
return None
try:
user = await self.fetch_user(user_id)
if not user:
return None
message = (
"📅 **One on One Request**\n\n"
f"**Player:** {player_name}\n"
@@ -234,27 +263,33 @@ class TeamTryoutsBot(commands.Bot):
f"{CHECK_EMOJI} - Confirm the meeting\n"
f"{CROSS_EMOJI} - Decline (you can add a reason by replying before clicking)"
)
msg = await user.send(message)
await msg.add_reaction(CHECK_EMOJI)
await msg.add_reaction(CROSS_EMOJI)
# Track this pending request
self.pending_requests[msg.id] = {'type': 'one_on_one', 'id': request_id}
self._save_pending()
logger.info(f"Sent One on One DM with reactions, message_id={msg.id}")
return msg.id
except Exception as e:
logger.error(f"Error sending One on One DM: {e}")
return None
async def _send_schedule_notification(self, user_id: int, event_type: str,
event_title: str, event_date: str,
event_time: str, reference_id: int) -> int:
async def _send_schedule_notification(
self,
user_id: int,
event_type: str,
event_title: str,
event_date: str,
event_time: str,
reference_id: int,
) -> int:
"""Send a schedule addition notification to a player.
Args:
user_id: Database primary key of the User (NOT Discord user ID).
event_type: 'match' or 'tryout'.
@@ -266,23 +301,24 @@ class TeamTryoutsBot(commands.Bot):
try:
# Look up the DB user to get their Discord user ID
from app.models import User as DBUser
db_user = DBUser.query.get(user_id)
if not db_user:
logger.warning(f"DB user {user_id} not found for schedule notification")
return None
if not db_user.discord_user_id:
logger.warning(f"User {db_user.username} has no Discord user ID, cannot send DM")
return None
discord_uid = int(db_user.discord_user_id)
user = await self.fetch_user(discord_uid)
if not user:
logger.warning(f"Could not fetch Discord user {discord_uid}")
return None
event_name = "Match" if event_type == 'match' else "Tryout"
message = (
f"📅 **{event_name} Scheduled**\n\n"
f"You have been added to the following {event_type}:\n"
@@ -293,52 +329,58 @@ class TeamTryoutsBot(commands.Bot):
f"{CHECK_EMOJI} - Confirm attendance\n"
f"{CROSS_EMOJI} - Decline"
)
msg = await user.send(message)
await msg.add_reaction(CHECK_EMOJI)
await msg.add_reaction(CROSS_EMOJI)
# Track this pending request
self.pending_requests[msg.id] = {'type': 'schedule_addition', 'id': reference_id, 'event_type': event_type}
self.pending_requests[msg.id] = {
'type': 'schedule_addition',
'id': reference_id,
'event_type': event_type,
}
self._save_pending()
logger.info(f"Sent {event_type} schedule notification to {db_user.username}, message_id={msg.id}")
logger.info(
f"Sent {event_type} schedule notification to {db_user.username}, message_id={msg.id}"
)
return msg.id
except Exception as e:
logger.error(f"Error sending schedule notification: {e}")
return None
async def handle_one_on_one_approve(self, coach, message_id, request_id, channel):
"""Handle coach approving a One on One request."""
try:
from app.models import OneOnOneRequest
from app.extensions import db
request = OneOnOneRequest.query.get(request_id)
if not request:
return
if request.coach.discord_user_id != str(coach.id):
await channel.send("⚠️ You are not the intended recipient.")
return
# Re-attach to current session (object may be detached across app contexts)
request = db.session.merge(request)
# Capture data before commit
player_full_name = request.player.full_name if request.player else 'Unknown'
player_discord_id = request.player.discord_user_id if request.player else None
coach_obj = request.coach
request.status = 'approved'
request.responded_at = datetime.utcnow()
db.session.commit()
await channel.send(
f"✅ You have **approved** the One on One session with {player_full_name}."
)
# Notify player via Discord
if player_discord_id:
await self.notify_player_about_one_on_one_direct(
@@ -346,56 +388,62 @@ class TeamTryoutsBot(commands.Bot):
player_full_name=player_full_name,
coach_full_name=coach_obj.full_name if coach_obj else 'Coach',
request=request,
approved=True
approved=True,
)
del self.pending_requests[message_id]
self._save_pending()
except Exception as e:
logger.error(f"Error handling approval: {e}\n{traceback.format_exc()}")
async def handle_one_on_one_reject(self, coach, message_id, request_id, channel):
"""Handle coach rejecting a One on One request."""
try:
from app.models import OneOnOneRequest
from app.extensions import db
request = OneOnOneRequest.query.get(request_id)
if not request:
return
if request.coach.discord_user_id != str(coach.id):
await channel.send("⚠️ You are not the intended recipient.")
return
# Re-attach to current session (object may be detached across app contexts)
request = db.session.merge(request)
player_full_name = request.player.full_name if request.player else 'Unknown'
player_discord_id = request.player.discord_user_id if request.player else None
coach_obj = request.coach
refusal_note = None
try:
async for reply in channel.history(limit=20):
if reply.author.id == coach.id and reply.reference and reply.reference.message_id == message_id:
if (
reply.author.id == coach.id
and reply.reference
and reply.reference.message_id == message_id
):
refusal_note = reply.content
break
except Exception as e:
logger.warning(f"Could not check for reply message: {e}")
request.status = 'rejected'
request.responded_at = datetime.utcnow()
if refusal_note:
request.coach_rejection_message = refusal_note
db.session.commit()
rejection_msg = f"❌ You have **rejected** the One on One session with {player_full_name}."
rejection_msg = (
f"❌ You have **rejected** the One on One session with {player_full_name}."
)
if refusal_note:
rejection_msg += f"\n**Reason:** {refusal_note}"
else:
rejection_msg += "\n\n️ The player has been notified that you are not available."
await channel.send(rejection_msg)
if player_discord_id:
await self.notify_player_about_one_on_one_direct(
@@ -404,23 +452,23 @@ class TeamTryoutsBot(commands.Bot):
coach_full_name=coach_obj.full_name if coach_obj else 'Coach',
request=request,
approved=False,
refusal_note=refusal_note
refusal_note=refusal_note,
)
del self.pending_requests[message_id]
self._save_pending()
except Exception as e:
logger.error(f"Error handling rejection: {e}\n{traceback.format_exc()}")
async def handle_attendance_confirm(self, player, message_id, reference_id, channel):
"""Handle player confirming attendance for a match/tryout."""
try:
from app.models import MatchParticipant, TryoutRegistration
from app.extensions import db
request_info = self.pending_requests[message_id]
event_type = request_info.get('event_type')
if event_type == 'match':
participant = MatchParticipant.query.get(reference_id)
if participant:
@@ -431,25 +479,25 @@ class TeamTryoutsBot(commands.Bot):
if registration:
registration = db.session.merge(registration)
registration.attendance_confirmed = True
db.session.commit()
await channel.send("✅ Your attendance has been confirmed!")
del self.pending_requests[message_id]
self._save_pending()
except Exception as e:
logger.error(f"Error handling attendance confirmation: {e}\n{traceback.format_exc()}")
async def handle_attendance_decline(self, player, message_id, reference_id, channel):
"""Handle player declining attendance for a match/tryout."""
try:
from app.models import MatchParticipant, TryoutRegistration
from app.extensions import db
request_info = self.pending_requests[message_id]
event_type = request_info.get('event_type')
if event_type == 'match':
participant = MatchParticipant.query.get(reference_id)
if participant:
@@ -460,24 +508,30 @@ class TeamTryoutsBot(commands.Bot):
if registration:
registration = db.session.merge(registration)
registration.status = 'no_show'
db.session.commit()
await channel.send("❌ Your attendance has been declined.")
del self.pending_requests[message_id]
self._save_pending()
except Exception as e:
logger.error(f"Error handling attendance decline: {e}\n{traceback.format_exc()}")
async def notify_player_about_one_on_one_direct(self, player_discord_id, player_full_name,
coach_full_name, request,
approved=True, refusal_note=None):
async def notify_player_about_one_on_one_direct(
self,
player_discord_id,
player_full_name,
coach_full_name,
request,
approved=True,
refusal_note=None,
):
"""Send confirmation to player about One on One response using pre-fetched data.
This method avoids session expiration issues by using data captured before
the database commit.
Args:
player_discord_id: The player's Discord user ID string.
player_full_name: The player's full name.
@@ -490,12 +544,12 @@ class TeamTryoutsBot(commands.Bot):
if not player_discord_id:
logger.warning(f"Player has no Discord user ID for request {request.id}")
return
player_user = await self.fetch_user(int(player_discord_id))
if not player_user:
logger.warning(f"Could not fetch Discord user {player_discord_id}")
return
if approved:
message = (
"🎉 **One on One Session Confirmed!**\n\n"
@@ -519,13 +573,15 @@ class TeamTryoutsBot(commands.Bot):
f"Your coach **{coach_full_name}** is not available.\n\n"
"Please try selecting a different time slot."
)
await player_user.send(message)
logger.info(f"Sent One on One notification to player {player_full_name} (request {request.id})")
logger.info(
f"Sent One on One notification to player {player_full_name} (request {request.id})"
)
except Exception as e:
logger.error(f"Error in direct One on One notification: {e}")
async def send_daily_reminders(self):
"""Send daily reminders at 18:00 EDT for events in 24-48 hours."""
try:
@@ -540,12 +596,18 @@ class TeamTryoutsBot(commands.Bot):
async def _send_daily_reminders_impl(self):
"""Internal implementation of daily reminders with proper app context."""
try:
from app.models import Match, Tryout, MatchParticipant, TryoutRegistration, OneOnOneRequest
from app.models import (
Match,
Tryout,
MatchParticipant,
TryoutRegistration,
OneOnOneRequest,
)
from sqlalchemy.orm import joinedload
now = datetime.now(self.timezone)
tomorrow = now.date() + timedelta(days=1)
# Find matches for tomorrow
matches = Match.query.filter(Match.date == tomorrow).all()
for match in matches:
@@ -553,7 +615,7 @@ class TeamTryoutsBot(commands.Bot):
for participant in participants:
if participant.player.discord_user_id:
await self.send_match_reminder(participant.player, match)
# Find tryouts for tomorrow
tryouts = Tryout.query.filter(Tryout.date == tomorrow).all()
for tryout in tryouts:
@@ -561,22 +623,22 @@ class TeamTryoutsBot(commands.Bot):
for reg in registrations:
if reg.player.discord_user_id:
await self.send_tryout_reminder(reg.player, tryout)
# Find One on One sessions for tomorrow (only approved ones)
one_on_ones = OneOnOneRequest.query.options(
joinedload(OneOnOneRequest.player),
joinedload(OneOnOneRequest.coach)
).filter(
OneOnOneRequest.date == tomorrow,
OneOnOneRequest.status == 'approved'
).all()
one_on_ones = (
OneOnOneRequest.query.options(
joinedload(OneOnOneRequest.player), joinedload(OneOnOneRequest.coach)
)
.filter(OneOnOneRequest.date == tomorrow, OneOnOneRequest.status == 'approved')
.all()
)
for session in one_on_ones:
if session.player and session.player.discord_user_id:
await self.send_one_on_one_reminder(session.player, session)
except Exception as e:
logger.error(f"Error sending daily reminders: {e}")
async def send_match_reminder(self, player, match):
"""Send match reminder to player."""
try:
@@ -593,7 +655,7 @@ class TeamTryoutsBot(commands.Bot):
await player_user.send(message)
except Exception as e:
logger.error(f"Error sending match reminder: {e}")
async def send_tryout_reminder(self, player, tryout):
"""Send tryout reminder to player."""
try:
@@ -608,25 +670,33 @@ class TeamTryoutsBot(commands.Bot):
await player_user.send(message)
except Exception as e:
logger.error(f"Error sending tryout reminder: {e}")
async def _send_one_on_one_response_dm(self, player_discord_id: str, player_full_name: str,
coach_full_name: str, date_str: str, start_time: str,
end_time: str, points: str, approved: bool,
refusal_note: str = None) -> bool:
async def _send_one_on_one_response_dm(
self,
player_discord_id: str,
player_full_name: str,
coach_full_name: str,
date_str: str,
start_time: str,
end_time: str,
points: str,
approved: bool,
refusal_note: str = None,
) -> bool:
"""Send a DM to a player notifying them of their One on One request response.
Called from the message queue when a coach accepts/rejects via the web app.
"""
try:
if not player_discord_id:
logger.warning("Cannot send response DM: no player_discord_id")
return False
player_user = await self.fetch_user(int(player_discord_id))
if not player_user:
logger.warning(f"Could not fetch Discord user {player_discord_id}")
return False
if approved:
message = (
"🎉 **One on One Session Confirmed!**\n\n"
@@ -650,11 +720,13 @@ class TeamTryoutsBot(commands.Bot):
f"Your coach **{coach_full_name}** is not available.\n\n"
"Please try selecting a different time slot."
)
await player_user.send(message)
logger.info(f"Sent One on One response DM to player {player_full_name} (approved={approved})")
logger.info(
f"Sent One on One response DM to player {player_full_name} (approved={approved})"
)
return True
except Exception as e:
logger.error(f"Error sending One on One response DM: {e}")
return False
@@ -691,78 +763,105 @@ def get_bot(flask_app=None):
return bot_instance
def send_one_on_one_dm(coach_name: str, coach_discord_id: str, player_name: str,
team_name: str, date_str: str, start_time: str, end_time: str,
points: str, request_id: int) -> bool:
def send_one_on_one_dm(
coach_name: str,
coach_discord_id: str,
player_name: str,
team_name: str,
date_str: str,
start_time: str,
end_time: str,
points: str,
request_id: int,
) -> bool:
"""Queue a One on One request DM to be sent by the bot."""
bot = get_bot()
try:
bot.message_queue.put({
'type': 'one_on_one_request',
'data': {
'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,
'end_time': end_time,
'points': points,
'request_id': request_id
bot.message_queue.put(
{
'type': 'one_on_one_request',
'data': {
'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,
'end_time': end_time,
'points': points,
'request_id': request_id,
},
}
})
)
return True
except Exception as e:
logger.error(f"Error queuing One on One DM: {e}")
return False
def send_schedule_notification(user_id: int, event_type: str, event_title: str,
event_date: str, event_time: str, reference_id: int) -> bool:
def send_schedule_notification(
user_id: int,
event_type: str,
event_title: str,
event_date: str,
event_time: str,
reference_id: int,
) -> bool:
"""Queue a schedule addition notification to be sent by the bot."""
bot = get_bot()
try:
bot.message_queue.put({
'type': 'schedule_addition',
'data': {
'user_id': user_id,
'event_type': event_type,
'event_title': event_title,
'event_date': event_date,
'event_time': event_time,
'reference_id': reference_id
bot.message_queue.put(
{
'type': 'schedule_addition',
'data': {
'user_id': user_id,
'event_type': event_type,
'event_title': event_title,
'event_date': event_date,
'event_time': event_time,
'reference_id': reference_id,
},
}
})
)
return True
except Exception as e:
logger.error(f"Error queuing schedule notification: {e}")
return False
def send_one_on_one_response(player_discord_id: str, player_full_name: str,
coach_full_name: str, date_str: str, start_time: str,
end_time: str, points: str, approved: bool,
refusal_note: str = None) -> bool:
def send_one_on_one_response(
player_discord_id: str,
player_full_name: str,
coach_full_name: str,
date_str: str,
start_time: str,
end_time: str,
points: str,
approved: bool,
refusal_note: str = None,
) -> bool:
"""Queue a One on One response DM to be sent to the player by the bot.
Called from Flask routes when a coach accepts/rejects via the web app.
"""
bot = get_bot()
try:
bot.message_queue.put({
'type': 'one_on_one_response',
'data': {
'player_discord_id': player_discord_id,
'player_full_name': player_full_name,
'coach_full_name': coach_full_name,
'date_str': date_str,
'start_time': start_time,
'end_time': end_time,
'points': points,
'approved': approved,
'refusal_note': refusal_note,
bot.message_queue.put(
{
'type': 'one_on_one_response',
'data': {
'player_discord_id': player_discord_id,
'player_full_name': player_full_name,
'coach_full_name': coach_full_name,
'date_str': date_str,
'start_time': start_time,
'end_time': end_time,
'points': points,
'approved': approved,
'refusal_note': refusal_note,
},
}
})
)
return True
except Exception as e:
logger.error(f"Error queuing One on One response DM: {e}")
@@ -772,17 +871,18 @@ def send_one_on_one_response(player_discord_id: str, player_full_name: str,
def start_bot(flask_app=None):
"""Start the Discord bot in the background."""
global bot_thread
bot = get_bot(flask_app=flask_app)
if DISCORD_BOT_TOKEN and bot_thread is None:
def run_bot():
try:
bot.run(DISCORD_BOT_TOKEN)
except Exception as e:
logger.error(f"Bot error: {e}")
bot_thread = threading.Thread(target=run_bot, daemon=True)
bot_thread.start()
logger.info("TeamTryoutsBot started in background thread")
elif not DISCORD_BOT_TOKEN:
logger.warning("DISCORD_BOT_TOKEN not set, bot not started")
logger.warning("DISCORD_BOT_TOKEN not set, bot not started")