Modularize bot: per-guild config system for roles/channels/categories

- Add utils/guild_config.py: centralized config loader per guild (data/guilds/{id}.json)
- Add commandes/config.py: /config set_role, /config set_channel, /config set_category, /config voir, /config reset
- Replace ALL hardcoded role IDs (banquier, gouvernement, citoyen, staff, ems, permis, fbm, ursaff, etc.) with guild_config lookups
- Replace ALL hardcoded channel IDs (welcome, candidature, questions, mp_reception) with guild_config lookups
- Replace ALL hardcoded category IDs (ticket) with guild_config lookups
- Remove duplicate commandes/welcome_message.py (kept utils/ version)
- Remove 20+ hardcoded snowflake IDs across 22 files
- Guilds configure via /config on Discord, no code changes needed
This commit is contained in:
utilisateurqu 2026-08-17 00:12:57 +02:00
parent bc3cb3f3d5
commit 2e38d05152
25 changed files with 368 additions and 125 deletions

157
utils/guild_config.py Normal file
View file

@ -0,0 +1,157 @@
import json
import os
import discord
from typing import Optional
CONFIG_DIR = os.path.join(os.path.dirname(__file__), "..", "data", "guilds")
DEFAULT_CONFIG = {
"roles": {
"banquier": None,
"gouvernement": None,
"citoyen": None,
"staff": None,
"team_staff": None,
"attente_oral": None,
"candidature_a_faire": None,
"ems": None,
"visite_medicale": None,
"permis": None,
"auto_ecole": None,
"fbm": None,
"banque_nationale": None,
"ursaff": None,
"session_reminders": None,
},
"channels": {
"welcome": None,
"candidature": None,
"candidature_2": None,
"questions": None,
"mp_reception": None,
},
"categories": {
"ticket": None,
},
}
ROLE_LABELS = {
"banquier": "Banquier",
"gouvernement": "Gouvernement",
"citoyen": "Citoyen",
"staff": "Staff",
"team_staff": "Team Staff",
"attente_oral": "Attente Oral",
"candidature_a_faire": "Candidature A Faire",
"ems": "EMS",
"visite_medicale": "Visite Médicale",
"permis": "Permis",
"auto_ecole": "Auto-École",
"fbm": "FBM",
"banque_nationale": "Banque Nationale",
"ursaff": "URSAFF",
"session_reminders": "Session Reminders",
}
CHANNEL_LABELS = {
"welcome": "Bienvenue",
"candidature": "Candidature",
"candidature_2": "Candidature 2",
"questions": "Questions",
"mp_reception": "Réception MP",
}
CATEGORY_LABELS = {
"ticket": "Tickets",
}
def _config_path(guild_id: int) -> str:
return os.path.join(CONFIG_DIR, f"{guild_id}.json")
def load_guild_config(guild_id: int) -> dict:
path = _config_path(guild_id)
if not os.path.exists(path):
return {"roles": {}, "channels": {}, "categories": {}}
try:
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
return {"roles": {}, "channels": {}, "categories": {}}
def save_guild_config(guild_id: int, config: dict):
os.makedirs(CONFIG_DIR, exist_ok=True)
path = _config_path(guild_id)
with open(path, "w", encoding="utf-8") as f:
json.dump(config, f, ensure_ascii=False, indent=4)
def get_role_id(guild_id: int, key: str) -> Optional[int]:
config = load_guild_config(guild_id)
val = config.get("roles", {}).get(key)
return int(val) if val else None
def get_channel_id(guild_id: int, key: str) -> Optional[int]:
config = load_guild_config(guild_id)
val = config.get("channels", {}).get(key)
return int(val) if val else None
def get_category_id(guild_id: int, key: str) -> Optional[int]:
config = load_guild_config(guild_id)
val = config.get("categories", {}).get(key)
return int(val) if val else None
def get_guild_role(guild: discord.Guild, key: str) -> Optional[discord.Role]:
role_id = get_role_id(guild.id, key)
if not role_id:
return None
return guild.get_role(role_id)
def get_guild_channel(guild: discord.Guild, key: str) -> Optional[discord.abc.Messageable]:
channel_id = get_channel_id(guild.id, key)
if not channel_id:
return None
return guild.get_channel(channel_id)
def get_guild_category(guild: discord.Guild, key: str) -> Optional[discord.CategoryChannel]:
cat_id = get_category_id(guild.id, key)
if not cat_id:
return None
return guild.get_channel(cat_id)
def has_role(member: discord.Member, key: str) -> bool:
role = get_guild_role(member.guild, key)
if not role:
return False
return role in member.roles
def set_config_value(guild_id: int, section: str, key: str, value):
config = load_guild_config(guild_id)
if section not in config:
config[section] = {}
config[section][key] = value
save_guild_config(guild_id, config)
def is_configured(guild_id: int) -> dict:
config = load_guild_config(guild_id)
missing = {"roles": [], "channels": [], "categories": []}
for key in DEFAULT_CONFIG["roles"]:
if not config.get("roles", {}).get(key):
missing["roles"].append(key)
for key in DEFAULT_CONFIG["channels"]:
if not config.get("channels", {}).get(key):
missing["channels"].append(key)
for key in DEFAULT_CONFIG["categories"]:
if not config.get("categories", {}).get(key):
missing["categories"].append(key)
return missing

View file

@ -1,6 +1,7 @@
import discord
from discord.ext import commands
from utils.logger import log_info, log_error
from utils.guild_config import get_guild_channel
class WelcomeMessage(commands.Cog):
def __init__(self, bot):
@ -9,16 +10,22 @@ class WelcomeMessage(commands.Cog):
@commands.Cog.listener()
async def on_member_join(self, member):
try:
# ID du salon discussion
welcome_channel_id = 1278483527783944242
welcome_channel = member.guild.get_channel(welcome_channel_id)
# Récupérer le salon de bienvenue depuis la config
welcome_channel = get_guild_channel(member.guild, "welcome")
if welcome_channel:
cand_ch = get_guild_channel(member.guild, "candidature")
cand2_ch = get_guild_channel(member.guild, "candidature_2")
quest_ch = get_guild_channel(member.guild, "questions")
cand_mention = cand_ch.mention if cand_ch else "#candidature"
cand2_mention = cand2_ch.mention if cand2_ch else "#candidature-2"
quest_mention = quest_ch.mention if quest_ch else "#questions"
welcome_message = (
f"## BIENVENUE <:panda_welcome:1297298999849517056> !\n\n"
f"> **L'équipe staff te souhaite la bienvenue sur Victoria's Vice {member.mention} !**\n\n"
f"> Pour ta candidature <:arrow_1:1297491509201080362> <#1278477608392196176> / <#1278477152215371806> \n\n"
f"> Si tu as des questions <:arrow_1:1297491509201080362> <#1279037583820718120>\n\n"
f"> Pour ta candidature <:arrow_1:1297491509201080362> {cand_mention} / {cand2_mention} \n\n"
f"> Si tu as des questions <:arrow_1:1297491509201080362> {quest_mention}\n\n"
f"*> Bien cordialement, L'équipe staff de Victoria's Vice*"
)