2025-08-13 07:45:37 +00:00
|
|
|
|
"""
|
|
|
|
|
|
DISCORD BOT - Personal Best Tracker for Multiple Bosses
|
|
|
|
|
|
======================================================
|
|
|
|
|
|
|
|
|
|
|
|
COMMANDS DOCUMENTATION:
|
|
|
|
|
|
=====================
|
|
|
|
|
|
|
|
|
|
|
|
📊 PERSONAL BEST COMMANDS:
|
|
|
|
|
|
--------------------------
|
|
|
|
|
|
!pbhydra → Show your Hydra PB + screenshot
|
|
|
|
|
|
!pbhydra <username> → Show username's Hydra PB + screenshot
|
|
|
|
|
|
!pbhydra <damage> → Submit new Hydra PB (requires screenshot attachment)
|
|
|
|
|
|
|
|
|
|
|
|
!pbchimera → Show your Chimera PB + screenshot
|
|
|
|
|
|
!pbchimera <username> → Show username's Chimera PB + screenshot
|
|
|
|
|
|
!pbchimera <damage> → Submit new Chimera PB (requires screenshot attachment)
|
|
|
|
|
|
|
|
|
|
|
|
!pbcvc → Show your CvC PB + screenshot
|
|
|
|
|
|
!pbcvc <username> → Show username's CvC PB + screenshot
|
|
|
|
|
|
!pbcvc <damage> → Submit new CvC PB (requires screenshot attachment)
|
|
|
|
|
|
|
|
|
|
|
|
🏆 LEADERBOARD COMMANDS (GLOBAL):
|
|
|
|
|
|
--------------------------------
|
|
|
|
|
|
!top10hydra → Top 10 Hydra records (all clans)
|
|
|
|
|
|
!top10chimera → Top 10 Chimera records (all clans)
|
|
|
|
|
|
!top10cvc → Top 10 CvC records (all clans)
|
|
|
|
|
|
|
|
|
|
|
|
⭐ LEADERBOARD COMMANDS (RTF CLAN):
|
|
|
|
|
|
----------------------------------
|
|
|
|
|
|
!rtfhydra → Top 10 Hydra records (RTF clan only)
|
|
|
|
|
|
!rtfchimera → Top 10 Chimera records (RTF clan only)
|
|
|
|
|
|
!rtfcvc → Top 10 CvC records (RTF clan only)
|
|
|
|
|
|
|
|
|
|
|
|
🔥 LEADERBOARD COMMANDS (RTFC CLAN):
|
|
|
|
|
|
-----------------------------------
|
|
|
|
|
|
!rtfchydra → Top 10 Hydra records (RTFC clan only)
|
|
|
|
|
|
!rtfcchimera → Top 10 Chimera records (RTFC clan only)
|
|
|
|
|
|
!rtfccvc → Top 10 CvC records (RTFC clan only)
|
|
|
|
|
|
|
|
|
|
|
|
⚡ LEADERBOARD COMMANDS (RTFR CLAN):
|
|
|
|
|
|
-----------------------------------
|
|
|
|
|
|
!rtfrhydra → Top 10 Hydra records (RTFR clan only)
|
|
|
|
|
|
!rtfrchimera → Top 10 Chimera records (RTFR clan only)
|
|
|
|
|
|
!rtfrcvc → Top 10 CvC records (RTFR clan only)
|
|
|
|
|
|
|
|
|
|
|
|
📈 STATS COMMANDS:
|
|
|
|
|
|
-----------------
|
|
|
|
|
|
!mystats → Show all your PBs across all bosses
|
|
|
|
|
|
!mystats <username> → Show all PBs for specified user
|
|
|
|
|
|
|
|
|
|
|
|
💡 USAGE EXAMPLES:
|
|
|
|
|
|
-----------------
|
|
|
|
|
|
!pbhydra [RTF]Alice → Shows Alice's Hydra PB and screenshot
|
|
|
|
|
|
!pbchimera 850000 → Submit 850k damage (with screenshot attached)
|
|
|
|
|
|
!rtfhydra → Shows RTF clan's top 10 Hydra records
|
|
|
|
|
|
!mystats [RTFC]Bob → Shows Bob's complete PB overview
|
|
|
|
|
|
|
|
|
|
|
|
🏛️ CLAN SYSTEM:
|
|
|
|
|
|
--------------
|
|
|
|
|
|
Clans are auto-detected from username prefixes:
|
|
|
|
|
|
- RTF members: [RTF]Username or RTFUsername
|
|
|
|
|
|
- RTFC members: [RTFC]Username or RTFCUsername
|
|
|
|
|
|
- RTFR members: [RTFR]Username or RTFRUsername
|
|
|
|
|
|
|
|
|
|
|
|
🔧 REQUIREMENTS:
|
|
|
|
|
|
---------------
|
|
|
|
|
|
- Screenshot must be attached when submitting new PBs
|
|
|
|
|
|
- Only works in authorized channel
|
|
|
|
|
|
- Supported image formats: PNG, JPG, JPEG, GIF, WEBP
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2025-04-29 07:16:53 +00:00
|
|
|
|
import discord
|
|
|
|
|
|
from discord.ext import commands
|
2025-08-13 07:45:37 +00:00
|
|
|
|
import sqlite3
|
|
|
|
|
|
import os
|
|
|
|
|
|
import aiohttp
|
|
|
|
|
|
from datetime import datetime
|
2025-04-29 07:16:53 +00:00
|
|
|
|
|
|
|
|
|
|
intents = discord.Intents.default()
|
2025-08-13 07:45:37 +00:00
|
|
|
|
intents.message_content = True
|
2025-04-29 07:16:53 +00:00
|
|
|
|
bot = commands.Bot(command_prefix="!", intents=intents)
|
|
|
|
|
|
|
2025-08-13 07:45:37 +00:00
|
|
|
|
# Configuration
|
|
|
|
|
|
AUTHORIZED_CHANNEL_ID = 0 # TODO: input channel ID here
|
|
|
|
|
|
SCREENSHOTS_BASE_PATH = "/share/Container/discord-bot/screenshots"
|
|
|
|
|
|
DATABASE_PATH = "/share/Container/discord-bot/bot_data.db"
|
|
|
|
|
|
|
|
|
|
|
|
# Configuration des clans
|
|
|
|
|
|
CLAN_CONFIG = {
|
|
|
|
|
|
'RTF': {
|
|
|
|
|
|
'name': 'RTF',
|
|
|
|
|
|
'emoji': '⭐',
|
|
|
|
|
|
'color': 0x00ff00
|
|
|
|
|
|
},
|
|
|
|
|
|
'RTFC': {
|
|
|
|
|
|
'name': 'RTFC',
|
|
|
|
|
|
'emoji': '🔥',
|
|
|
|
|
|
'color': 0xff4500
|
|
|
|
|
|
},
|
|
|
|
|
|
'RTFR': {
|
|
|
|
|
|
'name': 'RTFR',
|
|
|
|
|
|
'emoji': '⚡',
|
|
|
|
|
|
'color': 0x1e90ff
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
BOSS_CONFIG = {
|
|
|
|
|
|
'hydra': {
|
|
|
|
|
|
'name': 'Hydra',
|
|
|
|
|
|
'emoji': '🔥',
|
|
|
|
|
|
'color': 0xff6b35
|
|
|
|
|
|
},
|
|
|
|
|
|
'chimera': {
|
|
|
|
|
|
'name': 'Chimera',
|
|
|
|
|
|
'emoji': '⚡',
|
|
|
|
|
|
'color': 0x9932cc
|
|
|
|
|
|
},
|
|
|
|
|
|
'cvc': {
|
|
|
|
|
|
'name': 'Clan vs Clan',
|
|
|
|
|
|
'emoji': '⚔️',
|
|
|
|
|
|
'color': 0xff0000
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class DatabaseManager:
|
|
|
|
|
|
def __init__(self, db_path=DATABASE_PATH):
|
|
|
|
|
|
self.db_path = db_path
|
|
|
|
|
|
self.init_database()
|
|
|
|
|
|
|
|
|
|
|
|
def init_database(self):
|
|
|
|
|
|
"""Initialise la base de données"""
|
|
|
|
|
|
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
|
|
|
|
|
|
conn = sqlite3.connect(self.db_path)
|
|
|
|
|
|
cursor = conn.cursor()
|
|
|
|
|
|
|
|
|
|
|
|
cursor.execute('''
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
discord_username TEXT UNIQUE,
|
|
|
|
|
|
pb_hydra INTEGER DEFAULT 0,
|
|
|
|
|
|
pb_hydra_screenshot TEXT,
|
|
|
|
|
|
pb_hydra_date TIMESTAMP,
|
|
|
|
|
|
pb_chimera INTEGER DEFAULT 0,
|
|
|
|
|
|
pb_chimera_screenshot TEXT,
|
|
|
|
|
|
pb_chimera_date TIMESTAMP,
|
|
|
|
|
|
pb_cvc INTEGER DEFAULT 0,
|
|
|
|
|
|
pb_cvc_screenshot TEXT,
|
|
|
|
|
|
pb_cvc_date TIMESTAMP,
|
|
|
|
|
|
total_attempts INTEGER DEFAULT 0,
|
|
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
|
)
|
|
|
|
|
|
''')
|
|
|
|
|
|
|
|
|
|
|
|
# Table pour l'historique global
|
|
|
|
|
|
cursor.execute('''
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS pb_history (
|
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
|
username TEXT,
|
|
|
|
|
|
boss_type TEXT,
|
|
|
|
|
|
damage INTEGER,
|
|
|
|
|
|
screenshot_filename TEXT,
|
|
|
|
|
|
date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
|
)
|
|
|
|
|
|
''')
|
|
|
|
|
|
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
conn.close()
|
|
|
|
|
|
|
|
|
|
|
|
def get_user_pb(self, username, boss_type):
|
|
|
|
|
|
"""Récupère le PB d'un utilisateur pour un boss spécifique"""
|
|
|
|
|
|
conn = sqlite3.connect(self.db_path)
|
|
|
|
|
|
cursor = conn.cursor()
|
|
|
|
|
|
|
|
|
|
|
|
cursor.execute(
|
|
|
|
|
|
f"SELECT pb_{boss_type}, pb_{boss_type}_screenshot, pb_{boss_type}_date FROM users WHERE discord_username = ?",
|
|
|
|
|
|
(username.lower(),)
|
|
|
|
|
|
)
|
|
|
|
|
|
result = cursor.fetchone()
|
|
|
|
|
|
conn.close()
|
|
|
|
|
|
|
|
|
|
|
|
return result if result else (0, None, None)
|
|
|
|
|
|
|
|
|
|
|
|
def update_user_pb(self, username, boss_type, damage, screenshot_filename):
|
|
|
|
|
|
"""Met à jour le PB d'un utilisateur pour un boss spécifique"""
|
|
|
|
|
|
conn = sqlite3.connect(self.db_path)
|
|
|
|
|
|
cursor = conn.cursor()
|
|
|
|
|
|
|
|
|
|
|
|
# Créer l'utilisateur s'il n'existe pas, sinon mettre à jour
|
|
|
|
|
|
cursor.execute(f'''
|
|
|
|
|
|
INSERT INTO users (discord_username, pb_{boss_type}, pb_{boss_type}_screenshot, pb_{boss_type}_date, total_attempts)
|
|
|
|
|
|
VALUES (?, ?, ?, CURRENT_TIMESTAMP, 1)
|
|
|
|
|
|
ON CONFLICT(discord_username)
|
|
|
|
|
|
DO UPDATE SET
|
|
|
|
|
|
pb_{boss_type} = ?,
|
|
|
|
|
|
pb_{boss_type}_screenshot = ?,
|
|
|
|
|
|
pb_{boss_type}_date = CURRENT_TIMESTAMP,
|
|
|
|
|
|
total_attempts = total_attempts + 1
|
|
|
|
|
|
''', (username.lower(), damage, screenshot_filename, damage, screenshot_filename))
|
|
|
|
|
|
|
|
|
|
|
|
# Ajouter à l'historique
|
|
|
|
|
|
cursor.execute('''
|
|
|
|
|
|
INSERT INTO pb_history (username, boss_type, damage, screenshot_filename)
|
|
|
|
|
|
VALUES (?, ?, ?, ?)
|
|
|
|
|
|
''', (username.lower(), boss_type, damage, screenshot_filename))
|
|
|
|
|
|
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
|
conn.close()
|
|
|
|
|
|
|
|
|
|
|
|
def get_leaderboard(self, boss_type, limit=10, clan=None):
|
|
|
|
|
|
"""Récupère le classement pour un boss spécifique, optionnellement filtré par clan"""
|
|
|
|
|
|
conn = sqlite3.connect(self.db_path)
|
|
|
|
|
|
cursor = conn.cursor()
|
|
|
|
|
|
|
|
|
|
|
|
if clan:
|
|
|
|
|
|
cursor.execute(f'''
|
|
|
|
|
|
SELECT discord_username, pb_{boss_type}, pb_{boss_type}_date
|
|
|
|
|
|
FROM users
|
|
|
|
|
|
WHERE pb_{boss_type} > 0 AND (
|
|
|
|
|
|
discord_username LIKE '[{clan}]%' OR
|
|
|
|
|
|
discord_username LIKE '{clan}%'
|
|
|
|
|
|
)
|
|
|
|
|
|
ORDER BY pb_{boss_type} DESC
|
|
|
|
|
|
LIMIT ?
|
|
|
|
|
|
''', (limit,))
|
|
|
|
|
|
else:
|
|
|
|
|
|
cursor.execute(f'''
|
|
|
|
|
|
SELECT discord_username, pb_{boss_type}, pb_{boss_type}_date
|
|
|
|
|
|
FROM users
|
|
|
|
|
|
WHERE pb_{boss_type} > 0
|
|
|
|
|
|
ORDER BY pb_{boss_type} DESC
|
|
|
|
|
|
LIMIT ?
|
|
|
|
|
|
''', (limit,))
|
|
|
|
|
|
|
|
|
|
|
|
results = cursor.fetchall()
|
|
|
|
|
|
conn.close()
|
|
|
|
|
|
return results
|
|
|
|
|
|
|
|
|
|
|
|
def get_user_all_pbs(self, username):
|
|
|
|
|
|
"""Récupère tous les PB d'un utilisateur"""
|
|
|
|
|
|
conn = sqlite3.connect(self.db_path)
|
|
|
|
|
|
cursor = conn.cursor()
|
|
|
|
|
|
|
|
|
|
|
|
cursor.execute('''
|
|
|
|
|
|
SELECT pb_hydra, pb_hydra_date, pb_chimera, pb_chimera_date, pb_cvc, pb_cvc_date
|
|
|
|
|
|
FROM users WHERE discord_username = ?
|
|
|
|
|
|
''', (username.lower(),))
|
|
|
|
|
|
|
|
|
|
|
|
result = cursor.fetchone()
|
|
|
|
|
|
conn.close()
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
class ScreenshotManager:
|
|
|
|
|
|
def __init__(self, base_path=SCREENSHOTS_BASE_PATH):
|
|
|
|
|
|
self.base_path = base_path
|
|
|
|
|
|
# Créer les dossiers pour chaque boss
|
|
|
|
|
|
for boss_type in BOSS_CONFIG.keys():
|
|
|
|
|
|
os.makedirs(os.path.join(base_path, boss_type), exist_ok=True)
|
|
|
|
|
|
|
|
|
|
|
|
async def save_screenshot(self, attachment, username, damage, boss_type):
|
|
|
|
|
|
"""Sauvegarde la screenshot localement"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
timestamp = int(datetime.now().timestamp())
|
|
|
|
|
|
file_extension = attachment.filename.split('.')[-1].lower()
|
|
|
|
|
|
filename = f"{username.lower()}_{damage}_{timestamp}.{file_extension}"
|
|
|
|
|
|
boss_path = os.path.join(self.base_path, boss_type)
|
|
|
|
|
|
filepath = os.path.join(boss_path, filename)
|
|
|
|
|
|
|
|
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
|
|
|
|
async with session.get(attachment.url) as resp:
|
|
|
|
|
|
if resp.status == 200:
|
|
|
|
|
|
with open(filepath, 'wb') as f:
|
|
|
|
|
|
f.write(await resp.read())
|
|
|
|
|
|
return filename
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"Erreur sauvegarde screenshot: {e}")
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def get_screenshot_path(self, filename, boss_type):
|
|
|
|
|
|
"""Retourne le chemin complet de la screenshot"""
|
|
|
|
|
|
if filename:
|
|
|
|
|
|
return os.path.join(self.base_path, boss_type, filename)
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
# Fonctions utilitaires
|
|
|
|
|
|
def get_user_clan(username):
|
|
|
|
|
|
"""Détermine le clan d'un utilisateur basé sur son pseudo"""
|
|
|
|
|
|
username_upper = username.upper()
|
|
|
|
|
|
for clan_tag in ['[RTF]', '[RTFC]', '[RTFR]', 'RTF', 'RTFC', 'RTFR']:
|
|
|
|
|
|
if username_upper.startswith(clan_tag):
|
|
|
|
|
|
return clan_tag.replace('[', '').replace(']', '')
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def format_datetime(date_str):
|
|
|
|
|
|
"""Formate une date en format AM/PM"""
|
|
|
|
|
|
if not date_str:
|
|
|
|
|
|
return None
|
|
|
|
|
|
try:
|
|
|
|
|
|
dt = datetime.fromisoformat(date_str)
|
|
|
|
|
|
return dt.strftime("%m/%d/%Y at %I:%M %p")
|
|
|
|
|
|
except:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def format_date_only(date_str):
|
|
|
|
|
|
"""Formate une date sans l'heure"""
|
|
|
|
|
|
if not date_str:
|
|
|
|
|
|
return None
|
|
|
|
|
|
try:
|
|
|
|
|
|
dt = datetime.fromisoformat(date_str)
|
|
|
|
|
|
return dt.strftime("%m/%d/%Y")
|
|
|
|
|
|
except:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
# Initialisation des managers
|
|
|
|
|
|
db_manager = DatabaseManager()
|
|
|
|
|
|
screenshot_manager = ScreenshotManager()
|
2025-04-29 07:16:53 +00:00
|
|
|
|
|
|
|
|
|
|
@bot.event
|
|
|
|
|
|
async def on_ready():
|
|
|
|
|
|
print(f"Bot connected as {bot.user}")
|
|
|
|
|
|
|
2025-08-13 07:45:37 +00:00
|
|
|
|
async def handle_pb_command(ctx, boss_type, target_user=None, damage=None):
|
|
|
|
|
|
"""Fonction générique pour gérer toutes les commandes PB"""
|
2025-04-29 07:16:53 +00:00
|
|
|
|
if ctx.channel.id != AUTHORIZED_CHANNEL_ID:
|
|
|
|
|
|
return
|
2025-08-13 07:45:37 +00:00
|
|
|
|
|
|
|
|
|
|
boss_info = BOSS_CONFIG[boss_type]
|
|
|
|
|
|
|
2025-04-29 07:16:53 +00:00
|
|
|
|
try:
|
2025-08-13 07:45:37 +00:00
|
|
|
|
# Cas 1: !pb{boss} username (afficher le PB d'un autre utilisateur)
|
|
|
|
|
|
if target_user and damage is None and not target_user.isdigit():
|
|
|
|
|
|
pb_data = db_manager.get_user_pb(target_user, boss_type)
|
|
|
|
|
|
pb_damage, screenshot_filename, pb_date = pb_data
|
|
|
|
|
|
|
|
|
|
|
|
if pb_damage == 0:
|
|
|
|
|
|
await ctx.send(f"❌ **{target_user}** has no {boss_info['name']} PB recorded yet.")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
embed = discord.Embed(
|
|
|
|
|
|
title=f"{boss_info['emoji']} {target_user}'s {boss_info['name']} PB",
|
|
|
|
|
|
description=f"**{pb_damage:,} damage**",
|
|
|
|
|
|
color=boss_info['color']
|
|
|
|
|
|
)
|
|
|
|
|
|
if pb_date:
|
|
|
|
|
|
formatted_date = format_datetime(pb_date)
|
|
|
|
|
|
if formatted_date:
|
|
|
|
|
|
embed.add_field(name="📅 Record Date", value=formatted_date, inline=False)
|
|
|
|
|
|
|
|
|
|
|
|
# Envoyer la screenshot si elle existe
|
|
|
|
|
|
if screenshot_filename:
|
|
|
|
|
|
screenshot_path = screenshot_manager.get_screenshot_path(screenshot_filename, boss_type)
|
|
|
|
|
|
if screenshot_path and os.path.exists(screenshot_path):
|
|
|
|
|
|
file = discord.File(screenshot_path, filename=f"{target_user}_{boss_type}_pb.png")
|
|
|
|
|
|
embed.set_image(url=f"attachment://{target_user}_{boss_type}_pb.png")
|
|
|
|
|
|
await ctx.send(embed=embed, file=file)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
await ctx.send(embed=embed)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# Cas 2: !pb{boss} 1500000 (soumettre un nouveau PB)
|
|
|
|
|
|
if target_user and target_user.isdigit():
|
|
|
|
|
|
damage = int(target_user) # Le premier argument est en fait le damage
|
|
|
|
|
|
|
|
|
|
|
|
if not ctx.message.attachments:
|
|
|
|
|
|
await ctx.send("❌ Please attach a screenshot to validate your PB!")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
attachment = ctx.message.attachments[0]
|
|
|
|
|
|
if not any(attachment.filename.lower().endswith(ext) for ext in ['.png', '.jpg', '.jpeg', '.gif', '.webp']):
|
|
|
|
|
|
await ctx.send("❌ Please attach a valid image file!")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
username = ctx.author.name
|
|
|
|
|
|
current_pb, _, _ = db_manager.get_user_pb(username, boss_type)
|
|
|
|
|
|
|
|
|
|
|
|
if damage > current_pb:
|
|
|
|
|
|
# Sauvegarder la screenshot
|
|
|
|
|
|
screenshot_filename = await screenshot_manager.save_screenshot(attachment, username, damage, boss_type)
|
|
|
|
|
|
|
|
|
|
|
|
if screenshot_filename:
|
|
|
|
|
|
# Mettre à jour la base
|
|
|
|
|
|
db_manager.update_user_pb(username, boss_type, damage, screenshot_filename)
|
|
|
|
|
|
|
|
|
|
|
|
improvement = damage - current_pb if current_pb > 0 else damage
|
|
|
|
|
|
embed = discord.Embed(
|
|
|
|
|
|
title=f"🎉 NEW {boss_info['name'].upper()} PB! 🎉",
|
|
|
|
|
|
description=f"**{username}** just hit **{damage:,} damage** on {boss_info['name']}!",
|
|
|
|
|
|
color=0x00ff00
|
|
|
|
|
|
)
|
|
|
|
|
|
embed.add_field(name="📈 Improvement", value=f"+{improvement:,} damage", inline=True)
|
|
|
|
|
|
embed.set_image(url=attachment.url)
|
|
|
|
|
|
|
|
|
|
|
|
await ctx.send(embed=embed)
|
|
|
|
|
|
else:
|
|
|
|
|
|
await ctx.send("❌ Failed to save screenshot. Please try again.")
|
|
|
|
|
|
else:
|
|
|
|
|
|
embed = discord.Embed(
|
|
|
|
|
|
title="💪 Nice attempt!",
|
|
|
|
|
|
description=f"Your damage: **{damage:,}**\nCurrent PB: **{current_pb:,}**",
|
|
|
|
|
|
color=0xffa500
|
|
|
|
|
|
)
|
|
|
|
|
|
embed.add_field(
|
|
|
|
|
|
name="Keep going!",
|
|
|
|
|
|
value=f"You need **{current_pb - damage + 1:,}** more damage for a new PB!",
|
|
|
|
|
|
inline=False
|
|
|
|
|
|
)
|
|
|
|
|
|
await ctx.send(embed=embed)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# Cas 3: !pb{boss} (afficher son propre PB)
|
|
|
|
|
|
username = ctx.author.name
|
|
|
|
|
|
pb_data = db_manager.get_user_pb(username, boss_type)
|
|
|
|
|
|
pb_damage, screenshot_filename, pb_date = pb_data
|
|
|
|
|
|
|
|
|
|
|
|
if pb_damage == 0:
|
|
|
|
|
|
embed = discord.Embed(
|
|
|
|
|
|
title=f"{boss_info['emoji']} Your {boss_info['name']} PB",
|
|
|
|
|
|
description="**No record yet**",
|
|
|
|
|
|
color=0x666666
|
|
|
|
|
|
)
|
|
|
|
|
|
embed.add_field(
|
|
|
|
|
|
name="💡 Get started!",
|
|
|
|
|
|
value=f"Use `!pb{boss_type} <damage>` with a screenshot to set your first record!",
|
|
|
|
|
|
inline=False
|
|
|
|
|
|
)
|
|
|
|
|
|
await ctx.send(embed=embed)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
embed = discord.Embed(
|
|
|
|
|
|
title=f"{boss_info['emoji']} {username}'s {boss_info['name']} PB",
|
|
|
|
|
|
description=f"**{pb_damage:,} damage**",
|
|
|
|
|
|
color=boss_info['color']
|
|
|
|
|
|
)
|
|
|
|
|
|
if pb_date:
|
|
|
|
|
|
formatted_date = format_datetime(pb_date)
|
|
|
|
|
|
if formatted_date:
|
|
|
|
|
|
embed.add_field(name="📅 Record Date", value=formatted_date, inline=False)
|
|
|
|
|
|
|
|
|
|
|
|
# Envoyer la screenshot si elle existe
|
|
|
|
|
|
if screenshot_filename:
|
|
|
|
|
|
screenshot_path = screenshot_manager.get_screenshot_path(screenshot_filename, boss_type)
|
|
|
|
|
|
if screenshot_path and os.path.exists(screenshot_path):
|
|
|
|
|
|
file = discord.File(screenshot_path, filename=f"{username}_{boss_type}_pb.png")
|
|
|
|
|
|
embed.set_image(url=f"attachment://{username}_{boss_type}_pb.png")
|
|
|
|
|
|
await ctx.send(embed=embed, file=file)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
await ctx.send(embed=embed)
|
|
|
|
|
|
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
await ctx.send(f"❌ Please provide a valid damage number!\nExample: `!pb{boss_type} 1500000`")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
await ctx.send(f"❌ Error: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
# Commandes pour chaque boss
|
|
|
|
|
|
@bot.command()
|
|
|
|
|
|
async def pbhydra(ctx, target_user: str = None, damage: int = None):
|
|
|
|
|
|
"""Commande !pbhydra"""
|
|
|
|
|
|
await handle_pb_command(ctx, 'hydra', target_user, damage)
|
2025-04-29 07:16:53 +00:00
|
|
|
|
|
2025-08-13 07:45:37 +00:00
|
|
|
|
@bot.command()
|
|
|
|
|
|
async def pbchimera(ctx, target_user: str = None, damage: int = None):
|
|
|
|
|
|
"""Commande !pbchimera"""
|
|
|
|
|
|
await handle_pb_command(ctx, 'chimera', target_user, damage)
|
2025-04-29 07:16:53 +00:00
|
|
|
|
|
2025-08-13 07:45:37 +00:00
|
|
|
|
@bot.command()
|
|
|
|
|
|
async def pbcvc(ctx, target_user: str = None, damage: int = None):
|
|
|
|
|
|
"""Commande !pbcvc"""
|
|
|
|
|
|
await handle_pb_command(ctx, 'cvc', target_user, damage)
|
2025-04-29 07:16:53 +00:00
|
|
|
|
|
2025-08-13 07:45:37 +00:00
|
|
|
|
# Commandes de classement global
|
|
|
|
|
|
@bot.command()
|
|
|
|
|
|
async def top10hydra(ctx):
|
|
|
|
|
|
"""Affiche le top 10 des PB Hydra (tous clans)"""
|
|
|
|
|
|
await show_leaderboard(ctx, 'hydra')
|
2025-04-29 07:16:53 +00:00
|
|
|
|
|
2025-08-13 07:45:37 +00:00
|
|
|
|
@bot.command()
|
|
|
|
|
|
async def top10chimera(ctx):
|
|
|
|
|
|
"""Affiche le top 10 des PB Chimera (tous clans)"""
|
|
|
|
|
|
await show_leaderboard(ctx, 'chimera')
|
|
|
|
|
|
|
|
|
|
|
|
@bot.command()
|
|
|
|
|
|
async def top10cvc(ctx):
|
|
|
|
|
|
"""Affiche le top 10 des PB CvC (tous clans)"""
|
|
|
|
|
|
await show_leaderboard(ctx, 'cvc')
|
|
|
|
|
|
|
|
|
|
|
|
# Commandes de classement par clan - RTF
|
|
|
|
|
|
@bot.command()
|
|
|
|
|
|
async def rtfhydra(ctx):
|
|
|
|
|
|
"""Affiche le top 10 Hydra du clan RTF"""
|
|
|
|
|
|
await show_leaderboard(ctx, 'hydra', 'RTF')
|
|
|
|
|
|
|
|
|
|
|
|
@bot.command()
|
|
|
|
|
|
async def rtfchimera(ctx):
|
|
|
|
|
|
"""Affiche le top 10 Chimera du clan RTF"""
|
|
|
|
|
|
await show_leaderboard(ctx, 'chimera', 'RTF')
|
|
|
|
|
|
|
|
|
|
|
|
@bot.command()
|
|
|
|
|
|
async def rtfcvc(ctx):
|
|
|
|
|
|
"""Affiche le top 10 CvC du clan RTF"""
|
|
|
|
|
|
await show_leaderboard(ctx, 'cvc', 'RTF')
|
|
|
|
|
|
|
|
|
|
|
|
# Commandes de classement par clan - RTFC
|
|
|
|
|
|
@bot.command()
|
|
|
|
|
|
async def rtfchydra(ctx):
|
|
|
|
|
|
"""Affiche le top 10 Hydra du clan RTFC"""
|
|
|
|
|
|
await show_leaderboard(ctx, 'hydra', 'RTFC')
|
|
|
|
|
|
|
|
|
|
|
|
@bot.command()
|
|
|
|
|
|
async def rtfcchimera(ctx):
|
|
|
|
|
|
"""Affiche le top 10 Chimera du clan RTFC"""
|
|
|
|
|
|
await show_leaderboard(ctx, 'chimera', 'RTFC')
|
|
|
|
|
|
|
|
|
|
|
|
@bot.command()
|
|
|
|
|
|
async def rtfccvc(ctx):
|
|
|
|
|
|
"""Affiche le top 10 CvC du clan RTFC"""
|
|
|
|
|
|
await show_leaderboard(ctx, 'cvc', 'RTFC')
|
|
|
|
|
|
|
|
|
|
|
|
# Commandes de classement par clan - RTFR
|
|
|
|
|
|
@bot.command()
|
|
|
|
|
|
async def rtfrhydra(ctx):
|
|
|
|
|
|
"""Affiche le top 10 Hydra du clan RTFR"""
|
|
|
|
|
|
await show_leaderboard(ctx, 'hydra', 'RTFR')
|
|
|
|
|
|
|
|
|
|
|
|
@bot.command()
|
|
|
|
|
|
async def rtfrchimera(ctx):
|
|
|
|
|
|
"""Affiche le top 10 Chimera du clan RTFR"""
|
|
|
|
|
|
await show_leaderboard(ctx, 'chimera', 'RTFR')
|
|
|
|
|
|
|
|
|
|
|
|
@bot.command()
|
|
|
|
|
|
async def rtfrcvc(ctx):
|
|
|
|
|
|
"""Affiche le top 10 CvC du clan RTFR"""
|
|
|
|
|
|
await show_leaderboard(ctx, 'cvc', 'RTFR')
|
|
|
|
|
|
|
|
|
|
|
|
async def show_leaderboard(ctx, boss_type, clan=None):
|
|
|
|
|
|
"""Fonction générique pour afficher les classements"""
|
|
|
|
|
|
if ctx.channel.id != AUTHORIZED_CHANNEL_ID:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
boss_info = BOSS_CONFIG[boss_type]
|
|
|
|
|
|
leaderboard = db_manager.get_leaderboard(boss_type, 10, clan)
|
|
|
|
|
|
|
|
|
|
|
|
if not leaderboard:
|
|
|
|
|
|
clan_text = f" for clan {clan}" if clan else ""
|
|
|
|
|
|
await ctx.send(f"❌ No {boss_info['name']} records found{clan_text} yet!")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# Titre avec clan si spécifié
|
|
|
|
|
|
title = f"🏆 {boss_info['name']} Leaderboard - Top 10"
|
|
|
|
|
|
if clan:
|
|
|
|
|
|
clan_info = CLAN_CONFIG.get(clan, {'name': clan, 'emoji': '🏛️'})
|
|
|
|
|
|
title = f"{clan_info['emoji']} {clan_info['name']} - {boss_info['name']} Top 10"
|
|
|
|
|
|
|
|
|
|
|
|
embed = discord.Embed(
|
|
|
|
|
|
title=title,
|
|
|
|
|
|
color=boss_info['color'] if not clan else CLAN_CONFIG.get(clan, {'color': boss_info['color']})['color']
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
medals = ["🥇", "🥈", "🥉"] + ["🏅"] * 7
|
|
|
|
|
|
|
|
|
|
|
|
for i, (username, damage, date) in enumerate(leaderboard):
|
|
|
|
|
|
date_text = ""
|
|
|
|
|
|
if date:
|
|
|
|
|
|
formatted_date = format_date_only(date)
|
|
|
|
|
|
if formatted_date:
|
|
|
|
|
|
date_text = f" • {formatted_date}"
|
|
|
|
|
|
|
|
|
|
|
|
# Afficher le clan dans le nom si pas de filtre par clan
|
|
|
|
|
|
display_name = username
|
|
|
|
|
|
if not clan:
|
|
|
|
|
|
user_clan = get_user_clan(username)
|
|
|
|
|
|
if user_clan:
|
|
|
|
|
|
clan_emoji = CLAN_CONFIG.get(user_clan, {'emoji': '🏛️'})['emoji']
|
|
|
|
|
|
display_name = f"{clan_emoji} {username}"
|
|
|
|
|
|
|
|
|
|
|
|
embed.add_field(
|
|
|
|
|
|
name=f"{medals[i]} #{i+1} {display_name}",
|
|
|
|
|
|
value=f"**{damage:,}** damage{date_text}",
|
|
|
|
|
|
inline=False
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
await ctx.send(embed=embed)
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
await ctx.send(f"❌ Error: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
@bot.command()
|
|
|
|
|
|
async def mystats(ctx, target_user: str = None):
|
|
|
|
|
|
"""Affiche tous les PB d'un utilisateur"""
|
|
|
|
|
|
if ctx.channel.id != AUTHORIZED_CHANNEL_ID:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
username = target_user if target_user else ctx.author.name
|
|
|
|
|
|
user_data = db_manager.get_user_all_pbs(username)
|
|
|
|
|
|
|
|
|
|
|
|
if not user_data:
|
|
|
|
|
|
await ctx.send(f"❌ No data found for **{username}**.")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
hydra_pb, hydra_date, chimera_pb, chimera_date, cvc_pb, cvc_date = user_data
|
|
|
|
|
|
|
|
|
|
|
|
embed = discord.Embed(
|
|
|
|
|
|
title=f"📊 {username}'s Complete Stats",
|
|
|
|
|
|
color=0x00bfff
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# Hydra
|
|
|
|
|
|
hydra_text = f"**{hydra_pb:,} damage**" if hydra_pb > 0 else "No record"
|
|
|
|
|
|
if hydra_pb > 0 and hydra_date:
|
|
|
|
|
|
formatted_date = format_date_only(hydra_date)
|
|
|
|
|
|
if formatted_date:
|
|
|
|
|
|
hydra_text += f"\n📅 {formatted_date}"
|
|
|
|
|
|
embed.add_field(name="🔥 Hydra PB", value=hydra_text, inline=True)
|
|
|
|
|
|
|
|
|
|
|
|
# Chimera
|
|
|
|
|
|
chimera_text = f"**{chimera_pb:,} damage**" if chimera_pb > 0 else "No record"
|
|
|
|
|
|
if chimera_pb > 0 and chimera_date:
|
|
|
|
|
|
formatted_date = format_date_only(chimera_date)
|
|
|
|
|
|
if formatted_date:
|
|
|
|
|
|
chimera_text += f"\n📅 {formatted_date}"
|
|
|
|
|
|
embed.add_field(name="⚡ Chimera PB", value=chimera_text, inline=True)
|
|
|
|
|
|
|
|
|
|
|
|
# CvC
|
|
|
|
|
|
cvc_text = f"**{cvc_pb:,} damage**" if cvc_pb > 0 else "No record"
|
|
|
|
|
|
if cvc_pb > 0 and cvc_date:
|
|
|
|
|
|
formatted_date = format_date_only(cvc_date)
|
|
|
|
|
|
if formatted_date:
|
|
|
|
|
|
cvc_text += f"\n📅 {formatted_date}"
|
|
|
|
|
|
embed.add_field(name="⚔️ CvC PB", value=cvc_text, inline=True)
|
|
|
|
|
|
|
|
|
|
|
|
# Total des PB
|
|
|
|
|
|
total_damage = (hydra_pb or 0) + (chimera_pb or 0) + (cvc_pb or 0)
|
|
|
|
|
|
embed.add_field(name="💯 Total Combined", value=f"**{total_damage:,} damage**", inline=False)
|
|
|
|
|
|
|
|
|
|
|
|
await ctx.send(embed=embed)
|
|
|
|
|
|
|
2025-04-29 07:16:53 +00:00
|
|
|
|
except Exception as e:
|
2025-08-13 07:45:37 +00:00
|
|
|
|
await ctx.send(f"❌ Error: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
# TODO: Add your bot token here
|
|
|
|
|
|
# bot.run("YOUR_DISCORD_TOKEN")
|