Ajout de booking pour des One on One avec son coach,
Demande par discord au coach pour confirmer la rencontre Ajout de message automatisé discord pour avertir des nouveaux match et rappel 24h a l'avance
This commit is contained in:
@@ -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.
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- Tip: Use /create-agent in chat to generate content with agent assistance -->
|
|
||||||
|
|
||||||
Define what this custom agent does, including its behavior, capabilities, and any specific instructions for its operation.
|
|
||||||
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
*.env
|
.env
|
||||||
|
.env.*
|
||||||
instance/
|
instance/
|
||||||
documents/
|
documents/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|||||||
@@ -1 +1,53 @@
|
|||||||
### Plateforme centralisée de tryouts
|
### 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
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -88,6 +88,14 @@ def create_app():
|
|||||||
from seed import seed_database
|
from seed import seed_database
|
||||||
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
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+548
@@ -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")
|
||||||
Binary file not shown.
@@ -85,7 +85,8 @@ class User(UserMixin, db.Model):
|
|||||||
|
|
||||||
# E-Sports specific fields
|
# E-Sports specific fields
|
||||||
games = db.Column(db.Text, nullable=True) # Comma-separated list of games
|
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
|
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')
|
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).
|
status: Request status (pending, approved, rejected, scheduled).
|
||||||
created_at: Timestamp of creation.
|
created_at: Timestamp of creation.
|
||||||
responded_at: Timestamp when coach responded.
|
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'
|
__tablename__ = 'one_on_one_requests'
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
@@ -786,6 +789,8 @@ class OneOnOneRequest(db.Model):
|
|||||||
status = db.Column(db.String(20), default='pending') # pending, approved, rejected, scheduled
|
status = db.Column(db.String(20), default='pending') # pending, approved, rejected, scheduled
|
||||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
responded_at = db.Column(db.DateTime, nullable=True)
|
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')
|
player = db.relationship('User', foreign_keys=[player_id], backref='one_on_one_requests')
|
||||||
coach = db.relationship('User', foreign_keys=[coach_id])
|
coach = db.relationship('User', foreign_keys=[coach_id])
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
+69
-25
@@ -102,6 +102,7 @@ def edit_user(user_id):
|
|||||||
|
|
||||||
selected_games = request.form.getlist('games')
|
selected_games = request.form.getlist('games')
|
||||||
discord_username = request.form.get('discord_username', '').strip()
|
discord_username = request.form.get('discord_username', '').strip()
|
||||||
|
discord_user_id = request.form.get('discord_user_id', '').strip()
|
||||||
league_os_profile = request.form.get('league_os_profile', '').strip()
|
league_os_profile = request.form.get('league_os_profile', '').strip()
|
||||||
|
|
||||||
user.full_name = full_name
|
user.full_name = full_name
|
||||||
@@ -111,6 +112,7 @@ def edit_user(user_id):
|
|||||||
user.is_active_account = is_active
|
user.is_active_account = is_active
|
||||||
user.games = ','.join(selected_games) if selected_games else None
|
user.games = ','.join(selected_games) if selected_games else None
|
||||||
user.discord_username = discord_username or None
|
user.discord_username = discord_username or None
|
||||||
|
user.discord_user_id = discord_user_id or None
|
||||||
user.league_os_profile = league_os_profile or None
|
user.league_os_profile = league_os_profile or None
|
||||||
|
|
||||||
# Update gamertags using shared function
|
# Update gamertags using shared function
|
||||||
@@ -241,6 +243,7 @@ def edit_profile():
|
|||||||
|
|
||||||
selected_games = request.form.getlist('games')
|
selected_games = request.form.getlist('games')
|
||||||
discord_username = request.form.get('discord_username', '').strip()
|
discord_username = request.form.get('discord_username', '').strip()
|
||||||
|
discord_user_id = request.form.get('discord_user_id', '').strip()
|
||||||
league_os_profile = request.form.get('league_os_profile', '').strip()
|
league_os_profile = request.form.get('league_os_profile', '').strip()
|
||||||
|
|
||||||
if email != current_user.email and User.query.filter_by(email=email).first():
|
if email != current_user.email and User.query.filter_by(email=email).first():
|
||||||
@@ -252,6 +255,7 @@ def edit_profile():
|
|||||||
current_user.phone = phone
|
current_user.phone = phone
|
||||||
current_user.games = ','.join(selected_games) if selected_games else None
|
current_user.games = ','.join(selected_games) if selected_games else None
|
||||||
current_user.discord_username = discord_username or None
|
current_user.discord_username = discord_username or None
|
||||||
|
current_user.discord_user_id = discord_user_id or None
|
||||||
current_user.league_os_profile = league_os_profile or None
|
current_user.league_os_profile = league_os_profile or None
|
||||||
|
|
||||||
# Update gamertags using shared function
|
# Update gamertags using shared function
|
||||||
@@ -726,8 +730,11 @@ def download_signed_contract(contract_id):
|
|||||||
DISCORD_WEBHOOK_URL = os.environ.get('DISCORD_WEBHOOK_URL', '')
|
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):
|
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 webhook notification for a One on One request.
|
"""Send a Discord notification for a One on One request.
|
||||||
|
|
||||||
|
Sends a DM to the coach via Discord bot if their Discord User ID is configured,
|
||||||
|
otherwise falls back to webhook notification.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
player_name: Name of the player making the request.
|
player_name: Name of the player making the request.
|
||||||
@@ -738,32 +745,67 @@ def send_discord_notification(player_name, points, date_str, start_time_str, end
|
|||||||
team_name: Name of the player's team.
|
team_name: Name of the player's team.
|
||||||
coach_name: Name of the coach.
|
coach_name: Name of the coach.
|
||||||
coach_discord: Coach's Discord username.
|
coach_discord: Coach's Discord username.
|
||||||
|
coach_discord_id: Coach's Discord User ID (for DMs).
|
||||||
"""
|
"""
|
||||||
if not DISCORD_WEBHOOK_URL:
|
import logging
|
||||||
return # No webhook configured, skip notification
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
embed = {
|
# Try to send DM via Discord bot first (requires coach's Discord User ID)
|
||||||
"embeds": [{
|
if coach_discord_id:
|
||||||
"title": "One on One Request",
|
try:
|
||||||
"color": 3447003, # Blue color
|
from discord_bot import send_one_on_one_dm
|
||||||
"fields": [
|
send_one_on_one_dm(
|
||||||
{"name": "Player", "value": player_name, "inline": True},
|
coach_name=coach_name,
|
||||||
{"name": "Team", "value": team_name or "Unknown Team", "inline": True},
|
coach_discord_id=coach_discord_id,
|
||||||
{"name": "Date", "value": date_str, "inline": True},
|
player_name=player_name,
|
||||||
{"name": "Time", "value": f"{start_time_str} - {end_time_str}", "inline": True},
|
team_name=team_name,
|
||||||
{"name": "Discussion Points", "value": points or "No specific points provided", "inline": False}
|
date_str=date_str,
|
||||||
],
|
start_time=start_time_str,
|
||||||
"footer": {
|
end_time=end_time_str,
|
||||||
"text": f"Coach: {coach_name}" + (f" (Discord: {coach_discord})" if coach_discord else "")
|
points=points,
|
||||||
}
|
request_id=request_id
|
||||||
}]
|
)
|
||||||
}
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to send Discord DM: {e}")
|
||||||
|
|
||||||
try:
|
# Also send to webhook as backup/fallback
|
||||||
requests.post(DISCORD_WEBHOOK_URL, json=embed, timeout=5)
|
if DISCORD_WEBHOOK_URL:
|
||||||
except Exception:
|
try:
|
||||||
pass # Silently fail if webhook doesn't work
|
from discord_bot import send_one_on_one_dm
|
||||||
|
|
||||||
|
# Try to send DM to webhook URL if it's a user ID
|
||||||
|
if DISCORD_WEBHOOK_URL.isdigit() and not coach_discord_id:
|
||||||
|
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
|
||||||
|
)
|
||||||
|
elif not DISCORD_WEBHOOK_URL.isdigit():
|
||||||
|
# Legacy webhook URL - send traditional webhook embed
|
||||||
|
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 "")
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
requests.post(DISCORD_WEBHOOK_URL, json=embed, timeout=5)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to send Discord notification: {e}")
|
||||||
|
|
||||||
@users_bp.route('/one-on-one', methods=['GET', 'POST'])
|
@users_bp.route('/one-on-one', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@@ -858,7 +900,9 @@ def one_on_one():
|
|||||||
end_time_str=end_time_str,
|
end_time_str=end_time_str,
|
||||||
team_name=org_team.name if org_team else None,
|
team_name=org_team.name if org_team else None,
|
||||||
coach_name=coach.full_name,
|
coach_name=coach.full_name,
|
||||||
coach_discord=coach.discord_username
|
coach_discord=coach.discord_username,
|
||||||
|
coach_discord_id=coach.discord_user_id,
|
||||||
|
request_id=request_obj.id
|
||||||
)
|
)
|
||||||
|
|
||||||
flash('One on One request sent to your coach!', 'success')
|
flash('One on One request sent to your coach!', 'success')
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ def seed_database():
|
|||||||
{'username': 'president', 'password': 'password', 'role': 'president', 'full_name': 'Sarah Johnson', 'email': '[email protected]', 'phone': '555-0101'},
|
{'username': 'president', 'password': 'password', 'role': 'president', 'full_name': 'Sarah Johnson', 'email': '[email protected]', 'phone': '555-0101'},
|
||||||
{'username': 'manager1', 'password': 'password', 'role': 'manager', 'full_name': 'Mike Williams', 'email': '[email protected]', 'phone': '555-0102'},
|
{'username': 'manager1', 'password': 'password', 'role': 'manager', 'full_name': 'Mike Williams', 'email': '[email protected]', 'phone': '555-0102'},
|
||||||
{'username': 'manager2', 'password': 'password', 'role': 'manager', 'full_name': 'Emily Davis', 'email': '[email protected]', 'phone': '555-0103'},
|
{'username': 'manager2', 'password': 'password', 'role': 'manager', 'full_name': 'Emily Davis', 'email': '[email protected]', 'phone': '555-0103'},
|
||||||
{'username': 'coach1', 'password': 'password', 'role': 'coach', 'full_name': 'Coach Thompson', 'email': '[email protected]', 'phone': '555-0104'},
|
{'username': 'coach1', 'password': 'password', 'role': 'coach', 'full_name': 'Coach Thompson', 'email': '[email protected]', 'phone': '555-0104', 'discord_user_id': '484107446298738689'},
|
||||||
{'username': 'coach2', 'password': 'password', 'role': 'coach', 'full_name': 'Coach Martinez', 'email': '[email protected]', 'phone': '555-0105'},
|
{'username': 'coach2', 'password': 'password', 'role': 'coach', 'full_name': 'Coach Martinez', 'email': '[email protected]', 'phone': '555-0105'},
|
||||||
{'username': 'coach3', 'password': 'password', 'role': 'coach', 'full_name': 'Coach Anderson', 'email': '[email protected]', 'phone': '555-0106'},
|
{'username': 'coach3', 'password': 'password', 'role': 'coach', 'full_name': 'Coach Anderson', 'email': '[email protected]', 'phone': '555-0106'},
|
||||||
{'username': 'scout1', 'password': 'password', 'role': 'scout', 'full_name': 'Alex Rivera', 'email': '[email protected]', 'phone': '555-0107'},
|
{'username': 'scout1', 'password': 'password', 'role': 'scout', 'full_name': 'Alex Rivera', 'email': '[email protected]', 'phone': '555-0107'},
|
||||||
@@ -53,9 +53,9 @@ def seed_database():
|
|||||||
|
|
||||||
# Create players with E-Sports profile data (gamertags per game)
|
# Create players with E-Sports profile data (gamertags per game)
|
||||||
player_data = [
|
player_data = [
|
||||||
{'username': 'jplayer1', 'full_name': 'nordjan', 'email': '[email protected]', 'games': 'Valorant,Counter-Strike 2, Rainbow Six Siege, Rocket League, Overwatch 2',
|
{'username': 'jplayer1', 'full_name': 'nordjan', 'email': '[email protected]', 'games': 'Valorant, Counter-Strike 2, Rainbow Six Siege, Rocket League, Overwatch 2',
|
||||||
'gamertags': {'Valorant': 'nordjan#bad', 'Counter-Strike 2': 'nordjan', 'Rainbow Six Siege': 'n0rd-vpn', 'Rocket League': 'nordjiano'},
|
'gamertags': {'Valorant': 'nordjan#bad', 'Counter-Strike 2': 'nordjan', 'Rainbow Six Siege': 'n0rd-vpn', 'Rocket League': 'nordjiano'},
|
||||||
'discord': 'nordjan', 'league_os': 'https://leagueos.gg/player/nordjan'},
|
'discord': 'nordjan', 'discord_user_id': '484107446298738689', 'league_os': 'https://leagueos.gg/player/nordjan'},
|
||||||
{'username': 'jplayer2', 'full_name': 'Emma Garcia', 'email': '[email protected]', 'games': 'League of Legends,Valorant',
|
{'username': 'jplayer2', 'full_name': 'Emma Garcia', 'email': '[email protected]', 'games': 'League of Legends,Valorant',
|
||||||
'gamertags': {'League of Legends': 'emmagarcia_lol', 'Valorant': 'emmagarcia_val'},
|
'gamertags': {'League of Legends': 'emmagarcia_lol', 'Valorant': 'emmagarcia_val'},
|
||||||
'discord': 'EmmaG#4452', 'league_os': 'https://leagueos.gg/player/emmagarcia'},
|
'discord': 'EmmaG#4452', 'league_os': 'https://leagueos.gg/player/emmagarcia'},
|
||||||
@@ -90,7 +90,7 @@ def seed_database():
|
|||||||
'username': data['username'], 'password': 'password', 'role': 'player',
|
'username': data['username'], 'password': 'password', 'role': 'player',
|
||||||
'full_name': data['full_name'], 'email': data['email'], 'phone': f'555-01{i:02d}',
|
'full_name': data['full_name'], 'email': data['email'], 'phone': f'555-01{i:02d}',
|
||||||
'games': data['games'], 'gamertags': data.get('gamertags', {}),
|
'games': data['games'], 'gamertags': data.get('gamertags', {}),
|
||||||
'discord_username': data['discord'], 'league_os_profile': data['league_os']
|
'discord_username': data['discord'], 'discord_user_id': data['discord_user_id'], 'league_os_profile': data['league_os']
|
||||||
})
|
})
|
||||||
|
|
||||||
users = []
|
users = []
|
||||||
@@ -105,6 +105,7 @@ def seed_database():
|
|||||||
phone=data.get('phone', ''),
|
phone=data.get('phone', ''),
|
||||||
games=data.get('games'),
|
games=data.get('games'),
|
||||||
discord_username=data.get('discord_username'),
|
discord_username=data.get('discord_username'),
|
||||||
|
discord_user_id=data.get('discord_user_id'),
|
||||||
league_os_profile=data.get('league_os_profile')
|
league_os_profile=data.get('league_os_profile')
|
||||||
)
|
)
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
@@ -124,8 +125,11 @@ def seed_database():
|
|||||||
|
|
||||||
# Create gamertags for players
|
# Create gamertags for players
|
||||||
gamertag_data = [
|
gamertag_data = [
|
||||||
{'user': users[7], 'game': 'Valorant', 'gamertag': 'jameswilson_val'},
|
{'user': users[7], 'game': 'Valorant', 'gamertag': 'nordjan#bad'},
|
||||||
{'user': users[7], 'game': 'Counter-Strike 2', 'gamertag': 'jameswilson_cs'},
|
{'user': users[7], 'game': 'Counter-Strike 2', 'gamertag': 'nordjan'},
|
||||||
|
{'user': users[7], 'game': 'Rainbow Six Siege', 'gamertag': 'nordjan', 'platform': 'Ubisoft'},
|
||||||
|
{'user': users[7], 'game': 'Rocket League', 'gamertag': 'nordjiano', 'platform': 'Epic'},
|
||||||
|
{'user': users[7], 'game': 'Overwatch 2', 'gamertag': 'nordjan', 'platform': 'PC'},
|
||||||
{'user': users[8], 'game': 'League of Legends', 'gamertag': 'emmagarcia_lol'},
|
{'user': users[8], 'game': 'League of Legends', 'gamertag': 'emmagarcia_lol'},
|
||||||
{'user': users[8], 'game': 'Valorant', 'gamertag': 'emmagarcia_val'},
|
{'user': users[8], 'game': 'Valorant', 'gamertag': 'emmagarcia_val'},
|
||||||
{'user': users[9], 'game': 'Apex Legends', 'gamertag': 'liambrown_apex', 'platform': 'PC'},
|
{'user': users[9], 'game': 'Apex Legends', 'gamertag': 'liambrown_apex', 'platform': 'PC'},
|
||||||
|
|||||||
@@ -79,6 +79,13 @@
|
|||||||
<label for="discord_username"><i class="fab fa-discord"></i> Discord Username</label>
|
<label for="discord_username"><i class="fab fa-discord"></i> Discord Username</label>
|
||||||
<input type="text" id="discord_username" name="discord_username" value="{{ user.discord_username or '' }}" placeholder="e.g. Name#1234">
|
<input type="text" id="discord_username" name="discord_username" value="{{ user.discord_username or '' }}" placeholder="e.g. Name#1234">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group col-6">
|
||||||
|
<label for="discord_user_id"><i class="fab fa-discord"></i> Discord User ID <small>(for DMs)</small></label>
|
||||||
|
<input type="text" id="discord_user_id" name="discord_user_id" value="{{ user.discord_user_id or '' }}" placeholder="Numeric ID (e.g. 123456789012345678)">
|
||||||
|
<small class="text-muted">Enable Developer Mode in Discord → Right-click profile → Copy ID</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
<div class="form-group col-6">
|
<div class="form-group col-6">
|
||||||
<label for="league_os_profile"><i class="fas fa-link"></i> League OS Connection</label>
|
<label for="league_os_profile"><i class="fas fa-link"></i> League OS Connection</label>
|
||||||
<input type="text" id="league_os_profile" name="league_os_profile" value="{{ user.league_os_profile or '' }}" placeholder="League OS profile link or ID">
|
<input type="text" id="league_os_profile" name="league_os_profile" value="{{ user.league_os_profile or '' }}" placeholder="League OS profile link or ID">
|
||||||
|
|||||||
@@ -87,11 +87,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group col-6">
|
<div class="form-group col-4">
|
||||||
<label for="discord_username"><i class="fab fa-discord"></i> Discord Username</label>
|
<label for="discord_username"><i class="fab fa-discord"></i> Discord Username</label>
|
||||||
<input type="text" id="discord_username" name="discord_username" value="{{ user.discord_username or '' }}" placeholder="e.g. Name#1234">
|
<input type="text" id="discord_username" name="discord_username" value="{{ user.discord_username or '' }}" placeholder="e.g. Name#1234">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group col-6">
|
<div class="form-group col-4">
|
||||||
|
<label for="discord_user_id"><i class="fab fa-discord"></i> Discord User ID <small>(for DMs)</small></label>
|
||||||
|
<input type="text" id="discord_user_id" name="discord_user_id" value="{{ user.discord_user_id or '' }}" placeholder="Numeric ID (e.g. 123456789012345678)">
|
||||||
|
<small class="text-muted">Enable Developer Mode in Discord → Right-click profile → Copy ID</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-4">
|
||||||
<label for="league_os_profile"><i class="fas fa-link"></i> League OS Connection</label>
|
<label for="league_os_profile"><i class="fas fa-link"></i> League OS Connection</label>
|
||||||
<input type="text" id="league_os_profile" name="league_os_profile" value="{{ user.league_os_profile or '' }}" placeholder="League OS profile link or ID">
|
<input type="text" id="league_os_profile" name="league_os_profile" value="{{ user.league_os_profile or '' }}" placeholder="League OS profile link or ID">
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user