Compare commits

...

17 commits
main ... dev

Author SHA1 Message Date
LE BERRE Mickael
0e526a40bc docs: update CLAUDE.md community description to TEA rebrand
All checks were successful
Deploy Bot on NAS / deploy (push) Successful in 30s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 16:27:11 +02:00
LE BERRE Mickael
64b60f1597 docs: explicit comment on StrictHostKeyChecking=no in deploy workflow
All checks were successful
Deploy Bot on NAS / deploy (push) Successful in 34s
Runner Alpine stateless, pas de known_hosts persistant.
Cible fixe sur LAN interne (192.168.1.208) — risque MITM inexistant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 16:17:56 +02:00
LE BERRE Mickael
e364f354b0 fix: mystats username lookup and multi-word name support
All checks were successful
Deploy Bot on NAS / deploy (push) Successful in 25s
- Résolution du nom via find_user_by_name avant get_user_all_pbs
  (l'ancien code passait le nom en guise de discord_id → aucun résultat)
- Affichage du nom de la cible dans le titre de l'embed
- * pour capturer les noms avec espaces

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 11:56:28 +02:00
LE BERRE Mickael
314483e4f5 fix: support multi-word usernames in pb lookup commands
Sans le *, discord.py tronquait les noms avec espaces au premier mot.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 11:52:04 +02:00
LE BERRE Mickael
1a37ec8838 fix: set dev role IDs for TEA clans + fix leading-zero syntax error
All checks were successful
Deploy Bot on NAS / deploy (push) Successful in 12s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 11:01:35 +02:00
LE BERRE Mickael
0d744c8cdc chore: placeholder role IDs for dev environment
All checks were successful
Deploy Bot on NAS / deploy (push) Successful in 25s
IDs serveur dev à renseigner avant déploiement.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 10:51:59 +02:00
LE BERRE Mickael
4ecac026b5 fix: replace str | None with Optional[str] for Python 3.9 compat
str | None union syntax requires Python 3.10+, bot runs on 3.9-slim.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 10:51:26 +02:00
LE BERRE Mickael
85942723ab feat: rebrand RTF→TEA, clan detection via Discord roles
Replaces display-name prefix parsing ([RTF]/[RTFC]/[RTFR]) with
Discord role-based clan detection (TEAI/TEAF/TEAC/TEACO).

- config: new CLAN_CONFIG with 4 TEA clans, CLAN_ROLE_IDS, CLAN_MIGRATION
- helpers: get_user_clan() replaced by get_clan_from_member()
- DatabaseManager: adds clan column on startup, auto-migrates existing
  records from old username prefixes, filters leaderboard by clan column
- pb_handler: detects clan from roles on submission, passes it to DB
- leaderboard_handler: reads clan from DB column instead of username
- top10: new commands !teai*/!teaf*/!teac*/!teaco* (removes !rtf*)
- guide: updated command list and bot title

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 10:51:26 +02:00
ec86776e42 fix: remove container_name conflict between dev and prod, drop obsolete version
All checks were successful
Deploy Bot on NAS / deploy (push) Successful in 17s
2026-04-30 16:30:37 +02:00
01ef1cadcf fix: remove --build, code is volume-mounted
Some checks failed
Deploy Bot on NAS / deploy (push) Failing after 10s
2026-04-30 16:27:11 +02:00
14887625a2 fix: use full docker path for QNAP Container Station
Some checks failed
Deploy Bot on NAS / deploy (push) Failing after 17s
2026-04-30 16:22:25 +02:00
f4dbfba223 ci: re-trigger pipeline
Some checks failed
Deploy Bot on NAS / deploy (push) Failing after 2s
2026-04-30 16:19:26 +02:00
bccf437192 fix: use absolute path /root/.ssh/id_deploy in CI
Some checks failed
Deploy Bot on NAS / deploy (push) Has been cancelled
2026-04-30 16:15:39 +02:00
cd81d299ab swith do ip
Some checks failed
Deploy Bot on NAS / deploy (push) Has been cancelled
2026-04-30 16:12:11 +02:00
e4a38d7853 fix: install rsync et openssh dans le job (runner Alpine)
Some checks failed
Deploy Bot on NAS / deploy (push) Failing after 2s
2026-04-30 16:02:19 +02:00
cdc36dd42b test: trigger deploy pipeline
Some checks failed
Deploy Bot on NAS / deploy (push) Failing after 1s
2026-04-30 16:00:44 +02:00
8932696709 fix: remplace actions/checkout par git clone natif (pas de node.js)
Some checks failed
Deploy Bot on NAS / deploy (push) Failing after 1s
2026-04-30 15:54:16 +02:00
15 changed files with 160 additions and 111 deletions

View file

@ -8,8 +8,13 @@ jobs:
deploy: deploy:
runs-on: self-hosted runs-on: self-hosted
steps: steps:
- name: Install dependencies
run: apk add --no-cache rsync openssh-client
- name: Checkout - name: Checkout
uses: actions/checkout@v4 run: |
git clone --depth 1 --branch ${{ github.ref_name }} \
${{ github.server_url }}/${{ github.repository }}.git .
- name: Set deployment path - name: Set deployment path
run: | run: |
@ -23,29 +28,27 @@ jobs:
- name: Configure SSH - name: Configure SSH
run: | run: |
mkdir -p ~/.ssh mkdir -p /root/.ssh
echo "${{ secrets.NAS_SSH_KEY }}" > ~/.ssh/id_deploy echo "${{ secrets.NAS_SSH_KEY }}" > /root/.ssh/id_deploy
chmod 600 ~/.ssh/id_deploy chmod 600 /root/.ssh/id_deploy
cat >> ~/.ssh/config << 'EOF'
Host nas
HostName 192.168.1.208
User Elewyn
IdentityFile ~/.ssh/id_deploy
StrictHostKeyChecking no
EOF
- name: Sync files to NAS - name: Sync files to NAS
run: | run: |
# StrictHostKeyChecking=no : runner Alpine stateless, pas de known_hosts persistant.
# Cible fixe sur LAN interne (192.168.1.208) — risque MITM inexistant.
rsync -av --delete \ rsync -av --delete \
-e "ssh -i /root/.ssh/id_deploy -o StrictHostKeyChecking=no" \
--exclude='.git' \ --exclude='.git' \
--exclude='.env' \ --exclude='.env' \
--exclude='data/' \ --exclude='data/' \
--exclude='screenshots/' \ --exclude='screenshots/' \
--exclude='logs/' \ --exclude='logs/' \
./ nas:${{ env.DEPLOY_PATH }}/ ./ Elewyn@192.168.1.208:${{ env.DEPLOY_PATH }}/
- name: Restart bot on NAS - name: Restart bot on NAS
run: | run: |
ssh nas "cd ${{ env.DEPLOY_PATH }} && \ # StrictHostKeyChecking=no : runner Alpine stateless, pas de known_hosts persistant.
docker compose down || true && \ # Cible fixe sur LAN interne (192.168.1.208) — risque MITM inexistant.
docker compose up --build -d" ssh -i /root/.ssh/id_deploy -o StrictHostKeyChecking=no \
Elewyn@192.168.1.208 \
"cd ${{ env.DEPLOY_PATH }} && /share/CACHEDEV1_DATA/.qpkg/container-station/usr/bin/docker compose down || true && /share/CACHEDEV1_DATA/.qpkg/container-station/usr/bin/docker compose up -d"

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
.env
data/
screenshots/
logs/
__pycache__/
*.pyc
*.pyo
*.db

View file

@ -3,7 +3,7 @@
## Contexte ## Contexte
Bot Discord de tracking de Personal Best (PB) pour les clan bosses du jeu Raid Shadow Legends. Bot Discord de tracking de Personal Best (PB) pour les clan bosses du jeu Raid Shadow Legends.
Communauté RTF (3 clans : RTF, RTFC, RTFR). Communauté TEA — The Ember Accord (4 clans : TEAI, TEAF, TEAC, TEACO).
## Branches ## Branches

View file

@ -16,7 +16,7 @@ class Guide(commands.Cog):
return return
embed = discord.Embed( embed = discord.Embed(
title="🧐 RTF Bot - Commands Guide", title="🧐 TEA 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,9 +81,10 @@ class Guide(commands.Cog):
# Classements par clan # Classements par clan
embed.add_field( embed.add_field(
name="🏆 Clan Leaderboards", name="🏆 Clan Leaderboards",
value="**RTF:** `!rtfhydra <diff>` `!rtfchimera <diff>` `!rtfcvc`\n" value="**🔥 TEAI (Inferno):** `!teaihydra <diff>` `!teaichimera <diff>` `!teaicvc`\n"
"**RTFC:** `!rtfchydra <diff>` `!rtfcchimera <diff>` `!rtfccvc`\n" "**🛡️ TEAF (Flame):** `!teafhydra <diff>` `!teafchimera <diff>` `!teafcvc`\n"
"**RTFR:** `!rtfrhydra <diff>` `!rtfrchimera <diff>` `!rtfrcvc`", "**⚔️ TEAC (Cinder):** `!teachydra <diff>` `!teachimera <diff>` `!teaccvc`\n"
"**👑 TEACO (Corrupted Olympians):** `!teacohydra <diff>` `!teacochimera <diff>` `!teacocvc`",
inline=False inline=False
) )
@ -104,7 +105,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"
"`!rtfhydra nm` - RTF clan Nightmare rankings\n" "`!teaihydra nm` - TEAI clan Nightmare rankings\n"
"**Always attach screenshot when submitting PBs!**", "**Always attach screenshot when submitting PBs!**",
inline=False inline=False
) )

View file

@ -12,21 +12,33 @@ class MyStats(commands.Cog):
self.bot = bot self.bot = bot
@commands.command(name="mystats") @commands.command(name="mystats")
async def mystats(self, ctx, target_user: str = None): async def mystats(self, ctx, *, target_user: str = None):
"""Affiche tous les PB d'un utilisateur avec les nouvelles difficultés""" """Affiche tous les PB d'un utilisateur avec les nouvelles difficultés"""
if ctx.channel.id != AUTHORIZED_CHANNEL_ID: if ctx.channel.id != AUTHORIZED_CHANNEL_ID:
return return
try: try:
username = target_user if target_user else ctx.author.id if target_user:
user_data = db_manager.get_user_all_pbs(username) matches = db_manager.find_user_by_name(target_user)
if not matches:
await ctx.send(f"❌ No data found for **{target_user}**.")
return
if len(matches) > 1:
await ctx.send(f"⚠️ Multiple users found for **{target_user}**. Please be more specific.")
return
user_id, display_name = matches[0]
else:
user_id = ctx.author.id
display_name = ctx.author.display_name
user_data = db_manager.get_user_all_pbs(user_id)
if not user_data: if not user_data:
await ctx.send(f"❌ No data found for **{ctx.author.display_name}**.") await ctx.send(f"❌ No data found for **{display_name}**.")
return return
embed = discord.Embed( embed = discord.Embed(
title=f"📊 {ctx.author.display_name}'s Complete Stats", title=f"📊 {display_name}'s Complete Stats",
color=0x00bfff color=0x00bfff
) )

View file

@ -10,7 +10,7 @@ class Pbchimera(commands.Cog):
self.bot = bot self.bot = bot
@commands.command(name="pbchimera") @commands.command(name="pbchimera")
async def pbchimera(self, ctx, arg1: str = None, arg2: str = None): async def pbchimera(self, ctx, arg1: str = None, *, arg2: str = None):
"""Commande !pbchimera avec gestion des difficultés""" """Commande !pbchimera avec gestion des difficultés"""
await handle_pb_command(ctx, 'chimera', arg1, arg2) await handle_pb_command(ctx, 'chimera', arg1, arg2)

View file

@ -10,7 +10,7 @@ class Pbcvc(commands.Cog):
self.bot = bot self.bot = bot
@commands.command(name="pbcvc") @commands.command(name="pbcvc")
async def pbcvc(self, ctx, target_user: str = None): async def pbcvc(self, ctx, *, target_user: str = None):
"""Commande !pbcvc""" """Commande !pbcvc"""
await handle_pb_command(ctx, 'cvc', target_user) await handle_pb_command(ctx, 'cvc', target_user)

View file

@ -10,7 +10,7 @@ class Pbhydra(commands.Cog):
self.bot = bot self.bot = bot
@commands.command(name="pbhydra") @commands.command(name="pbhydra")
async def pbhydra(self, ctx, arg1: str = None, arg2: str = None): async def pbhydra(self, ctx, arg1: str = None, *, arg2: str = None):
"""Commande !pbhydra avec gestion des difficultés""" """Commande !pbhydra avec gestion des difficultés"""
await handle_pb_command(ctx, 'hydra', arg1, arg2) await handle_pb_command(ctx, 'hydra', arg1, arg2)

View file

@ -32,44 +32,57 @@ 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 RTF --- # --- Commandes par clan TEAI (Inferno) ---
@commands.command() @commands.command()
async def rtfhydra(self, ctx, difficulty: str = None): async def teaihydra(self, ctx, difficulty: str = None):
await self._show_clan_leaderboard(ctx, 'hydra', difficulty, 'RTF') await self._show_clan_leaderboard(ctx, 'hydra', difficulty, 'TEAI')
@commands.command() @commands.command()
async def rtfchimera(self, ctx, difficulty: str = None): async def teaichimera(self, ctx, difficulty: str = None):
await self._show_clan_leaderboard(ctx, 'chimera', difficulty, 'RTF') await self._show_clan_leaderboard(ctx, 'chimera', difficulty, 'TEAI')
@commands.command() @commands.command()
async def rtfcvc(self, ctx): async def teaicvc(self, ctx):
await show_leaderboard(ctx, 'cvc', clan='RTF') await show_leaderboard(ctx, 'cvc', clan='TEAI')
# --- Commandes par clan RTFC --- # --- Commandes par clan TEAF (Flame) ---
@commands.command() @commands.command()
async def rtfchydra(self, ctx, difficulty: str = None): async def teafhydra(self, ctx, difficulty: str = None):
await self._show_clan_leaderboard(ctx, 'hydra', difficulty, 'RTFC') await self._show_clan_leaderboard(ctx, 'hydra', difficulty, 'TEAF')
@commands.command() @commands.command()
async def rtfcchimera(self, ctx, difficulty: str = None): async def teafchimera(self, ctx, difficulty: str = None):
await self._show_clan_leaderboard(ctx, 'chimera', difficulty, 'RTFC') await self._show_clan_leaderboard(ctx, 'chimera', difficulty, 'TEAF')
@commands.command() @commands.command()
async def rtfccvc(self, ctx): async def teafcvc(self, ctx):
await show_leaderboard(ctx, 'cvc', clan='RTFC') await show_leaderboard(ctx, 'cvc', clan='TEAF')
# --- Commandes par clan RTFR --- # --- Commandes par clan TEAC (Cinder) ---
@commands.command() @commands.command()
async def rtfrhydra(self, ctx, difficulty: str = None): async def teachydra(self, ctx, difficulty: str = None):
await self._show_clan_leaderboard(ctx, 'hydra', difficulty, 'RTFR') await self._show_clan_leaderboard(ctx, 'hydra', difficulty, 'TEAC')
@commands.command() @commands.command()
async def rtfrchimera(self, ctx, difficulty: str = None): async def teachimera(self, ctx, difficulty: str = None):
await self._show_clan_leaderboard(ctx, 'chimera', difficulty, 'RTFR') await self._show_clan_leaderboard(ctx, 'chimera', difficulty, 'TEAC')
@commands.command() @commands.command()
async def rtfrcvc(self, ctx): async def teaccvc(self, ctx):
await show_leaderboard(ctx, 'cvc', clan='RTFR') await show_leaderboard(ctx, 'cvc', clan='TEAC')
# --- 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,11 +12,27 @@ 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 # Configuration des clans TEA - The Ember Accord
CLAN_CONFIG = { CLAN_CONFIG = {
'RTF': {'name': 'RTF', 'emoji': '🛡️', 'color': 0x00ff00}, 'TEAI': {'name': 'TEAI', 'full_name': 'Inferno', 'emoji': '🔥', 'color': 0xff4500},
'RTFC': {'name': 'RTFC', 'emoji': '🔥', 'color': 0xff4500}, 'TEAF': {'name': 'TEAF', 'full_name': 'Flame', 'emoji': '🛡️', 'color': 0x00ff00},
'RTFR': {'name': 'RTFR', 'emoji': '⚔️', 'color': 0x1e90ff} 'TEAC': {'name': 'TEAC', 'full_name': 'Cinder', 'emoji': '⚔️', 'color': 0x1e90ff},
'TEACO': {'name': 'TEACO', 'full_name': 'Corrupted Olympians', 'emoji': '👑', 'color': 0x9932cc},
}
# Mapping role Discord ID → clé de clan
CLAN_ROLE_IDS = {
1505856647744979004: 'TEAI',
1505856725490733056: 'TEAF',
1505856808361656370: 'TEAC',
1505856914439929856: 'TEACO',
}
# 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,3 @@
version: '3.8'
services: services:
discord-bot: discord-bot:
build: . build: .
@ -15,7 +13,6 @@ services:
- ./logs:/app/logs - ./logs:/app/logs
environment: environment:
- TZ=Europe/Paris - TZ=Europe/Paris
container_name: rtf-discord-bot
deploy: deploy:
resources: resources:
limits: limits:

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from config import DATABASE_PATH from config import DATABASE_PATH, CLAN_MIGRATION
import sqlite3, os import sqlite3, os
class DatabaseManager: class DatabaseManager:
@ -70,7 +70,16 @@ 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('''
@ -108,7 +117,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): 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""" """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()
@ -122,18 +131,19 @@ class DatabaseManager:
else: else:
column_prefix = f"pb_{boss_type}" column_prefix = f"pb_{boss_type}"
# Créer l'utilisateur s'il n'existe pas, sinon mettre à jour # COALESCE(?, clan) : on n'écrase pas le clan existant si la détection retourne None
cursor.execute(f''' cursor.execute(f'''
INSERT INTO users (discord_id, discord_username, {column_prefix}, {column_prefix}_screenshot, {column_prefix}_date, total_attempts) INSERT INTO users (discord_id, discord_username, clan, {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, damage, screenshot_filename, username, damage, screenshot_filename)) ''', (str(user_id), username, clan, damage, screenshot_filename, username, clan, damage, screenshot_filename))
# Ajouter à l'historique # Ajouter à l'historique
cursor.execute(''' cursor.execute('''
@ -157,20 +167,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 SELECT discord_username, {column_prefix}, {column_prefix}_date, clan
FROM users FROM users
WHERE {column_prefix} > 0 WHERE {column_prefix} > 0
''' '''
params = []
if clan: if clan:
base_query += ''' AND ( base_query += ' AND clan = ?'
discord_username LIKE '[''' + clan + '''] %' OR params.append(clan)
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, (limit,)) cursor.execute(base_query, params)
results = cursor.fetchall() results = cursor.fetchall()
conn.close() conn.close()
return results return results

View file

@ -1,7 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import re import re
from datetime import datetime from datetime import datetime
from config import AUTHORIZED_CHANNEL_ID, DIFFICULTY_SHORTCUTS from typing import Optional
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"""
@ -43,24 +44,12 @@ def normalize_difficulty(difficulty):
return DIFFICULTY_SHORTCUTS[difficulty_lower] return DIFFICULTY_SHORTCUTS[difficulty_lower]
return difficulty_lower return difficulty_lower
def get_user_clan(username): def get_clan_from_member(member) -> Optional[str]:
"""Détermine le clan d'un utilisateur basé sur son pseudo""" """Détecte le clan d'un membre via ses rôles Discord"""
if not username: for role in member.roles:
if role.id in CLAN_ROLE_IDS:
return CLAN_ROLE_IDS[role.id]
return None return None
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
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"""

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, get_user_clan from utils.helpers import normalize_difficulty, get_difficulty_display_name, format_damage_display, format_date_only
db_manager = None db_manager = None
@ -49,19 +49,17 @@ async def show_leaderboard(ctx, boss_type, difficulty=None, clan=None):
medals = ["🥇", "🥈", "🥉"] + ["🏅"] * 7 medals = ["🥇", "🥈", "🥉"] + ["🏅"] * 7
for i, (username, damage, date) in enumerate(leaderboard): for i, (username, damage, date, row_clan) 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 le clan dans le nom si pas de filtre par clan # Afficher l'emoji du clan si le leaderboard est global (pas filtré par clan)
display_name = username display_name = username
if not clan: if not clan and row_clan:
user_clan = get_user_clan(username) clan_emoji = CLAN_CONFIG.get(row_clan, {'emoji': '🏛️'})['emoji']
if user_clan:
clan_emoji = CLAN_CONFIG.get(user_clan, {'emoji': '🏛️'})['emoji']
display_name = f"{clan_emoji} {username}" display_name = f"{clan_emoji} {username}"
embed.add_field( embed.add_field(

View file

@ -8,6 +8,7 @@ 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
@ -92,6 +93,7 @@ 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:
@ -101,7 +103,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 user_id, username, boss_type, damage, screenshot_filename, difficulty, clan
) )
if old_screenshot: if old_screenshot: