Compare commits
17 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e526a40bc | ||
|
|
64b60f1597 | ||
|
|
e364f354b0 | ||
|
|
314483e4f5 | ||
|
|
1a37ec8838 | ||
|
|
0d744c8cdc | ||
|
|
4ecac026b5 | ||
|
|
85942723ab | ||
| ec86776e42 | |||
| 01ef1cadcf | |||
| 14887625a2 | |||
| f4dbfba223 | |||
| bccf437192 | |||
| cd81d299ab | |||
| e4a38d7853 | |||
| cdc36dd42b | |||
| 8932696709 |
15 changed files with 160 additions and 111 deletions
|
|
@ -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
8
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
.env
|
||||||
|
data/
|
||||||
|
screenshots/
|
||||||
|
logs/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.db
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
24
config.py
24
config.py
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"""
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue