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