i18n: translate all French comments, docstrings and logs to English
All checks were successful
Deploy Bot on NAS / deploy (push) Successful in 29s
All checks were successful
Deploy Bot on NAS / deploy (push) Successful in 29s
discord.py's built-in !help command exposes cog docstrings directly to Discord members — leaving them in French made the output partially French. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ca8d761293
commit
6621599fc6
16 changed files with 1168 additions and 1159 deletions
|
|
@ -56,6 +56,15 @@ Dockerfile # Image Python 3.9-slim
|
|||
docker-compose.yml # Déploiement container
|
||||
```
|
||||
|
||||
## Langue du code
|
||||
|
||||
Tout le code source doit être **exclusivement en anglais** :
|
||||
- Docstrings et commentaires
|
||||
- Messages Discord affichés aux utilisateurs
|
||||
- Logs (`print`, `logging`)
|
||||
|
||||
Le `!help` de discord.py affiche les docstrings des cogs directement — les laisser en français les rend visibles aux membres.
|
||||
|
||||
## Boss supportés
|
||||
|
||||
- **Hydra** : normal, hard, brutal, nightmare
|
||||
|
|
|
|||
18
bot.py
18
bot.py
|
|
@ -16,20 +16,20 @@ from utils.leaderboard_handler import set_db_manager
|
|||
os.environ["PYTHONIOENCODING"] = "utf-8"
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
|
||||
# Définir les intents
|
||||
# Define intents
|
||||
intents = discord.Intents.default()
|
||||
intents.message_content = True
|
||||
|
||||
# Initialisation des managers
|
||||
# Initialize managers
|
||||
db_manager = DatabaseManager()
|
||||
screenshot_manager = ScreenshotManager()
|
||||
mercy_manager = MercyManager()
|
||||
|
||||
# Injection des managers dans les handlers
|
||||
# Inject managers into handlers
|
||||
set_managers(db_manager, screenshot_manager) # pb_handler
|
||||
set_db_manager(db_manager) # leaderboard_handler
|
||||
|
||||
# Liste des cogs
|
||||
# Cog list
|
||||
initial_cogs = [
|
||||
"cogs.guide",
|
||||
"cogs.pbhydra",
|
||||
|
|
@ -40,7 +40,7 @@ initial_cogs = [
|
|||
"cogs.mercy",
|
||||
]
|
||||
|
||||
# Liste des dossiers
|
||||
# Directory list
|
||||
folders = [
|
||||
"screenshots/hydra/normal",
|
||||
"screenshots/hydra/hard",
|
||||
|
|
@ -55,7 +55,7 @@ folders = [
|
|||
"screenshots/cvc",
|
||||
]
|
||||
|
||||
# Création des dossiers si nécessaire (exist_ok=True évite d'écraser)
|
||||
# Create directories if needed (exist_ok=True avoids overwriting)
|
||||
for f in folders:
|
||||
os.makedirs(f, exist_ok=True)
|
||||
|
||||
|
|
@ -70,12 +70,12 @@ class MyBot(commands.Bot):
|
|||
for cog in initial_cogs:
|
||||
try:
|
||||
await self.load_extension(cog)
|
||||
print(f"[OK] Cog {cog} chargé")
|
||||
print(f"[OK] Cog {cog} loaded")
|
||||
except Exception as e:
|
||||
print(f"[ERREUR] Impossible de charger {cog}: {e}")
|
||||
print(f"[ERROR] Failed to load {cog}: {e}")
|
||||
|
||||
async def on_ready(self):
|
||||
print(f"{self.user.name} est connecté !")
|
||||
print(f"{self.user.name} connected!")
|
||||
|
||||
bot = MyBot()
|
||||
bot.run(DISCORD_TOKEN)
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ from discord.ext import commands
|
|||
from config import AUTHORIZED_CHANNEL_ID
|
||||
|
||||
class Guide(commands.Cog):
|
||||
"""Affiche la liste des commandes disponibles"""
|
||||
"""Shows the list of available commands"""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
@commands.command(name="guide")
|
||||
async def guide(self, ctx):
|
||||
"""Affiche toutes les commandes disponibles avec les difficultés"""
|
||||
"""Shows all available commands with difficulties"""
|
||||
if ctx.channel.id != AUTHORIZED_CHANNEL_ID:
|
||||
return
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ class Guide(commands.Cog):
|
|||
color=0x00bfff
|
||||
)
|
||||
|
||||
# Info sur les formats de dégâts
|
||||
# Damage format info
|
||||
embed.add_field(
|
||||
name="💠 Damage Formats",
|
||||
value="**Accepted formats:** `1500000`, `1.5M`, `500K`, `2B`\n"
|
||||
|
|
@ -30,7 +30,7 @@ class Guide(commands.Cog):
|
|||
inline=False
|
||||
)
|
||||
|
||||
# Commandes PB Hydra
|
||||
# Hydra PB commands
|
||||
embed.add_field(
|
||||
name="🐍 Hydra Commands",
|
||||
value="**Difficulties:** Normal | Hard | Brutal | Nightmare (nm)\n"
|
||||
|
|
@ -40,7 +40,7 @@ class Guide(commands.Cog):
|
|||
inline=False
|
||||
)
|
||||
|
||||
# Commandes PB Chimera
|
||||
# Chimera PB commands
|
||||
embed.add_field(
|
||||
name="🦁 Chimera Commands",
|
||||
value="**Difficulties:** Easy | Normal | Hard | Brutal | Nightmare (nm) | Ultra (unm)\n"
|
||||
|
|
@ -50,7 +50,7 @@ class Guide(commands.Cog):
|
|||
inline=False
|
||||
)
|
||||
|
||||
# Commandes PB CvC
|
||||
# CvC PB commands
|
||||
embed.add_field(
|
||||
name="⚔️ CvC Commands",
|
||||
value="`!pbcvc <damage>` - Submit PB + screenshot\n"
|
||||
|
|
@ -59,7 +59,7 @@ class Guide(commands.Cog):
|
|||
inline=False
|
||||
)
|
||||
|
||||
# Commandes Mercy
|
||||
# Mercy commands
|
||||
embed.add_field(
|
||||
name="🎲 Mercy Commands",
|
||||
value="`!mercy show` - Show your current mercy pulls\n"
|
||||
|
|
@ -69,7 +69,7 @@ class Guide(commands.Cog):
|
|||
inline=False
|
||||
)
|
||||
|
||||
# Classements globaux
|
||||
# Global leaderboards
|
||||
embed.add_field(
|
||||
name="🌍 Global Leaderboards",
|
||||
value="`!top10hydra <difficulty>` - Global Hydra rankings\n"
|
||||
|
|
@ -78,7 +78,7 @@ class Guide(commands.Cog):
|
|||
inline=False
|
||||
)
|
||||
|
||||
# Classements par clan
|
||||
# Clan leaderboards
|
||||
embed.add_field(
|
||||
name="🏆 Clan Leaderboards",
|
||||
value="**🔥 TEAI (Inferno):** `!teaihydra <diff>` `!teaichimera <diff>` `!teaicvc`\n"
|
||||
|
|
@ -88,7 +88,7 @@ class Guide(commands.Cog):
|
|||
inline=False
|
||||
)
|
||||
|
||||
# Stats et aide
|
||||
# Stats and help
|
||||
embed.add_field(
|
||||
name="📈 Stats & Info",
|
||||
value="`!mystats` - View all your PBs\n"
|
||||
|
|
@ -97,7 +97,7 @@ class Guide(commands.Cog):
|
|||
inline=False
|
||||
)
|
||||
|
||||
# Instructions
|
||||
# Examples
|
||||
embed.add_field(
|
||||
name="⚡ Examples",
|
||||
value="`!pbhydra brutal 1.5M` - Submit Brutal Hydra PB\n"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from utils.helpers import calc_chance_and_guarantee
|
|||
VALID_SHARDS = ["ancient", "void", "sacred", "primal", "remnant"]
|
||||
|
||||
class Mercy(commands.Cog):
|
||||
"""Cog pour gérer les pulls de Mercy"""
|
||||
"""Cog for managing Mercy pulls"""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
|
|
|||
|
|
@ -3,17 +3,17 @@ import discord
|
|||
from discord.ext import commands
|
||||
from config import AUTHORIZED_CHANNEL_ID, BOSS_CONFIG
|
||||
from utils.helpers import format_damage_display, format_date_only
|
||||
from utils.pb_handler import db_manager # Assurez-vous que db_manager est initialisé correctement
|
||||
from utils.pb_handler import db_manager # Make sure db_manager is initialized correctly
|
||||
|
||||
class MyStats(commands.Cog):
|
||||
"""Cog pour afficher tous les PB d'un utilisateur"""
|
||||
"""Cog for displaying all PBs for a user"""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
@commands.command(name="mystats")
|
||||
async def mystats(self, ctx, *, target_user: str = None):
|
||||
"""Affiche tous les PB d'un utilisateur avec les nouvelles difficultés"""
|
||||
"""Shows all PBs for a user across all difficulties"""
|
||||
if ctx.channel.id != AUTHORIZED_CHANNEL_ID:
|
||||
return
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ class MyStats(commands.Cog):
|
|||
color=0x00bfff
|
||||
)
|
||||
|
||||
# Hydra - toutes les difficultés
|
||||
# Hydra - all difficulties
|
||||
hydra_stats = []
|
||||
for difficulty in BOSS_CONFIG['hydra']['difficulties']:
|
||||
pb_key = f'pb_hydra_{difficulty}'
|
||||
|
|
@ -57,7 +57,7 @@ class MyStats(commands.Cog):
|
|||
hydra_text = "\n".join(hydra_stats) if hydra_stats else "No records"
|
||||
embed.add_field(name="⚔️ Hydra PBs", value=hydra_text, inline=False)
|
||||
|
||||
# Chimera - toutes les difficultés
|
||||
# Chimera - all difficulties
|
||||
chimera_stats = []
|
||||
for difficulty in BOSS_CONFIG['chimera']['difficulties']:
|
||||
pb_key = f'pb_chimera_{difficulty}'
|
||||
|
|
@ -83,7 +83,7 @@ class MyStats(commands.Cog):
|
|||
cvc_text += f" • {formatted_date}"
|
||||
embed.add_field(name="🗡️ CvC PB", value=cvc_text, inline=False)
|
||||
|
||||
# Total combiné
|
||||
# Combined total
|
||||
total_damage = sum(user_data.get(f'pb_hydra_{d}', 0) for d in BOSS_CONFIG['hydra']['difficulties'])
|
||||
total_damage += sum(user_data.get(f'pb_chimera_{d}', 0) for d in BOSS_CONFIG['chimera']['difficulties'])
|
||||
total_damage += user_data.get('pb_cvc', 0)
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ from discord.ext import commands
|
|||
from utils.pb_handler import handle_pb_command
|
||||
|
||||
class Pbchimera(commands.Cog):
|
||||
"""Cog pour gérer les Personal Bests Chimera"""
|
||||
"""Cog for managing Chimera Personal Bests"""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
@commands.command(name="pbchimera")
|
||||
async def pbchimera(self, ctx, arg1: str = None, *, arg2: str = None):
|
||||
"""Commande !pbchimera avec gestion des difficultés"""
|
||||
"""!pbchimera command with difficulty handling"""
|
||||
await handle_pb_command(ctx, 'chimera', arg1, arg2)
|
||||
|
||||
async def setup(bot):
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ from discord.ext import commands
|
|||
from utils.pb_handler import handle_pb_command
|
||||
|
||||
class Pbcvc(commands.Cog):
|
||||
"""Cog pour gérer les Personal Bests CvC (sans difficultés)"""
|
||||
"""Cog for managing CvC Personal Bests (no difficulties)"""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
@commands.command(name="pbcvc")
|
||||
async def pbcvc(self, ctx, *, target_user: str = None):
|
||||
"""Commande !pbcvc"""
|
||||
"""!pbcvc command"""
|
||||
await handle_pb_command(ctx, 'cvc', target_user)
|
||||
|
||||
async def setup(bot):
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ from discord.ext import commands
|
|||
from utils.pb_handler import handle_pb_command
|
||||
|
||||
class Pbhydra(commands.Cog):
|
||||
"""Cog pour gérer les Personal Bests Hydra"""
|
||||
"""Cog for managing Hydra Personal Bests"""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
@commands.command(name="pbhydra")
|
||||
async def pbhydra(self, ctx, arg1: str = None, *, arg2: str = None):
|
||||
"""Commande !pbhydra avec gestion des difficultés"""
|
||||
"""!pbhydra command with difficulty handling"""
|
||||
await handle_pb_command(ctx, 'hydra', arg1, arg2)
|
||||
|
||||
async def setup(bot):
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ from utils.helpers import normalize_difficulty
|
|||
from config import BOSS_CONFIG
|
||||
|
||||
class Top10(commands.Cog):
|
||||
"""Cog regroupant toutes les commandes de leaderboard globales et par clan"""
|
||||
"""Cog grouping all global and per-clan leaderboard commands"""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
# --- Commandes globales ---
|
||||
# --- Global commands ---
|
||||
@commands.command()
|
||||
async def top10hydra(self, ctx, difficulty: str = None):
|
||||
if difficulty and normalize_difficulty(difficulty) in BOSS_CONFIG['hydra']['difficulties']:
|
||||
|
|
@ -32,7 +32,7 @@ class Top10(commands.Cog):
|
|||
async def top10cvc(self, ctx):
|
||||
await show_leaderboard(ctx, 'cvc')
|
||||
|
||||
# --- Commandes par clan TEAI (Inferno) ---
|
||||
# --- TEAI clan commands (Inferno) ---
|
||||
@commands.command()
|
||||
async def teaihydra(self, ctx, difficulty: str = None):
|
||||
await self._show_clan_leaderboard(ctx, 'hydra', difficulty, 'TEAI')
|
||||
|
|
@ -45,7 +45,7 @@ class Top10(commands.Cog):
|
|||
async def teaicvc(self, ctx):
|
||||
await show_leaderboard(ctx, 'cvc', clan='TEAI')
|
||||
|
||||
# --- Commandes par clan TEAF (Flame) ---
|
||||
# --- TEAF clan commands (Flame) ---
|
||||
@commands.command()
|
||||
async def teafhydra(self, ctx, difficulty: str = None):
|
||||
await self._show_clan_leaderboard(ctx, 'hydra', difficulty, 'TEAF')
|
||||
|
|
@ -58,7 +58,7 @@ class Top10(commands.Cog):
|
|||
async def teafcvc(self, ctx):
|
||||
await show_leaderboard(ctx, 'cvc', clan='TEAF')
|
||||
|
||||
# --- Commandes par clan TEAC (Cinder) ---
|
||||
# --- TEAC clan commands (Cinder) ---
|
||||
@commands.command()
|
||||
async def teachydra(self, ctx, difficulty: str = None):
|
||||
await self._show_clan_leaderboard(ctx, 'hydra', difficulty, 'TEAC')
|
||||
|
|
@ -71,7 +71,7 @@ class Top10(commands.Cog):
|
|||
async def teaccvc(self, ctx):
|
||||
await show_leaderboard(ctx, 'cvc', clan='TEAC')
|
||||
|
||||
# --- Commandes par clan TEACO (Corrupted Olympians) ---
|
||||
# --- TEACO clan commands (Corrupted Olympians) ---
|
||||
@commands.command()
|
||||
async def teacohydra(self, ctx, difficulty: str = None):
|
||||
await self._show_clan_leaderboard(ctx, 'hydra', difficulty, 'TEACO')
|
||||
|
|
@ -84,12 +84,12 @@ class Top10(commands.Cog):
|
|||
async def teacocvc(self, ctx):
|
||||
await show_leaderboard(ctx, 'cvc', clan='TEACO')
|
||||
|
||||
# --- Méthode interne pour éviter la répétition ---
|
||||
# --- Internal helper method ---
|
||||
async def _show_clan_leaderboard(self, ctx, boss_type, difficulty, clan):
|
||||
"""Affiche le leaderboard pour un boss et un clan spécifique"""
|
||||
"""Shows the leaderboard for a specific boss and clan"""
|
||||
if difficulty and normalize_difficulty(difficulty) in BOSS_CONFIG[boss_type]['difficulties']:
|
||||
await show_leaderboard(ctx, boss_type, difficulty, clan)
|
||||
elif boss_type != 'cvc': # CvC n’a pas de difficultés
|
||||
elif boss_type != 'cvc': # CvC has no difficulties
|
||||
difficulties = " | ".join(BOSS_CONFIG[boss_type]['difficulties'])
|
||||
await ctx.send(
|
||||
f"❌ Please specify difficulty: `!{ctx.command.name} <difficulty>`\n"
|
||||
|
|
|
|||
14
config.py
14
config.py
|
|
@ -4,15 +4,15 @@ from dotenv import load_dotenv
|
|||
|
||||
load_dotenv()
|
||||
|
||||
# Token et channel autorisé
|
||||
# Token and authorized channel
|
||||
DISCORD_TOKEN = os.getenv("DISCORD_TOKEN")
|
||||
AUTHORIZED_CHANNEL_ID = int(os.getenv("AUTHORIZED_CHANNEL_ID"))
|
||||
|
||||
# Chemins
|
||||
# Paths
|
||||
SCREENSHOTS_BASE_PATH = "/app/screenshots"
|
||||
DATABASE_PATH = "/app/data/bot_data.db"
|
||||
|
||||
# Configuration des clans TEA - The Ember Accord
|
||||
# TEA clan configuration - The Ember Accord
|
||||
CLAN_CONFIG = {
|
||||
'TEAI': {'name': 'TEAI', 'full_name': 'Inferno', 'emoji': '🔥', 'color': 0xff4500},
|
||||
'TEAF': {'name': 'TEAF', 'full_name': 'Flame', 'emoji': '🛡️', 'color': 0x00ff00},
|
||||
|
|
@ -20,7 +20,7 @@ CLAN_CONFIG = {
|
|||
'TEACO': {'name': 'TEACO', 'full_name': 'Corrupted Olympians', 'emoji': '👑', 'color': 0x9932cc},
|
||||
}
|
||||
|
||||
# Mapping role Discord ID → clé de clan
|
||||
# Discord role ID to clan key mapping
|
||||
CLAN_ROLE_IDS = {
|
||||
1190674529731747901: 'TEAI',
|
||||
1197646966599983185: 'TEAF',
|
||||
|
|
@ -28,14 +28,14 @@ CLAN_ROLE_IDS = {
|
|||
1496965820868198550: 'TEACO',
|
||||
}
|
||||
|
||||
# Mapping anciens clans → nouveaux (migration base existante)
|
||||
# Old clan to new clan mapping (existing database migration)
|
||||
CLAN_MIGRATION = {
|
||||
'RTF': 'TEAI',
|
||||
'RTFC': 'TEAF',
|
||||
'RTFR': 'TEAC',
|
||||
}
|
||||
|
||||
# Configuration des boss avec difficultés
|
||||
# Boss configuration with difficulties
|
||||
BOSS_CONFIG = {
|
||||
'hydra': {'name': 'Hydra', 'emoji': '📍', 'color': 0xff6b35,
|
||||
'difficulties': ['normal', 'hard', 'brutal', 'nightmare']},
|
||||
|
|
@ -44,7 +44,7 @@ BOSS_CONFIG = {
|
|||
'cvc': {'name': 'Clan vs Clan', 'emoji': '✔️', 'color': 0xff0000, 'difficulties': []}
|
||||
}
|
||||
|
||||
# Mappings pour diminutifs de difficultés
|
||||
# Difficulty shortcut mappings
|
||||
DIFFICULTY_SHORTCUTS = {
|
||||
'nm': 'nightmare',
|
||||
'unm': 'ultra'
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ class DatabaseManager:
|
|||
self.init_database()
|
||||
|
||||
def init_database(self):
|
||||
"""Initialise la base de données avec les nouvelles colonnes pour les difficultés"""
|
||||
"""Initializes the database with all columns for difficulties"""
|
||||
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Table principale avec toutes les difficultés
|
||||
# Main table with all difficulties
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
|
@ -54,7 +54,7 @@ class DatabaseManager:
|
|||
pb_chimera_ultra_screenshot TEXT,
|
||||
pb_chimera_ultra_date TIMESTAMP,
|
||||
|
||||
-- CvC (inchangé)
|
||||
-- CvC (unchanged)
|
||||
pb_cvc INTEGER DEFAULT 0,
|
||||
pb_cvc_screenshot TEXT,
|
||||
pb_cvc_date TIMESTAMP,
|
||||
|
|
@ -64,7 +64,7 @@ class DatabaseManager:
|
|||
)
|
||||
''')
|
||||
|
||||
# Migration des données existantes (si nécessaire)
|
||||
# Existing data migration (if needed)
|
||||
cursor.execute("PRAGMA table_info(users)")
|
||||
columns = [row[1] for row in cursor.fetchall()]
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ class DatabaseManager:
|
|||
|
||||
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
|
||||
# Auto-migration: derive clan from old username prefix
|
||||
for old_tag, new_clan in CLAN_MIGRATION.items():
|
||||
cursor.execute(
|
||||
"UPDATE users SET clan = ? WHERE clan IS NULL AND ("
|
||||
|
|
@ -81,7 +81,7 @@ class DatabaseManager:
|
|||
(new_clan, f'[{old_tag}] %', f'[{old_tag}]%')
|
||||
)
|
||||
|
||||
# Table pour l'historique global
|
||||
# Global history table
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS pb_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
|
@ -99,7 +99,7 @@ class DatabaseManager:
|
|||
conn.close()
|
||||
|
||||
def get_user_pb(self, user_id, boss_type, difficulty=None):
|
||||
"""Récupère le PB d'un utilisateur pour un boss et difficulté spécifique"""
|
||||
"""Returns PB for a user on a specific boss and difficulty"""
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
|
|
@ -118,11 +118,11 @@ class DatabaseManager:
|
|||
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):
|
||||
"""Met à jour le PB d'un utilisateur et supprime l'ancien screenshot"""
|
||||
"""Updates a user's PB and deletes the previous screenshot"""
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Récupérer l'ancien screenshot pour le supprimer
|
||||
# Get old screenshot for deletion
|
||||
old_data = self.get_user_pb(user_id, boss_type, difficulty)
|
||||
old_screenshot = old_data[1] if old_data else None
|
||||
|
||||
|
|
@ -131,7 +131,7 @@ class DatabaseManager:
|
|||
else:
|
||||
column_prefix = f"pb_{boss_type}"
|
||||
|
||||
# COALESCE(?, clan) : on n'écrase pas le clan existant si la détection retourne None
|
||||
# COALESCE(?, clan): keeps existing clan if detection returns None
|
||||
cursor.execute(f'''
|
||||
INSERT INTO users (discord_id, discord_username, clan, {column_prefix}, {column_prefix}_screenshot, {column_prefix}_date, total_attempts)
|
||||
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP, 1)
|
||||
|
|
@ -145,7 +145,7 @@ class DatabaseManager:
|
|||
total_attempts = total_attempts + 1
|
||||
''', (str(user_id), username, clan, damage, screenshot_filename, username, clan, damage, screenshot_filename))
|
||||
|
||||
# Ajouter à l'historique
|
||||
# Add to history
|
||||
cursor.execute('''
|
||||
INSERT INTO pb_history (discord_id, username, boss_type, difficulty, damage, screenshot_filename)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
|
|
@ -157,7 +157,7 @@ class DatabaseManager:
|
|||
return old_screenshot
|
||||
|
||||
def get_leaderboard(self, boss_type, difficulty=None, limit=10, clan=None):
|
||||
"""Récupère le classement pour un boss et difficulté spécifique"""
|
||||
"""Returns the leaderboard for a specific boss and difficulty"""
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
|
|
@ -186,11 +186,11 @@ class DatabaseManager:
|
|||
return results
|
||||
|
||||
def get_user_all_pbs(self, user_id):
|
||||
"""Récupère tous les PB d'un utilisateur"""
|
||||
"""Returns all PBs for a user"""
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Récupérer toutes les colonnes de PB
|
||||
# Retrieve all PB columns
|
||||
cursor.execute('SELECT * FROM users WHERE discord_id = ?', (str(user_id),))
|
||||
result = cursor.fetchone()
|
||||
columns = [desc[0] for desc in cursor.description]
|
||||
|
|
@ -202,7 +202,7 @@ class DatabaseManager:
|
|||
return dict(zip(columns, result))
|
||||
|
||||
def find_user_by_name(self, username):
|
||||
"""Trouve un utilisateur par son nom (pour rétrocompatibilité)"""
|
||||
"""Finds a user by name (for backwards compatibility)"""
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import sqlite3
|
|||
from datetime import datetime
|
||||
from config import DATABASE_PATH
|
||||
|
||||
# Règles de mercy pour stockage
|
||||
# Mercy rules for storage
|
||||
MERCY_RULES = {
|
||||
"ancient": {"threshold": 200, "increment": 0.5, "base": 0},
|
||||
"void": {"threshold": 200, "increment": 0.5, "base": 0},
|
||||
|
|
@ -19,7 +19,7 @@ class MercyManager:
|
|||
self.init_table()
|
||||
|
||||
def init_table(self):
|
||||
"""Initialise la table des compteurs de mercy"""
|
||||
"""Initializes the mercy counters table"""
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
|
|
@ -35,7 +35,7 @@ class MercyManager:
|
|||
conn.close()
|
||||
|
||||
def get_pulls(self, user_id, shard_type):
|
||||
"""Retourne le nombre de pulls actuels pour un utilisateur et un type de shard"""
|
||||
"""Returns current pull count for a user and shard type"""
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
|
|
@ -47,11 +47,11 @@ class MercyManager:
|
|||
return row[0] if row else 0
|
||||
|
||||
def add_pulls(self, user_id, shard_type, pulls):
|
||||
"""Ajoute des pulls pour un utilisateur en gérant correctement l'INSERT/UPDATE"""
|
||||
"""Adds pulls for a user, handling INSERT/UPDATE correctly"""
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Vérifie si l'enregistrement existe
|
||||
# Check if record exists
|
||||
cursor.execute(
|
||||
"SELECT pulls FROM mercy_counters WHERE user_id = ? AND shard_type = ?",
|
||||
(user_id, shard_type)
|
||||
|
|
@ -76,7 +76,7 @@ class MercyManager:
|
|||
return new_pulls
|
||||
|
||||
def reset_pulls(self, user_id, shard_type):
|
||||
"""Réinitialise les pulls d'un utilisateur pour un shard"""
|
||||
"""Resets pull count for a user on a shard"""
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
|
|
@ -87,7 +87,7 @@ class MercyManager:
|
|||
conn.close()
|
||||
|
||||
def get_all_pulls(self, user_id):
|
||||
"""Retourne tous les pulls d'un utilisateur pour tous les shards"""
|
||||
"""Returns all pull counts for a user across all shards"""
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
|
|
@ -99,14 +99,14 @@ class MercyManager:
|
|||
return {shard_type: pulls for shard_type, pulls in rows}
|
||||
|
||||
def get_mercy_chance(self, shard_type, pulls):
|
||||
"""Calcule la probabilité de mercy selon le nombre de pulls"""
|
||||
"""Calculates mercy probability based on pull count"""
|
||||
rule = MERCY_RULES[shard_type]
|
||||
if pulls <= rule["threshold"]:
|
||||
return rule["base"]
|
||||
return rule["base"] + (pulls - rule["threshold"]) * rule["increment"]
|
||||
|
||||
def pulls_until_guaranteed(self, shard_type, pulls):
|
||||
"""Retourne combien de pulls restent avant un loot garanti"""
|
||||
"""Returns pulls remaining until guaranteed loot"""
|
||||
rules = {
|
||||
"ancient": {"start": 200, "increment": 5, "base": 0.5},
|
||||
"void": {"start": 200, "increment": 5, "base": 0.5},
|
||||
|
|
|
|||
|
|
@ -7,18 +7,18 @@ from config import SCREENSHOTS_BASE_PATH, BOSS_CONFIG
|
|||
class ScreenshotManager:
|
||||
def __init__(self, base_path=SCREENSHOTS_BASE_PATH):
|
||||
self.base_path = base_path
|
||||
# Créer les dossiers pour chaque boss et difficulté
|
||||
# Create directories for each boss and difficulty
|
||||
for boss_type in BOSS_CONFIG.keys():
|
||||
boss_path = os.path.join(base_path, boss_type)
|
||||
os.makedirs(boss_path, exist_ok=True)
|
||||
|
||||
# Créer sous-dossiers pour les difficultés
|
||||
# Create subdirectories for difficulties
|
||||
for difficulty in BOSS_CONFIG[boss_type]['difficulties']:
|
||||
difficulty_path = os.path.join(boss_path, difficulty)
|
||||
os.makedirs(difficulty_path, exist_ok=True)
|
||||
|
||||
async def save_screenshot(self, attachment, username, damage, boss_type, difficulty=None):
|
||||
"""Sauvegarde le screenshot localement"""
|
||||
"""Saves the screenshot locally"""
|
||||
try:
|
||||
timestamp = int(datetime.now().timestamp())
|
||||
file_extension = attachment.filename.split('.')[-1].lower()
|
||||
|
|
@ -34,18 +34,18 @@ class ScreenshotManager:
|
|||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(attachment.url) as resp:
|
||||
if resp.status == 200:
|
||||
# Ouverture en binaire, pas de problème d'encodage
|
||||
# Binary mode, no encoding issues
|
||||
with open(filepath, 'wb') as f:
|
||||
f.write(await resp.read())
|
||||
return filename
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"Erreur sauvegarde screenshot: {str(e)}")
|
||||
print(f"Screenshot save error: {str(e)}")
|
||||
return None
|
||||
|
||||
def get_screenshot_path(self, filename, boss_type, difficulty=None):
|
||||
"""Retourne le chemin complet du screenshot"""
|
||||
"""Returns the full path to the screenshot"""
|
||||
if filename:
|
||||
if difficulty:
|
||||
return os.path.join(self.base_path, boss_type, difficulty, filename)
|
||||
|
|
@ -54,12 +54,12 @@ class ScreenshotManager:
|
|||
return None
|
||||
|
||||
def delete_old_screenshot(self, filename, boss_type, difficulty=None):
|
||||
"""Supprime l'ancien screenshot"""
|
||||
"""Deletes the old screenshot"""
|
||||
if filename:
|
||||
old_path = self.get_screenshot_path(filename, boss_type, difficulty)
|
||||
if old_path and os.path.exists(old_path):
|
||||
try:
|
||||
os.remove(old_path)
|
||||
print(f"Ancien screenshot supprimé: {filename}")
|
||||
print(f"Old screenshot deleted: {filename}")
|
||||
except Exception as e:
|
||||
print(f"Erreur suppression screenshot: {str(e)}")
|
||||
print(f"Screenshot deletion error: {str(e)}")
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from typing import Optional
|
|||
from config import AUTHORIZED_CHANNEL_ID, DIFFICULTY_SHORTCUTS, CLAN_ROLE_IDS
|
||||
|
||||
def parse_damage_amount(damage_str):
|
||||
"""Convertit les montants avec suffixes (K, M, B) en nombres entiers"""
|
||||
"""Converts amounts with suffixes (K, M, B) to integers"""
|
||||
if not damage_str:
|
||||
return None
|
||||
damage_str = damage_str.strip().upper()
|
||||
|
|
@ -23,7 +23,7 @@ def parse_damage_amount(damage_str):
|
|||
return int(number * multipliers[suffix])
|
||||
|
||||
def format_damage_display(damage):
|
||||
"""Formate un montant de dégâts avec le suffixe approprié"""
|
||||
"""Formats a damage amount with the appropriate suffix"""
|
||||
if damage >= 1_000_000_000:
|
||||
billions = damage / 1_000_000_000
|
||||
return f"{int(billions)}B" if billions == int(billions) else f"{billions:.1f}B"
|
||||
|
|
@ -36,7 +36,7 @@ def format_damage_display(damage):
|
|||
return str(damage)
|
||||
|
||||
def normalize_difficulty(difficulty):
|
||||
"""Normalise une difficulté en gérant les diminutifs"""
|
||||
"""Normalizes a difficulty string, handling shortcuts"""
|
||||
if not difficulty:
|
||||
return None
|
||||
difficulty_lower = difficulty.lower()
|
||||
|
|
@ -45,14 +45,14 @@ def normalize_difficulty(difficulty):
|
|||
return difficulty_lower
|
||||
|
||||
def get_clan_from_member(member) -> Optional[str]:
|
||||
"""Détecte le clan d'un membre via ses rôles Discord"""
|
||||
"""Detects a member's clan via their Discord roles"""
|
||||
for role in member.roles:
|
||||
if role.id in CLAN_ROLE_IDS:
|
||||
return CLAN_ROLE_IDS[role.id]
|
||||
return None
|
||||
|
||||
def format_datetime(date_str):
|
||||
"""Formate une date en format AM/PM"""
|
||||
"""Formats a date in AM/PM format"""
|
||||
if not date_str:
|
||||
return None
|
||||
try:
|
||||
|
|
@ -62,7 +62,7 @@ def format_datetime(date_str):
|
|||
return None
|
||||
|
||||
def format_date_only(date_str):
|
||||
"""Formate une date sans l'heure"""
|
||||
"""Formats a date without the time component"""
|
||||
if not date_str:
|
||||
return None
|
||||
try:
|
||||
|
|
@ -72,7 +72,7 @@ def format_date_only(date_str):
|
|||
return None
|
||||
|
||||
def get_difficulty_display_name(difficulty):
|
||||
"""Convertit le nom de difficulté en nom d'affichage"""
|
||||
"""Converts an internal difficulty name to display name"""
|
||||
difficulty_names = {
|
||||
'ultra': 'Ultra Nightmare',
|
||||
'nightmare': 'Nightmare',
|
||||
|
|
@ -96,7 +96,7 @@ MERCY_RULES = {
|
|||
}
|
||||
|
||||
def calc_chance_and_guarantee(shard_type, pulls):
|
||||
"""Retourne chance, pull garanti et pulls restants"""
|
||||
"""Returns chance, guaranteed pull count, and remaining pulls"""
|
||||
if shard_type not in MERCY_RULES:
|
||||
return 0, None, None
|
||||
rule = MERCY_RULES[shard_type]
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ def set_db_manager(db):
|
|||
db_manager = db
|
||||
|
||||
async def show_leaderboard(ctx, boss_type, difficulty=None, clan=None):
|
||||
"""Fonction générique pour afficher les classements"""
|
||||
"""Generic function to display leaderboards"""
|
||||
if ctx.channel.id != AUTHORIZED_CHANNEL_ID:
|
||||
return
|
||||
|
||||
try:
|
||||
# Normaliser la difficulté si spécifiée
|
||||
# Normalize difficulty if specified
|
||||
if difficulty:
|
||||
difficulty = normalize_difficulty(difficulty)
|
||||
if difficulty not in BOSS_CONFIG[boss_type]['difficulties']:
|
||||
|
|
@ -34,7 +34,7 @@ async def show_leaderboard(ctx, boss_type, difficulty=None, clan=None):
|
|||
await ctx.send(f"⚠️ No{difficulty_text} {boss_info['name']} records found{clan_text} yet!")
|
||||
return
|
||||
|
||||
# Titre avec clan et difficulté si spécifiés
|
||||
# Title with clan and difficulty if specified
|
||||
difficulty_name = get_difficulty_display_name(difficulty) if difficulty else ""
|
||||
title = f"🏆 {difficulty_name} {boss_info['name']} Leaderboard - Top 10"
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ async def show_leaderboard(ctx, boss_type, difficulty=None, clan=None):
|
|||
if formatted_date:
|
||||
date_text = f" • {formatted_date}"
|
||||
|
||||
# Afficher l'emoji du clan si le leaderboard est global (pas filtré par clan)
|
||||
# Show clan emoji if leaderboard is global (not filtered by clan)
|
||||
display_name = username
|
||||
if not clan and row_clan:
|
||||
clan_emoji = CLAN_CONFIG.get(row_clan, {'emoji': '🏛️'})['emoji']
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@ db_manager = None
|
|||
screenshot_manager = None
|
||||
|
||||
def set_managers(db, ss):
|
||||
"""Injection des managers (appelée une seule fois depuis bot.py)"""
|
||||
"""Injects managers (called once from bot.py)"""
|
||||
global db_manager, screenshot_manager
|
||||
db_manager = db
|
||||
screenshot_manager = ss
|
||||
|
||||
async def handle_pb_command(ctx, boss_type, arg1=None, arg2=None):
|
||||
"""Fonction générique pour gérer toutes les commandes PB avec difficultés"""
|
||||
"""Generic handler for all PB commands"""
|
||||
if ctx.channel.id != AUTHORIZED_CHANNEL_ID:
|
||||
return
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ async def handle_pb_command(ctx, boss_type, arg1=None, arg2=None):
|
|||
difficulties = boss_info['difficulties']
|
||||
|
||||
try:
|
||||
# Pour CvC (pas de difficultés)
|
||||
# For CvC (no difficulties)
|
||||
if not difficulties:
|
||||
if arg1:
|
||||
damage = parse_damage_amount(arg1)
|
||||
|
|
@ -41,7 +41,7 @@ async def handle_pb_command(ctx, boss_type, arg1=None, arg2=None):
|
|||
await show_user_pb(ctx, boss_type, None, ctx.author.display_name)
|
||||
return
|
||||
|
||||
# Pour Hydra et Chimera (avec difficultés)
|
||||
# For Hydra and Chimera (with difficulties)
|
||||
if not arg1:
|
||||
difficulty_list = " | ".join([d.title() for d in difficulties])
|
||||
await ctx.send(
|
||||
|
|
@ -81,7 +81,7 @@ async def handle_pb_command(ctx, boss_type, arg1=None, arg2=None):
|
|||
await ctx.send(f"⚠️ Error: {str(e)}")
|
||||
|
||||
async def handle_pb_submission(ctx, boss_type, difficulty, damage):
|
||||
"""Gère la soumission d'un nouveau PB"""
|
||||
"""Handles submission of a new PB"""
|
||||
if not ctx.message.attachments:
|
||||
await ctx.send("⚠️ Please attach a screenshot to validate your PB!")
|
||||
return
|
||||
|
|
@ -120,7 +120,7 @@ async def handle_pb_submission(ctx, boss_type, difficulty, damage):
|
|||
)
|
||||
embed.add_field(name="📈 Improvement", value=f"+{format_damage_display(improvement)} damage", inline=True)
|
||||
|
||||
# Envoi du screenshot correctement pour Discord
|
||||
# Send screenshot to Discord
|
||||
screenshot_path = screenshot_manager.get_screenshot_path(screenshot_filename, boss_type, difficulty)
|
||||
if screenshot_path and os.path.exists(screenshot_path):
|
||||
file = discord.File(screenshot_path, filename=screenshot_filename)
|
||||
|
|
@ -131,19 +131,19 @@ async def handle_pb_submission(ctx, boss_type, difficulty, damage):
|
|||
else:
|
||||
await ctx.send("⚠️ Failed to save screenshot. Please try again.")
|
||||
else:
|
||||
# Si le PB n'est pas battu, on montre le PB existant
|
||||
# PB not beaten, show current PB
|
||||
await show_user_pb(ctx, boss_type, difficulty, username)
|
||||
|
||||
async def show_user_pb(ctx, boss_type, difficulty, target_user):
|
||||
"""Affiche le PB actuel d'un utilisateur"""
|
||||
# Si target_user est un nom d'utilisateur, on essaie de le trouver
|
||||
"""Displays the current PB for a user"""
|
||||
# If target_user is a username, try to find them
|
||||
if isinstance(target_user, str) and not target_user.isdigit():
|
||||
# D'abord, vérifier si c'est l'utilisateur actuel
|
||||
# First check if it's the current user
|
||||
if target_user.lower() == ctx.author.display_name.lower():
|
||||
user_id = ctx.author.id
|
||||
display_name = ctx.author.display_name
|
||||
else:
|
||||
# Chercher dans la base de données
|
||||
# Search in database
|
||||
matches = db_manager.find_user_by_name(target_user)
|
||||
if not matches:
|
||||
await ctx.send(f"⚠️ User **{target_user}** not found in database.")
|
||||
|
|
@ -154,7 +154,7 @@ async def show_user_pb(ctx, boss_type, difficulty, target_user):
|
|||
else:
|
||||
user_id, display_name = matches[0]
|
||||
else:
|
||||
# Si c'est l'utilisateur actuel
|
||||
# Current user
|
||||
user_id = ctx.author.id
|
||||
display_name = ctx.author.display_name
|
||||
|
||||
|
|
@ -171,7 +171,7 @@ async def show_user_pb(ctx, boss_type, difficulty, target_user):
|
|||
if date:
|
||||
embed.add_field(name="📅 Date", value=format_datetime(date), inline=True)
|
||||
|
||||
# Envoi du screenshot local correctement
|
||||
# Send local screenshot to Discord
|
||||
if screenshot:
|
||||
screenshot_path = screenshot_manager.get_screenshot_path(screenshot, boss_type, difficulty)
|
||||
if screenshot_path and os.path.exists(screenshot_path):
|
||||
|
|
|
|||
Loading…
Reference in a new issue