From 4ed42d5ec8057dbd772e9b3912e682cca057dea5 Mon Sep 17 00:00:00 2001 From: cedrick2711 Date: Fri, 17 Jul 2026 15:32:32 -0400 Subject: [PATCH] =?UTF-8?q?Ajout=20de=20booking=20pour=20des=20One=20on=20?= =?UTF-8?q?One=20avec=20son=20coach,=20Demande=20par=20discord=20au=20coac?= =?UTF-8?q?h=20pour=20confirmer=20la=20rencontre=20Ajout=20de=20message=20?= =?UTF-8?q?automatis=C3=A9=20discord=20pour=20avertir=20des=20nouveaux=20m?= =?UTF-8?q?atch=20et=20rappel=2024h=20a=20l'avance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/agents/test.agent.md | 10 - .gitignore | 5 +- README.md | 54 ++- __pycache__/app.cpython-313.pyc | Bin 4160 -> 4522 bytes __pycache__/models.cpython-313.pyc | Bin 42213 -> 42677 bytes __pycache__/seed.cpython-313.pyc | Bin 27427 -> 27683 bytes app.py | 8 + discord_bot.py | 548 +++++++++++++++++++++++ instance/team_tryouts.db | Bin 163840 -> 167936 bytes models.py | 9 +- requirements.txt | Bin 1086 -> 1148 bytes routes/__pycache__/users.cpython-313.pyc | Bin 71241 -> 72897 bytes routes/users.py | 98 ++-- seed.py | 16 +- templates/pages/edit_profile.html | 7 + templates/pages/edit_user.html | 9 +- 16 files changed, 714 insertions(+), 50 deletions(-) delete mode 100644 .agents/agents/test.agent.md create mode 100644 discord_bot.py diff --git a/.agents/agents/test.agent.md b/.agents/agents/test.agent.md deleted file mode 100644 index 0832e0c..0000000 --- a/.agents/agents/test.agent.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: test -description: Describe what this custom agent does and when to use it. -argument-hint: The inputs this agent expects, e.g., "a task to implement" or "a question to answer". -# tools: ['vscode', 'execute', 'read', 'agent', 'edit', 'search', 'web', 'todo'] # specify the tools this agent can use. If not set, all enabled tools are allowed. ---- - - - -Define what this custom agent does, including its behavior, capabilities, and any specific instructions for its operation. \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7028253..edbc9a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -*.env +.env +.env.* instance/ documents/ __pycache__/ -*.pyc \ No newline at end of file +*.pyc diff --git a/README.md b/README.md index e143bd6..c1e771d 100644 --- a/README.md +++ b/README.md @@ -1 +1,53 @@ -### Plateforme centralisée de tryouts \ No newline at end of file +### Plateforme centralisée de tryouts + +## Discord Integration for One on One Requests + +The application supports sending Discord direct messages to coaches when players request One on One sessions. + +### Setup Instructions + +#### 1. Create a Discord Bot + +1. Go to the [Discord Developer Portal](https://discord.com/developers/applications) +2. Create a new application +3. Go to the "Bot" tab and create a bot user +4. Copy the bot token - this will be your `DISCORD_BOT_TOKEN` +5. Enable the "Message Content Intent" under Privileged Gateway Intents (required for sending messages) + +#### 2. Configure Environment Variables + +Add the following to your `.env` file (create one if it doesn't exist): + +``` +DISCORD_BOT_TOKEN=your_bot_token_here +DISCORD_WEBHOOK_URL=optional_webhook_url_for_backup +``` + +- `DISCORD_BOT_TOKEN`: Required for sending direct messages to coaches +- `DISCORD_WEBHOOK_URL`: Optional fallback for webhook-based notifications + +#### 3. Add Coaches to the Bot + +For the bot to send DMs to coaches: +1. Each coach must have the bot added to their Discord server OR be friends with the bot +2. Coaches need to add their Discord User ID to their profile: + - Enable Developer Mode in Discord (User Settings → Advanced → Developer Mode) + - Right-click on their profile → Copy ID + - Enter this numeric ID in the "Discord User ID" field in their profile settings + +### How It Works + +When a player submits a One on One request: +1. The system checks if the coach has a Discord User ID configured +2. If configured, a direct message is sent to the coach via the Discord bot +3. If the bot fails or no Discord User ID is set, the system falls back to the webhook URL (if configured) +4. The message includes player name, team, requested date/time, and discussion points + +### Message Format + +The Discord DM includes: +- Player name +- Team name +- Requested date and time slot +- Discussion points (if provided) +- Link to the application for approval/rejection \ No newline at end of file diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc index cfa6192675352cdb747f9daa32c77de794db4969..0d681713e4c6c954541afa8f09ef64a87726602e 100644 GIT binary patch delta 597 zcmX@0uu7TlGcPX}0}yPhipn~}vyo4QnbCQ&F>?=72J7a7%%7M=#+; z6c|Ezlo(7ICa-4S%s72=Jcp8>UzshlJVP3zB15zX$Q%%0XGk<-V94VIY83L;&EpH@ z3yOuvAd}HdFrFzRgcTG&Igv$JUp$nTkHMG;CaS<7&k!t>$Lq)&%Af*er7@;4KfkSzle{Jz-o$5Gf0a|}_ZK||9?{PXOh8q)ImB*o@L%VUyvQLrN8>Vw@^udF ziyYb)^t?|vo-n?kix(i#}k%%^l%f$Yf| w{8o%=lau)axIrE%QUwt%llSq?6|bid^Dz!vcPpmj ztlB{KaVtk5Pkz>uJWN2&Nj^ppo1YoP7UJ?$WS=}yz=~0I@)>~uZjenyDj>psvbvx= bmp72f2*kyKlLG`bSnV14Ke$Y;6Vw3!i={g6 diff --git a/__pycache__/models.cpython-313.pyc b/__pycache__/models.cpython-313.pyc index ecf4b092f7d7f32f72cb691ab14ca538a920d625..8b8238c899f50d87564aa6d4fc2debb6a7dc40bd 100644 GIT binary patch delta 4841 zcmb7H3s6+o8RpzwcG=~nJeH@pf?yUDpH(p`h=7k;FfM4Kr0cqSS+2tFa`tWv8YSvf zQ;iLJ+G>o6Al64S(+u32P}-SJwM}D9GN?_)Tw^R#?PyaQOk2kp)3N9O?;4OS)G*Aq z=lf7hEFL-((lmQa zNNRm3jZa5eMv;z=dnk*}w2!f8X)9TI%$%6?2s+Vjqmw-OwgTk`FCD_Oq}0H4BY=;CO`ugU4zkr_7n5Hq9b=D^*cU*uB{a)cj+;r| zV|&LfCxz^@ac<&c3$t^%W+{6kS;qRZEu-SyQo!Y-ZbvX6QitSbce4YYr$EUnfLegH z04{(!014nZfM$Sg9MoioBOp6v$>ng$GL_Z^WijAz&>gT~B0xSs?n9W#2Z8%aZ_X@| zVo5z9?U&kw6Q-l4li3}y-TDibc`sHmLvDZeG1%}k4mQ22FZW9>`n7}}hq>b{HLo!5 z1Pq7Fba0s(X_rN3BR$E-o2Ik&yhNoXuR}-fDmnS@l7y{X%oB_0w}FZl<$gh%u4FGP zH*fYgIORGYZQQ~G4H2avw-N2fOk4Pq8dbfb(owTwnVlM8g%*8wW%6{MxcDg<ypKKqBTS_iNjU=IM~l$q`=XANDIIgq3FK|oSe(jT+*v$!Za1u~0FVLD ziA69f=Ai1Uffk^M=N9viA<-Pn9yvqcpc*Mla)U@;g>kf747jM|mnEN zNFU6v;nM?_sV12=`+_o0v1-!74cUG)L24`&J)9XL72Uk3Ddx2gtU{+Fy*^&zVUxt| zGitHlz^+wvkqXv3ElqiI(I52&2)?vkd4Ad7gv{%FTurBd#+w`}=u8-%26&ros!BKf z9fqH?LshvkJuuX=lMC6g4NE_aytWhe++lIc-$_P`qg=kmM$sz=&=oa>U9C)6GQ??Q zrD_iP-A-9^`+d;z0YJDcfj`Ko?aG{|R6>d|ONI6;#BhaANu5f3&0dmugO9)IY7uNc z&AL~P8Ha3L%%4>~Hmm`70_adO9(L(za^NtjDJzW$=p60kEc`{JwyMMKpwoQBJp8%Lev~ zXxI@h8(0A(^M@Fs)Pc1bb`xCdK(JAwV0g5Su0>vPyqe60rGvI&}PCX@HN7dTwBeU$5JIk;D zb}mw4#nB`RLI8(3%d4v-W)`el5DMZ1Nc+6U;5w%LQ&0(1r&j?!1GvVLJl2B0z_5>l zYDP01ai}-zBX`R%-dGIjCS_S-{QwJ*m;>f$Pv)SRR zb8HmF4KxF+$pjb!@F=geBPaqUM&{LcSOP#2S(o=B8IL}_;roIZ61h~Gq_C#5BtxqK z@CNAp{8gTy zC*?|X^G5r>48%Bcy$MSb%E*J=!jytYKg>+Cl9pW$(ARM_1 zVFI9B*|p>6gkP#Pa>om7^{yP!rfl66C*%!FaII%NKA-XKGy&fEt#}Ogsj%jkf+~PE zf%nJX^ZxkV-RDUuIx%HWkwAKs^?Mn~MAO7x3GKR$%ma?^D^r(4qxQs*WS^^@RmWOd(^W6n8nh;i^>2QF>^^Ui+A!j?Z{R(^Hp7-7{> zscgsLMArJ6ku|N&WLsWK*Jafj*yv?R%zrpu$G@aQm(Qd=H$EUsIL(>O|G|YtQWmpFKz{WYNF38I(%OKFLIS=b0BmZJI%HoNy?oH(H!X6 z19U*rLRN9FK7+Nl z7n?wNr7fGj#BQ}8XgLevIRnrIfC0zS!%sK7USm1p)efr@4`V#mup47f!#;pDf%Zej z28ukicKtN(WdKK5@?f*or} QaxN()+Qfe%V2o=22g>!=umAu6 delta 4499 zcmb7Hdr*|u73Y4yvb-P4@(jo-=n^U_CP-p+5kad4)W!H7T-Z-o1QzcuqeQ`1QjOM( zdfS+ZW3mR5nItnE;!T^Hf3&vsl{Sfv>5QM+cA7C`(l*$KO=dFf^#0Db^2%b3ivxi9Ot8mkv8au+FMg1IX#$CcjSBL?#A zGXIfHT1GwQwE;R5I5b`hIUr8tFJw}Fk^cru+y>3|(5N_G@Ho35t`#h0-xGy)59<*7 z?S<-ye)|^NVl~M~@p%1BKJIDg^6R|A-L8w;iT1Wg>O*I5|+%7s>8PQGA}I^(eJU5T((9Eqdjql3=9sN0=VBt+UM?*y!W! zJt{^B28Z6k%%3>ktG*0t{qov|70Z{ncnoHkffI|Tm8szGn3luNh-1@cv1D;=+BXTi z)r?w)jqg#vVwp3$ED}qe2kZwR9sDqWj5z=ahlF;8bWBW{Q8MuY#2^4$@)AHXp#MJH z6I*6nWc|`vX4fn}rS%remdCzkObU1Tq$_?9-S|t`O1M@k#u&Kzb2y6mSfH$~Iz}xb6=4xDlhm-mP2NW|!YT=o6gZZSh;%X$TlHt~<2|~+iF-Q(US9|A6@AW92YFA9b8J4Q z_gBO>&RKau%o&xPq-4&b_)2_ESj#X{2p9A_)ZKss* z!-&6Y@gi!KWY@{fE34jP78vclC|_7p#w?-SU07TG@Lc3aaL(rdDlBWK&+ny58bwE| z+v71)LVY71D}sPgDS<&8a1nN&Py*~0ee3$z0y2BU`jb)Yri^aKDQD{gPB3`KJCAOU7QnDs;e16zXh{087A;Ffr{ zdBUTAfQTwK;>gT~KzFCk!*WG@)SOvOu?AVFjjFZ>hs>WDd^}>30SIEos6@nJ0B}>5 zv|MK_PNW{rlKHJOHI^pp+y295Be0>er$GM=yO=RnkBUxThquw&rpRzV#%3-2pDTzO z0OF|nvb(#%x3OW1u5aR5uqGRj0~kpz^FI17j(;$@>{ts(({Ke9`95TJax1BGfLT)2 zfVV_O@?WtDW-VYxat&UE8rVbBvSBroqna{#5{s}m_`M$8Y{M~OT(Cm-VC!KY2b942 zQxRfE;C+@#_Pe?!N3r>GTlXiLx=ogCpB|r#$#ms@EDvs96S-uQa-qf%XTDUZVC&%& zc~%5zGJwjNmUZHDvjRty`O3Tm1=VdplgI5>-3l14Z0P*sk+{&TIG3B^n z4ei1Cbi`pMAc%FNNM3>gzvALQ?k9FiLLwu`CD(aWPGa#rR%`Ie@Ej(MtiQ{ytV6S8I$E1+CYP9H|x? zX4-_)VH0Op$4SSrlT6&*Y7=$G6GU2fc5>pngi_{8uF!biI?M7|!kv&_lCmx_q|0}u z@(Impj&VkEU58%RpVPCf7Rbe6Kj&cXY=u zvFBMpKY-#u{Hn$$y)21y^q?UM((OY{oZ1c*FL9j`Pxg$o(Xh^L_u(Zohp$p+tN#)H z+Y0YSGA2GcxxuzlV;kANibIV@TMoDUD}p}nZk5%quVv{4Y#uWQ?nOYRKQ;dcY%D=x diff --git a/__pycache__/seed.cpython-313.pyc b/__pycache__/seed.cpython-313.pyc index 7a933dec8ff3e0bc37b093f06c89d24c31a395f9..32c087579fd90d4666de767cf3c372da51601381 100644 GIT binary patch delta 4551 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 diff --git a/app.py b/app.py index 9b035d9..19e7c74 100644 --- a/app.py +++ b/app.py @@ -88,6 +88,14 @@ def create_app(): from seed import seed_database seed_database() + # Start the Discord bot for notifications + try: + from discord_bot import start_bot + start_bot() + except Exception as e: + import logging + logging.getLogger(__name__).warning(f"Could not start Discord bot: {e}") + return app diff --git a/discord_bot.py b/discord_bot.py new file mode 100644 index 0000000..765463f --- /dev/null +++ b/discord_bot.py @@ -0,0 +1,548 @@ +"""Unified Discord bot for Team Tryouts notifications. + +This module provides a persistent bot that handles: +- One on One request approvals/rejections via reactions +- Match/tryout schedule addition notifications with attendance confirmation +- Daily reminders at 18:00 EDT for upcoming events +""" + +import os +import logging +import asyncio +import threading +from datetime import datetime, timedelta +from zoneinfo import ZoneInfo +from queue import Queue, Empty +from discord import Forbidden, HTTPException, NotFound, Intents +from discord.ext import commands +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from apscheduler.triggers.cron import CronTrigger +from dotenv import load_dotenv + +load_dotenv() +DISCORD_BOT_TOKEN = os.getenv('DISCORD_BOT_TOKEN') + +# Configure logging +logger = logging.getLogger(__name__) + +# Emoji constants +CHECK_EMOJI = '✅' # Green checkmark +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): + intents = Intents.default() + intents.message_content = True + intents.dm_messages = True + intents.dm_reactions = True + intents.reactions = True + intents.guilds = True + + super().__init__(command_prefix='!', intents=intents) + self.pending_requests = {} # Maps message_id to {type, id} for reaction handling + self.message_queue = Queue() # Thread-safe queue for messages from Flask + self.scheduler = AsyncIOScheduler() + self.timezone = ZoneInfo('America/Toronto') # EDT timezone + + async def setup_hook(self): + """Called when the bot is ready.""" + logger.info(f'TeamTryoutsBot logged in as {self.user}') + + async def on_ready(self): + """Log when the bot is ready and start background tasks.""" + 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: + self.scheduler.add_job( + self.send_daily_reminders, + trigger=CronTrigger(hour=18, minute=0, timezone=self.timezone), + id='daily_reminders', + 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: + try: + try: + item = self.message_queue.get_nowait() + 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': + await self._send_schedule_notification(**item['data']) + + except Exception as e: + logger.error(f"Error processing queue: {e}") + 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_reaction_add(self, reaction, user): + """Handle when a reaction is added to a message.""" + if user.bot: + return + + if not self.is_dm_channel(reaction.message.channel): + return + + message_id = reaction.message.id + + if message_id not in self.pending_requests: + return + + request_info = self.pending_requests[message_id] + emoji_str = str(reaction.emoji) + + handler_type = request_info.get('type') + request_id = request_info.get('id') + + if emoji_str == CHECK_EMOJI: + if handler_type == 'one_on_one': + await self.handle_one_on_one_approve(user, message_id, request_id, reaction.message) + elif handler_type == 'schedule_addition': + await self.handle_attendance_confirm(user, message_id, request_id, reaction.message) + elif emoji_str == CROSS_EMOJI: + if handler_type == 'one_on_one': + await self.handle_one_on_one_reject(user, message_id, request_id, reaction.message) + elif handler_type == 'schedule_addition': + await self.handle_attendance_decline(user, message_id, request_id, reaction.message) + + 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" + f"**Team:** {team_name or 'Unknown Team'}\n" + f"**Date:** {date_str}\n" + f"**Time:** {start_time} - {end_time}\n" + f"**Discussion Points:** {points or 'No specific points provided'}\n\n" + "Please respond by clicking a reaction below:\n" + 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} + + 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: + """Send a schedule addition notification to a player.""" + try: + user = await self.fetch_user(user_id) + if not user: + 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" + f"**{event_title}**\n" + f"**Date:** {event_date}\n" + f"**Time:** {event_time}\n\n" + "Please confirm your attendance:\n" + 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} + + logger.info(f"Sent {event_type} schedule notification, 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, original_message): + """Handle coach approving a One on One request.""" + try: + from models import OneOnOneRequest, db + from sqlalchemy.orm import joinedload + + request = OneOnOneRequest.query.options( + joinedload(OneOnOneRequest.player), + joinedload(OneOnOneRequest.coach) + ).get(request_id) + if not request: + return + + if request.coach.discord_user_id != str(coach.id): + await original_message.channel.send("⚠️ You are not the intended recipient.") + return + + request.status = 'approved' + request.responded_at = datetime.utcnow() + db.session.commit() + + await original_message.channel.send( + f"✅ You have **approved** the One on One session with {request.player.full_name}." + ) + + await self.notify_player_about_one_on_one(request, approved=True) + del self.pending_requests[message_id] + + except Exception as e: + logger.error(f"Error handling approval: {e}") + + async def handle_one_on_one_reject(self, coach, message_id, request_id, original_message): + """Handle coach rejecting a One on One request.""" + try: + from models import OneOnOneRequest, db + from sqlalchemy.orm import joinedload + + request = OneOnOneRequest.query.options( + joinedload(OneOnOneRequest.player), + joinedload(OneOnOneRequest.coach) + ).get(request_id) + if not request: + return + + if request.coach.discord_user_id != str(coach.id): + await original_message.channel.send("⚠️ You are not the intended recipient.") + return + + refusal_note = None + try: + async for reply in original_message.channel.history(limit=20): + 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 {request.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 original_message.channel.send(rejection_msg) + await self.notify_player_about_one_on_one(request, approved=False, refusal_note=refusal_note) + del self.pending_requests[message_id] + + except Exception as e: + logger.error(f"Error handling rejection: {e}") + + async def handle_attendance_confirm(self, player, message_id, reference_id, original_message): + """Handle player confirming attendance for a match/tryout.""" + try: + from models import MatchParticipant, TryoutRegistration, Match, Tryout, 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: + participant.attendance_confirmed = True + elif event_type == 'tryout': + registration = TryoutRegistration.query.get(reference_id) + if registration: + registration.attendance_confirmed = True + + db.session.commit() + + await original_message.channel.send("✅ Your attendance has been confirmed!") + del self.pending_requests[message_id] + + except Exception as e: + logger.error(f"Error handling attendance confirmation: {e}") + + async def handle_attendance_decline(self, player, message_id, reference_id, original_message): + """Handle player declining attendance for a match/tryout.""" + try: + from models import MatchParticipant, TryoutRegistration, Match, Tryout, 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: + db.session.delete(participant) + elif event_type == 'tryout': + registration = TryoutRegistration.query.get(reference_id) + if registration: + registration.status = 'no_show' + + db.session.commit() + + await original_message.channel.send("❌ Your attendance has been declined.") + del self.pending_requests[message_id] + + except Exception as e: + logger.error(f"Error handling attendance decline: {e}") + + async def notify_player_about_one_on_one(self, request, approved=True, refusal_note=None): + """Send confirmation to player about One on One response.""" + try: + # Ensure player and coach relationships are loaded + player = request.player + coach = request.coach + + if not player or not player.discord_user_id: + logger.warning(f"Player has no Discord user ID for request {request.id}") + return + + player_user = await self.fetch_user(int(player.discord_user_id)) + if not player_user: + logger.warning(f"Could not fetch Discord user for player {player.id}") + return + + if approved: + message = ( + "🎉 **One on One Session Confirmed!**\n\n" + f"Your coach **{request.coach.full_name}** has approved your request:\n" + f"**Date:** {request.date.strftime('%A, %B %d, %Y')}\n" + f"**Time:** {request.start_time.strftime('%I:%M %p')} - {request.end_time.strftime('%I:%M %p')}\n" + f"**Discussion Points:** {request.points or 'No specific points provided'}\n\n" + "Please prepare for your session!" + ) + else: + if refusal_note: + message = ( + "😞 **One on One Session Rejected**\n\n" + f"Your coach **{request.coach.full_name}** has declined:\n" + f"**Reason:** {refusal_note}\n\n" + "Please try selecting a different time slot." + ) + else: + message = ( + "😞 **One on One Session Unavailable**\n\n" + f"Your coach **{request.coach.full_name}** is not available.\n\n" + "Please try selecting a different time slot." + ) + + await player_user.send(message) + + except Exception as e: + logger.error(f"Error notifying player: {e}") + + async def send_daily_reminders(self): + """Send daily reminders at 18:00 EDT for events in 24-48 hours.""" + try: + from models import Match, Tryout, MatchParticipant, TryoutRegistration, OneOnOneRequest, db + 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: + participants = MatchParticipant.query.filter_by(match_id=match.id).all() + 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: + registrations = TryoutRegistration.query.filter_by(tryout_id=tryout.id).all() + 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() + 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: + player_user = await self.fetch_user(int(player.discord_user_id)) + message = ( + "🔔 **Match Reminder**\n\n" + f"Your match **{match.title}** is scheduled for tomorrow:\n" + f"**Date:** {match.date.strftime('%A, %B %d, %Y')}\n" + f"**Time:** {match.start_time.strftime('%I:%M %p') if match.start_time else 'TBD'} - " + f"{match.end_time.strftime('%I:%M %p') if match.end_time else 'TBD'}\n" + f"**Location:** {match.location or 'TBD'}\n\n" + "Please confirm your attendance in the app." + ) + 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: + player_user = await self.fetch_user(int(player.discord_user_id)) + message = ( + "🔔 **Tryout Reminder**\n\n" + f"Your tryout **{tryout.title}** is scheduled for tomorrow:\n" + f"**Date:** {tryout.date.strftime('%A, %B %d, %Y')}\n" + f"**Location:** {tryout.location or 'TBD'}\n\n" + "Please confirm your attendance in the app." + ) + await player_user.send(message) + except Exception as e: + logger.error(f"Error sending tryout reminder: {e}") + + async def send_one_on_one_reminder(self, player, session): + """Send One on One reminder to player.""" + try: + player_user = await self.fetch_user(int(player.discord_user_id)) + message = ( + "🔔 **One on One Reminder**\n\n" + f"Your One on One session with **{session.coach.full_name}** is scheduled for tomorrow:\n" + f"**Date:** {session.date.strftime('%A, %B %d, %Y')}\n" + f"**Time:** {session.start_time.strftime('%I:%M %p')} - {session.end_time.strftime('%I:%M %p')}\n" + f"**Discussion Points:** {session.points or 'No specific points provided'}\n\n" + "Please prepare for your session!" + ) + await player_user.send(message) + except Exception as e: + logger.error(f"Error sending One on One reminder: {e}") + + +# Global bot instance +bot_instance = None +bot_thread = None + + +def get_bot(): + """Get or create the bot instance.""" + global bot_instance + if bot_instance is None: + bot_instance = TeamTryoutsBot() + 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: + """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 + } + }) + 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: + """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 + } + }) + return True + except Exception as e: + logger.error(f"Error queuing schedule notification: {e}") + return False + + +def start_bot(): + """Start the Discord bot in the background.""" + global bot_thread + + bot = get_bot() + 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") \ No newline at end of file diff --git a/instance/team_tryouts.db b/instance/team_tryouts.db index 9ab6cdd8443f931eeecfb0b3ef8869b56875a893..ca98f70337f24199ec9d5a979cadb8dbf40882a9 100644 GIT binary patch literal 167936 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

