Compare commits

..

No commits in common. "0d744c8cdcf95e633d6f07890dad1e5a43fc73aa" and "ec86776e4208e650cf83c3cee280a41dbbd9c9cb" have entirely different histories.

7 changed files with 84 additions and 113 deletions

View file

@ -16,7 +16,7 @@ class Guide(commands.Cog):
return return
embed = discord.Embed( embed = discord.Embed(
title="🧐 TEA Bot - Commands Guide", title="🧐 RTF Bot - Commands Guide",
description="Here are all available commands for tracking your Personal Bests!", description="Here are all available commands for tracking your Personal Bests!",
color=0x00bfff color=0x00bfff
) )
@ -81,10 +81,9 @@ class Guide(commands.Cog):
# Classements par clan # Classements par clan
embed.add_field( embed.add_field(
name="🏆 Clan Leaderboards", name="🏆 Clan Leaderboards",
value="**🔥 TEAI (Inferno):** `!teaihydra <diff>` `!teaichimera <diff>` `!teaicvc`\n" value="**RTF:** `!rtfhydra <diff>` `!rtfchimera <diff>` `!rtfcvc`\n"
"**🛡️ TEAF (Flame):** `!teafhydra <diff>` `!teafchimera <diff>` `!teafcvc`\n" "**RTFC:** `!rtfchydra <diff>` `!rtfcchimera <diff>` `!rtfccvc`\n"
"**⚔️ TEAC (Cinder):** `!teachydra <diff>` `!teachimera <diff>` `!teaccvc`\n" "**RTFR:** `!rtfrhydra <diff>` `!rtfrchimera <diff>` `!rtfrcvc`",
"**👑 TEACO (Corrupted Olympians):** `!teacohydra <diff>` `!teacochimera <diff>` `!teacocvc`",
inline=False inline=False
) )
@ -105,7 +104,7 @@ class Guide(commands.Cog):
"`!pbcvc 2.3M` - Submit CvC PB\n" "`!pbcvc 2.3M` - Submit CvC PB\n"
"`!mercy add 50 primal` - Add 50 pulls to Primal shard\n" "`!mercy add 50 primal` - Add 50 pulls to Primal shard\n"
"`!mercy show` - Show your mercy pulls\n" "`!mercy show` - Show your mercy pulls\n"
"`!teaihydra nm` - TEAI clan Nightmare rankings\n" "`!rtfhydra nm` - RTF clan Nightmare rankings\n"
"**Always attach screenshot when submitting PBs!**", "**Always attach screenshot when submitting PBs!**",
inline=False inline=False
) )

View file

@ -32,57 +32,44 @@ class Top10(commands.Cog):
async def top10cvc(self, ctx): async def top10cvc(self, ctx):
await show_leaderboard(ctx, 'cvc') await show_leaderboard(ctx, 'cvc')
# --- Commandes par clan TEAI (Inferno) --- # --- Commandes par clan RTF ---
@commands.command() @commands.command()
async def teaihydra(self, ctx, difficulty: str = None): async def rtfhydra(self, ctx, difficulty: str = None):
await self._show_clan_leaderboard(ctx, 'hydra', difficulty, 'TEAI') await self._show_clan_leaderboard(ctx, 'hydra', difficulty, 'RTF')
@commands.command() @commands.command()
async def teaichimera(self, ctx, difficulty: str = None): async def rtfchimera(self, ctx, difficulty: str = None):
await self._show_clan_leaderboard(ctx, 'chimera', difficulty, 'TEAI') await self._show_clan_leaderboard(ctx, 'chimera', difficulty, 'RTF')
@commands.command() @commands.command()
async def teaicvc(self, ctx): async def rtfcvc(self, ctx):
await show_leaderboard(ctx, 'cvc', clan='TEAI') await show_leaderboard(ctx, 'cvc', clan='RTF')
# --- Commandes par clan TEAF (Flame) --- # --- Commandes par clan RTFC ---
@commands.command() @commands.command()
async def teafhydra(self, ctx, difficulty: str = None): async def rtfchydra(self, ctx, difficulty: str = None):
await self._show_clan_leaderboard(ctx, 'hydra', difficulty, 'TEAF') await self._show_clan_leaderboard(ctx, 'hydra', difficulty, 'RTFC')
@commands.command() @commands.command()
async def teafchimera(self, ctx, difficulty: str = None): async def rtfcchimera(self, ctx, difficulty: str = None):
await self._show_clan_leaderboard(ctx, 'chimera', difficulty, 'TEAF') await self._show_clan_leaderboard(ctx, 'chimera', difficulty, 'RTFC')
@commands.command() @commands.command()
async def teafcvc(self, ctx): async def rtfccvc(self, ctx):
await show_leaderboard(ctx, 'cvc', clan='TEAF') await show_leaderboard(ctx, 'cvc', clan='RTFC')
# --- Commandes par clan TEAC (Cinder) --- # --- Commandes par clan RTFR ---
@commands.command() @commands.command()
async def teachydra(self, ctx, difficulty: str = None): async def rtfrhydra(self, ctx, difficulty: str = None):
await self._show_clan_leaderboard(ctx, 'hydra', difficulty, 'TEAC') await self._show_clan_leaderboard(ctx, 'hydra', difficulty, 'RTFR')
@commands.command() @commands.command()
async def teachimera(self, ctx, difficulty: str = None): async def rtfrchimera(self, ctx, difficulty: str = None):
await self._show_clan_leaderboard(ctx, 'chimera', difficulty, 'TEAC') await self._show_clan_leaderboard(ctx, 'chimera', difficulty, 'RTFR')
@commands.command() @commands.command()
async def teaccvc(self, ctx): async def rtfrcvc(self, ctx):
await show_leaderboard(ctx, 'cvc', clan='TEAC') await show_leaderboard(ctx, 'cvc', clan='RTFR')
# --- Commandes par clan TEACO (Corrupted Olympians) ---
@commands.command()
async def teacohydra(self, ctx, difficulty: str = None):
await self._show_clan_leaderboard(ctx, 'hydra', difficulty, 'TEACO')
@commands.command()
async def teacochimera(self, ctx, difficulty: str = None):
await self._show_clan_leaderboard(ctx, 'chimera', difficulty, 'TEACO')
@commands.command()
async def teacocvc(self, ctx):
await show_leaderboard(ctx, 'cvc', clan='TEACO')
# --- Méthode interne pour éviter la répétition --- # --- Méthode interne pour éviter la répétition ---
async def _show_clan_leaderboard(self, ctx, boss_type, difficulty, clan): async def _show_clan_leaderboard(self, ctx, boss_type, difficulty, clan):