yuc+OPNiBw!3O(PVgiN>SJ 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 Hzw7n_>Fo#e literal 163840 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

DXta#|X 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 PJZVW8BM6MenqKD*voVIx diff --git a/models.py b/models.py index f458d0c..82d761a 100644 --- a/models.py +++ b/models.py @@ -85,7 +85,8 @@ class User(UserMixin, db.Model): # E-Sports specific fields games = db.Column(db.Text, nullable=True) # Comma-separated list of games - discord_username = db.Column(db.String(128), nullable=True) # Discord handle + discord_username = db.Column(db.String(128), nullable=True) # Discord handle (e.g., Username#1234) + discord_user_id = db.Column(db.String(64), nullable=True) # Discord User ID for DMs (numeric, e.g., 123456789012345678) league_os_profile = db.Column(db.String(256), nullable=True) # League OS profile URL or ID evaluations_given = db.relationship('Evaluation', foreign_keys='Evaluation.evaluator_id', backref='evaluator', lazy='dynamic') @@ -773,6 +774,8 @@ class OneOnOneRequest(db.Model): status: Request status (pending, approved, rejected, scheduled). created_at: Timestamp of creation. responded_at: Timestamp when coach responded. + discord_message_id: Discord message ID for reaction handling. + coach_rejection_message: Optional justification from coach when rejecting. """ __tablename__ = 'one_on_one_requests' id = db.Column(db.Integer, primary_key=True) @@ -786,7 +789,9 @@ class OneOnOneRequest(db.Model): status = db.Column(db.String(20), default='pending') # pending, approved, rejected, scheduled created_at = db.Column(db.DateTime, default=datetime.utcnow) responded_at = db.Column(db.DateTime, nullable=True) + discord_message_id = db.Column(db.BigInteger, nullable=True) # Discord message ID for reaction handling + coach_rejection_message = db.Column(db.Text, nullable=True) # Optional justification when rejecting player = db.relationship('User', foreign_keys=[player_id], backref='one_on_one_requests') coach = db.relationship('User', foreign_keys=[coach_id]) - team = db.relationship('OrgTeam', foreign_keys=[org_team_id]) + team = db.relationship('OrgTeam', foreign_keys=[org_team_id]) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 5c75244fe189a88b336c1d5c871d53a4ca0297c7..179f7485b89e3ac7c14e40890fe0f89d1b2f3d81 100644 GIT binary patch delta 69 zcmdnT@rPqVi((2xCPOhpGDAK?5km@t9zy{`C4(&x8Ufj+Ky0+Jem}E>Qer`Iaz<)O UX-;a9ovpE+fu4bqf&woW0Bk!E4FCWD delta 10 Rcmeyvv5#Xy%f?l^m;oGr1or>{ diff --git a/routes/__pycache__/users.cpython-313.pyc b/routes/__pycache__/users.cpython-313.pyc index cf78966e820deeaf13f314c04557ddade7753248..e0b6b5c509653470279abfc2d20c2038c5fdaf95 100644 GIT binary patch delta 8461 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 bklb-ir6V$Y9$6&owZswA$-5KDH{|~S?SL0@ delta 7250 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 Discord Username +

+ + + Enable Developer Mode in Discord → Right-click profile → Copy ID +
+ +
diff --git a/templates/pages/edit_user.html b/templates/pages/edit_user.html index 8c35fad..9a3e0d1 100644 --- a/templates/pages/edit_user.html +++ b/templates/pages/edit_user.html @@ -87,11 +87,16 @@
-
+
-
+
+ + + Enable Developer Mode in Discord → Right-click profile → Copy ID +
+