View file

@ -12,27 +12,11 @@ AUTHORIZED_CHANNEL_ID = int(os.getenv("AUTHORIZED_CHANNEL_ID"))
SCREENSHOTS_BASE_PATH = "/app/screenshots" SCREENSHOTS_BASE_PATH = "/app/screenshots"
DATABASE_PATH = "/app/data/bot_data.db" DATABASE_PATH = "/app/data/bot_data.db"
# Configuration des clans TEA - The Ember Accord # Configuration des clans
CLAN_CONFIG = { CLAN_CONFIG = {
'TEAI': {'name': 'TEAI', 'full_name': 'Inferno', 'emoji': '🔥', 'color': 0xff4500}, 'RTF': {'name': 'RTF', 'emoji': '🛡️', 'color': 0x00ff00},
'TEAF': {'name': 'TEAF', 'full_name': 'Flame', 'emoji': '🛡️', 'color': 0x00ff00}, 'RTFC': {'name': 'RTFC', 'emoji': '🔥', 'color': 0xff4500},
'TEAC': {'name': 'TEAC', 'full_name': 'Cinder', 'emoji': '⚔️', 'color': 0x1e90ff}, 'RTFR': {'name': 'RTFR', 'emoji': '⚔️', 'color': 0x1e90ff}
'TEACO': {'name': 'TEACO', 'full_name': 'Corrupted Olympians', 'emoji': '👑', 'color': 0x9932cc},
}
# Mapping role Discord ID → clé de clan [IDs serveur DEV — à remplacer]
CLAN_ROLE_IDS = {
0000000000000000001: 'TEAI', # TODO: remplacer par l'ID rôle TEAI du serveur dev
0000000000000000002: 'TEAF', # TODO: remplacer par l'ID rôle TEAF du serveur dev
0000000000000000003: 'TEAC', # TODO: remplacer par l'ID rôle TEAC du serveur dev
0000000000000000004: 'TEACO', # TODO: remplacer par l'ID rôle TEACO du serveur dev
}
# Mapping anciens clans → nouveaux (migration base existante)
CLAN_MIGRATION = {
'RTF': 'TEAI',
'RTFC': 'TEAF',
'RTFR': 'TEAC',
} }
# Configuration des boss avec difficultés # Configuration des boss avec difficultés

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from config import DATABASE_PATH, CLAN_MIGRATION from config import DATABASE_PATH
import sqlite3, os import sqlite3, os
class DatabaseManager: class DatabaseManager:
@ -70,16 +70,7 @@ class DatabaseManager:
if 'discord_id' not in columns: if 'discord_id' not in columns:
cursor.execute('ALTER TABLE users ADD COLUMN discord_id TEXT') cursor.execute('ALTER TABLE users ADD COLUMN discord_id TEXT')
# Note: Vous devrez peut-être faire une migration manuelle pour les données existantes
if 'clan' not in columns:
cursor.execute('ALTER TABLE users ADD COLUMN clan TEXT')
# Migration automatique : déduction du clan depuis l'ancien préfixe du pseudo
for old_tag, new_clan in CLAN_MIGRATION.items():
cursor.execute(
"UPDATE users SET clan = ? WHERE clan IS NULL AND ("
"discord_username LIKE ? OR discord_username LIKE ?)",
(new_clan, f'[{old_tag}] %', f'[{old_tag}]%')
)
# Table pour l'historique global # Table pour l'historique global
cursor.execute(''' cursor.execute('''
@ -117,7 +108,7 @@ class DatabaseManager:
return result if result else (0, None, None) return result if result else (0, None, None)
def update_user_pb(self, user_id, username, boss_type, damage, screenshot_filename, difficulty=None, clan=None): def update_user_pb(self, user_id, username, boss_type, damage, screenshot_filename, difficulty=None):
"""Met à jour le PB d'un utilisateur et supprime l'ancien screenshot""" """Met à jour le PB d'un utilisateur et supprime l'ancien screenshot"""
conn = sqlite3.connect(self.db_path) conn = sqlite3.connect(self.db_path)
cursor = conn.cursor() cursor = conn.cursor()
@ -131,19 +122,18 @@ class DatabaseManager:
else: else:
column_prefix = f"pb_{boss_type}" column_prefix = f"pb_{boss_type}"
# COALESCE(?, clan) : on n'écrase pas le clan existant si la détection retourne None # Créer l'utilisateur s'il n'existe pas, sinon mettre à jour
cursor.execute(f''' cursor.execute(f'''
INSERT INTO users (discord_id, discord_username, clan, {column_prefix}, {column_prefix}_screenshot, {column_prefix}_date, total_attempts) INSERT INTO users (discord_id, discord_username, {column_prefix}, {column_prefix}_screenshot, {column_prefix}_date, total_attempts)
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP, 1) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP, 1)
ON CONFLICT(discord_id) ON CONFLICT(discord_id)
DO UPDATE SET DO UPDATE SET
discord_username = ?, discord_username = ?,
clan = COALESCE(?, clan),
{column_prefix} = ?, {column_prefix} = ?,
{column_prefix}_screenshot = ?, {column_prefix}_screenshot = ?,
{column_prefix}_date = CURRENT_TIMESTAMP, {column_prefix}_date = CURRENT_TIMESTAMP,
total_attempts = total_attempts + 1 total_attempts = total_attempts + 1
''', (str(user_id), username, clan, damage, screenshot_filename, username, clan, damage, screenshot_filename)) ''', (str(user_id), username, damage, screenshot_filename, username, damage, screenshot_filename))
# Ajouter à l'historique # Ajouter à l'historique
cursor.execute(''' cursor.execute('''
@ -167,20 +157,20 @@ class DatabaseManager:
column_prefix = f"pb_{boss_type}" column_prefix = f"pb_{boss_type}"
base_query = f''' base_query = f'''
SELECT discord_username, {column_prefix}, {column_prefix}_date, clan SELECT discord_username, {column_prefix}, {column_prefix}_date
FROM users FROM users
WHERE {column_prefix} > 0 WHERE {column_prefix} > 0
''' '''
params = []
if clan: if clan:
base_query += ' AND clan = ?' base_query += ''' AND (
params.append(clan) discord_username LIKE '[''' + clan + '''] %' OR
discord_username LIKE '[''' + clan + ''']%'
)'''
base_query += f' ORDER BY {column_prefix} DESC LIMIT ?' base_query += f' ORDER BY {column_prefix} DESC LIMIT ?'
params.append(limit)
cursor.execute(base_query, params) cursor.execute(base_query, (limit,))
results = cursor.fetchall() results = cursor.fetchall()
conn.close() conn.close()
return results return results

View file

@ -1,8 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import re import re
from datetime import datetime from datetime import datetime
from typing import Optional from config import AUTHORIZED_CHANNEL_ID, DIFFICULTY_SHORTCUTS
from config import AUTHORIZED_CHANNEL_ID, DIFFICULTY_SHORTCUTS, CLAN_ROLE_IDS
def parse_damage_amount(damage_str): def parse_damage_amount(damage_str):
"""Convertit les montants avec suffixes (K, M, B) en nombres entiers""" """Convertit les montants avec suffixes (K, M, B) en nombres entiers"""
@ -44,13 +43,25 @@ def normalize_difficulty(difficulty):
return DIFFICULTY_SHORTCUTS[difficulty_lower] return DIFFICULTY_SHORTCUTS[difficulty_lower]
return difficulty_lower return difficulty_lower
def get_clan_from_member(member) -> Optional[str]: def get_user_clan(username):
"""Détecte le clan d'un membre via ses rôles Discord""" """Détermine le clan d'un utilisateur basé sur son pseudo"""
for role in member.roles: if not username:
if role.id in CLAN_ROLE_IDS: return None
return CLAN_ROLE_IDS[role.id] username_upper = username.upper()
# Tags avec crochets et espace
for clan_tag in ['[RTF] ', '[RTFC] ', '[RTFR] ']:
if username_upper.startswith(clan_tag):
return clan_tag.replace('[', '').replace(']', '').strip()
# Tags avec crochets sans espace
for clan_tag in ['[RTF]', '[RTFC]', '[RTFR]']:
if username_upper.startswith(clan_tag):
return clan_tag.replace('[', '').replace(']', '')
return None return None
def get_user_clan_from_ctx(ctx):
"""Détermine le clan d'un utilisateur depuis le contexte Discord"""
return get_user_clan(ctx.author.display_name)
def format_datetime(date_str): def format_datetime(date_str):
"""Formate une date en format AM/PM""" """Formate une date en format AM/PM"""
if not date_str: if not date_str:

View file

@ -3,7 +3,7 @@ import os
import discord import discord
from discord.ext import commands from discord.ext import commands
from config import AUTHORIZED_CHANNEL_ID, BOSS_CONFIG, CLAN_CONFIG from config import AUTHORIZED_CHANNEL_ID, BOSS_CONFIG, CLAN_CONFIG
from utils.helpers import normalize_difficulty, get_difficulty_display_name, format_damage_display, format_date_only from utils.helpers import normalize_difficulty, get_difficulty_display_name, format_damage_display, format_date_only, get_user_clan
db_manager = None db_manager = None
@ -49,18 +49,20 @@ async def show_leaderboard(ctx, boss_type, difficulty=None, clan=None):
medals = ["🥇", "🥈", "🥉"] + ["🏅"] * 7 medals = ["🥇", "🥈", "🥉"] + ["🏅"] * 7
for i, (username, damage, date, row_clan) in enumerate(leaderboard): for i, (username, damage, date) in enumerate(leaderboard):
date_text = "" date_text = ""
if date: if date:
formatted_date = format_date_only(date) formatted_date = format_date_only(date)
if formatted_date: if formatted_date:
date_text = f"{formatted_date}" date_text = f"{formatted_date}"
# Afficher l'emoji du clan si le leaderboard est global (pas filtré par clan) # Afficher le clan dans le nom si pas de filtre par clan
display_name = username display_name = username
if not clan and row_clan: if not clan:
clan_emoji = CLAN_CONFIG.get(row_clan, {'emoji': '🏛️'})['emoji'] user_clan = get_user_clan(username)
display_name = f"{clan_emoji} {username}" if user_clan:
clan_emoji = CLAN_CONFIG.get(user_clan, {'emoji': '🏛️'})['emoji']
display_name = f"{clan_emoji} {username}"
embed.add_field( embed.add_field(
name=f"{medals[i]} #{i+1} {display_name}", name=f"{medals[i]} #{i+1} {display_name}",

View file

@ -8,7 +8,6 @@ from utils.helpers import (
get_difficulty_display_name, get_difficulty_display_name,
format_damage_display, format_damage_display,
format_datetime, format_datetime,
get_clan_from_member,
) )
db_manager = None db_manager = None
@ -93,7 +92,6 @@ async def handle_pb_submission(ctx, boss_type, difficulty, damage):
user_id = ctx.author.id user_id = ctx.author.id
username = ctx.author.display_name username = ctx.author.display_name
clan = get_clan_from_member(ctx.author)
current_pb, _, _ = db_manager.get_user_pb(user_id, boss_type, difficulty) current_pb, _, _ = db_manager.get_user_pb(user_id, boss_type, difficulty)
if damage > current_pb: if damage > current_pb:
@ -103,7 +101,7 @@ async def handle_pb_submission(ctx, boss_type, difficulty, damage):
if screenshot_filename: if screenshot_filename:
old_screenshot = db_manager.update_user_pb( old_screenshot = db_manager.update_user_pb(
user_id, username, boss_type, damage, screenshot_filename, difficulty, clan user_id, username, boss_type, damage, screenshot_filename, difficulty
) )
if old_screenshot: if old_screenshot: