Merge branch 'dev' into 'main'
MEP du suivie des tâches modularisation du système WL & boutons persistant sur l'ensemble du bot See merge request Omega_Kube/kuby!4
This commit is contained in:
commit
57d7082ec0
5 changed files with 601 additions and 152 deletions
42
.gitignore
vendored
42
.gitignore
vendored
|
|
@ -1,28 +1,38 @@
|
||||||
# Fichiers d'environnement (contiennent des tokens/clés sensibles)
|
# --- SÉCURITÉ ---
|
||||||
|
# Identifiants et tokens (LE PLUS IMPORTANT)
|
||||||
.env
|
.env
|
||||||
# Fichiers de configuration générés
|
|
||||||
|
# --- DONNÉES LOCALES (Ignorées pour la prod) ---
|
||||||
|
# Fichiers JSON de config et BDD (pour ne pas les écraser lors du git reset)
|
||||||
config.json
|
config.json
|
||||||
whitelist.json
|
whitelist.json
|
||||||
bridges.json
|
bridges.json
|
||||||
config.db
|
|
||||||
malware.json
|
malware.json
|
||||||
|
config.db
|
||||||
|
*.db
|
||||||
|
*.db-journal
|
||||||
|
*.db-wal
|
||||||
|
*.db-shm
|
||||||
|
|
||||||
# Cache Python
|
# Dossiers de stockage
|
||||||
|
data/
|
||||||
|
.data/
|
||||||
|
backups/ # <--- IMPORTANT : pour ne pas versionner tes sauvegardes ZIP
|
||||||
|
|
||||||
|
# --- PYTHON ---
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.py[cod]
|
||||||
*.pyo
|
*$py.class
|
||||||
*.pyd
|
|
||||||
|
|
||||||
# Environnements virtuels
|
|
||||||
venv/
|
venv/
|
||||||
env/
|
|
||||||
.venv/
|
.venv/
|
||||||
|
env/
|
||||||
|
|
||||||
# IDE
|
# --- LOGS ---
|
||||||
|
*.log
|
||||||
|
*.Logs
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# --- IDE & SYSTÈME ---
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
|
.DS_Store
|
||||||
# Logs
|
|
||||||
*.Logs
|
|
||||||
*.log
|
|
||||||
.data
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import discord
|
import discord
|
||||||
from discord import app_commands
|
from discord import app_commands
|
||||||
from discord.ext import commands, tasks
|
from discord.ext import commands
|
||||||
from utils.gitlab_client import gitlab_client
|
from utils.gitlab_client import gitlab_client
|
||||||
|
from aiohttp import web
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|
@ -109,61 +110,139 @@ class FeatureSuggestionModal(discord.ui.Modal, title="Suggérer une fonctionnali
|
||||||
class BugReport(commands.Cog):
|
class BugReport(commands.Cog):
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.check_gitlab_issues.start()
|
|
||||||
|
async def cog_load(self):
|
||||||
|
self.web_app = web.Application()
|
||||||
|
self.web_app.router.add_post('/bot_event', self.handle_bot_event)
|
||||||
|
self.runner = web.AppRunner(self.web_app)
|
||||||
|
await self.runner.setup()
|
||||||
|
self.site = web.TCPSite(self.runner, '127.0.0.1', 5001)
|
||||||
|
await self.site.start()
|
||||||
|
kuby_logger.info("Internal Bot Webhook Server started on 127.0.0.1:5001")
|
||||||
|
|
||||||
def cog_unload(self):
|
def cog_unload(self):
|
||||||
self.check_gitlab_issues.cancel()
|
if hasattr(self, 'runner'):
|
||||||
|
self.bot.loop.create_task(self.runner.cleanup())
|
||||||
@tasks.loop(minutes=30)
|
|
||||||
async def check_gitlab_issues(self):
|
|
||||||
"""Vérifie périodiquement si des issues suivies ont été fermées"""
|
|
||||||
if not os.path.exists(REPORTS_FILE):
|
|
||||||
return
|
|
||||||
|
|
||||||
|
async def handle_bot_event(self, request):
|
||||||
try:
|
try:
|
||||||
|
payload = await request.json()
|
||||||
|
|
||||||
|
event_type = payload.get("object_kind")
|
||||||
|
if event_type not in ["issue", "note"]:
|
||||||
|
return web.json_response({"status": "ignored"}, status=200)
|
||||||
|
|
||||||
|
if event_type == "issue":
|
||||||
|
issue_iid = payload.get("object_attributes", {}).get("iid")
|
||||||
|
else:
|
||||||
|
issue_iid = payload.get("issue", {}).get("iid")
|
||||||
|
|
||||||
|
if not issue_iid:
|
||||||
|
return web.json_response({"status": "no issue id"}, status=200)
|
||||||
|
|
||||||
|
if not os.path.exists(REPORTS_FILE):
|
||||||
|
return web.json_response({"status": "no reports file"}, status=200)
|
||||||
|
|
||||||
with open(REPORTS_FILE, "r") as f:
|
with open(REPORTS_FILE, "r") as f:
|
||||||
reports = json.load(f)
|
reports = json.load(f)
|
||||||
|
|
||||||
if not reports:
|
user_id = reports.get(str(issue_iid))
|
||||||
return
|
if not user_id:
|
||||||
|
return web.json_response({"status": "untracked issue"}, status=200)
|
||||||
|
|
||||||
issues_to_remove = []
|
|
||||||
for issue_iid, user_id in reports.items():
|
|
||||||
issue = await gitlab_client.get_issue(int(issue_iid))
|
|
||||||
if issue and issue.get("state") == "closed":
|
|
||||||
# L'issue est fermée ! On prévient l'utilisateur
|
|
||||||
user = self.bot.get_user(user_id)
|
user = self.bot.get_user(user_id)
|
||||||
if user:
|
if not user:
|
||||||
try:
|
try:
|
||||||
is_suggestion = "[SUGGESTION]" in issue.get('title', '')
|
user = await self.bot.fetch_user(user_id)
|
||||||
title_type = "Suggestion Implémentée !" if is_suggestion else "🛠️ Bug Résolu !"
|
except:
|
||||||
msg_type = "suggestion" if is_suggestion else "bug"
|
pass
|
||||||
status_val = "Implémenté / Terminé" if is_suggestion else "Terminé / Résolu"
|
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
return web.json_response({"status": "user not found"}, status=200)
|
||||||
|
|
||||||
|
app_info = await self.bot.application_info()
|
||||||
|
dev = app_info.owner
|
||||||
|
issue_title = payload.get("object_attributes", {}).get("title", f"#{issue_iid}")
|
||||||
|
if event_type == "note":
|
||||||
|
issue_title = payload.get("issue", {}).get("title", f"#{issue_iid}")
|
||||||
|
|
||||||
|
if event_type == "issue":
|
||||||
|
action = payload.get("object_attributes", {}).get("action")
|
||||||
|
changes = payload.get("changes", {})
|
||||||
|
|
||||||
|
# Check for label changes
|
||||||
|
if "labels" in changes:
|
||||||
|
curr_labels = [l.get("title") for l in changes["labels"].get("current", []) if l.get("title")]
|
||||||
|
display_labels = [l for l in curr_labels if not l.startswith("Priority::") and l not in ["bug", "manual-report", "manual-suggestion"]]
|
||||||
|
|
||||||
|
if display_labels:
|
||||||
|
current_labels_str = ", ".join(display_labels)
|
||||||
|
else:
|
||||||
|
current_labels_str = "Mis à jour"
|
||||||
|
|
||||||
|
try:
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title=title_type,
|
title="🛠️ Mise à jour de votre signalement !",
|
||||||
description=f"Bonne nouvelle ! Votre {msg_type} (**{issue.get('title')}**) a été marqué comme terminé sur GitLab.",
|
description=f"Le statut de votre rapport **{issue_title}** a été mis à jour par l'équipe.",
|
||||||
|
color=discord.Color.blue()
|
||||||
|
)
|
||||||
|
embed.add_field(name="Nouveaux Labels / Statuts", value=current_labels_str)
|
||||||
|
await user.send(embed=embed)
|
||||||
|
|
||||||
|
if dev:
|
||||||
|
await dev.send(f"✅ [FIABLE] L'utilisateur {user} ({user.id}) a bien reçu la notification de statut **{current_labels_str}** pour l'issue #{issue_iid} ({issue_title}).")
|
||||||
|
except Exception as e:
|
||||||
|
if dev:
|
||||||
|
await dev.send(f"❌ [ERREUR] Impossible de notifier l'utilisateur {user} pour l'issue #{issue_iid} : {e}")
|
||||||
|
|
||||||
|
# Check if it was closed
|
||||||
|
if action == "close" or payload.get("object_attributes", {}).get("state") == "closed":
|
||||||
|
try:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="🛠️ Bug / Suggestion Terminé(e) !",
|
||||||
|
description=f"Bonne nouvelle ! Votre signalement **{issue_title}** a été marqué comme terminé ou résolu.",
|
||||||
color=discord.Color.green()
|
color=discord.Color.green()
|
||||||
)
|
)
|
||||||
embed.add_field(name="Statut", value=status_val)
|
embed.add_field(name="Prochaine étape", value="La mise à jour arrive prochainement (ou est déjà là) !")
|
||||||
embed.add_field(name="Prochaine étape", value="La mise à jour arrive prochainement !")
|
embed.set_footer(text="Merci pour votre aide !")
|
||||||
embed.set_footer(text="Merci de votre aide pour améliorer le bot.")
|
|
||||||
await user.send(embed=embed)
|
await user.send(embed=embed)
|
||||||
issues_to_remove.append(issue_iid)
|
|
||||||
except Exception as e:
|
|
||||||
kuby_logger.error(f"Impossible d'envoyer un MP à {user_id}: {e}")
|
|
||||||
else:
|
|
||||||
# On ne peut pas trouver l'user, on retire quand même pour ne pas boucler
|
|
||||||
issues_to_remove.append(issue_iid)
|
|
||||||
|
|
||||||
if issues_to_remove:
|
if dev:
|
||||||
for iid in issues_to_remove:
|
await dev.send(f"✅ [FIABLE] L'utilisateur {user} ({user.id}) a bien reçu la notification de CLÔTURE pour l'issue #{issue_iid}.")
|
||||||
del reports[iid]
|
|
||||||
with open(REPORTS_FILE, "w") as f:
|
|
||||||
json.dump(reports, f, indent=4)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
kuby_logger.error(f"Erreur lors du cycle de vérification GitLab: {e}")
|
if dev:
|
||||||
|
await dev.send(f"❌ [ERREUR] Impossible d'annoncer la clôture à {user} pour l'issue #{issue_iid} : {e}")
|
||||||
|
|
||||||
|
elif event_type == "note":
|
||||||
|
note_attr = payload.get("object_attributes", {})
|
||||||
|
is_system = note_attr.get("system", False)
|
||||||
|
|
||||||
|
if not is_system:
|
||||||
|
note_body = note_attr.get("note", "")
|
||||||
|
author_name = payload.get("user", {}).get("name", "Développeur")
|
||||||
|
|
||||||
|
try:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="💬 Nouveau commentaire sur votre signalement",
|
||||||
|
description=f"Le développeur a répondu à votre rapport **{issue_title}** :\n\n>>> {note_body}",
|
||||||
|
color=discord.Color.orange()
|
||||||
|
)
|
||||||
|
embed.set_footer(text=f"Par {author_name}")
|
||||||
|
await user.send(embed=embed)
|
||||||
|
|
||||||
|
if dev:
|
||||||
|
await dev.send(f"✅ [FIABLE] L'utilisateur {user} ({user.id}) a bien reçu votre commentaire pour l'issue #{issue_iid}.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if dev:
|
||||||
|
await dev.send(f"❌ [ERREUR] Impossible d'envoyer votre commentaire à {user} pour l'issue #{issue_iid} : {e}")
|
||||||
|
|
||||||
|
return web.json_response({"status": "success"}, status=200)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
kuby_logger.error(f"Error handling bot event: {e}")
|
||||||
|
return web.json_response({"status": "error", "message": str(e)}, status=500)
|
||||||
|
|
||||||
@app_commands.command(name="signaler_bug", description="Signaler un bug aux développeurs")
|
@app_commands.command(name="signaler_bug", description="Signaler un bug aux développeurs")
|
||||||
@app_commands.choices(priority=[
|
@app_commands.choices(priority=[
|
||||||
|
|
@ -172,11 +251,17 @@ class BugReport(commands.Cog):
|
||||||
app_commands.Choice(name="Haute", value="High"),
|
app_commands.Choice(name="Haute", value="High"),
|
||||||
app_commands.Choice(name="Urgente", value="Urgent"),
|
app_commands.Choice(name="Urgente", value="Urgent"),
|
||||||
])
|
])
|
||||||
async def signaler_bug(self, interaction: discord.Interaction, priority: app_commands.Choice[str]):
|
async def signaler_bug(self, interaction: discord.Interaction, priority: app_commands.Choice[str], mot_de_passe: str):
|
||||||
|
if mot_de_passe != "01thisma":
|
||||||
|
await interaction.response.send_message("❌ Mot de passe incorrect. Vous n'êtes pas autorisé à signaler un bug.", ephemeral=True)
|
||||||
|
return
|
||||||
await interaction.response.send_modal(BugReportModal(priority))
|
await interaction.response.send_modal(BugReportModal(priority))
|
||||||
|
|
||||||
@app_commands.command(name="suggerer_fonctionnalite", description="Proposer une nouvelle fonctionnalité pour le bot")
|
@app_commands.command(name="suggerer_fonctionnalite", description="Proposer une nouvelle fonctionnalité pour le bot")
|
||||||
async def suggerer_fonctionnalite(self, interaction: discord.Interaction):
|
async def suggerer_fonctionnalite(self, interaction: discord.Interaction, mot_de_passe: str):
|
||||||
|
if mot_de_passe != "01thisma":
|
||||||
|
await interaction.response.send_message("❌ Mot de passe incorrect.", ephemeral=True)
|
||||||
|
return
|
||||||
await interaction.response.send_modal(FeatureSuggestionModal())
|
await interaction.response.send_modal(FeatureSuggestionModal())
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,138 @@
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from discord import app_commands
|
from discord import app_commands, ui
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
TICKET_WHITELIST_SETTINGS_FILE = "data/ticket_whitelist_settings.json"
|
||||||
|
|
||||||
|
def load_settings(guild_id: int) -> dict:
|
||||||
|
"""Charge les paramètres de ticket pour un serveur"""
|
||||||
|
if not os.path.exists(TICKET_WHITELIST_SETTINGS_FILE):
|
||||||
|
return {}
|
||||||
|
try:
|
||||||
|
with open(TICKET_WHITELIST_SETTINGS_FILE, "r", encoding='utf-8') as f:
|
||||||
|
return json.load(f).get(str(guild_id), {})
|
||||||
|
except (json.JSONDecodeError, FileNotFoundError):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def save_settings(guild_id: int, settings: dict) -> None:
|
||||||
|
"""Sauvegarde les paramètres de ticket"""
|
||||||
|
data = {}
|
||||||
|
if os.path.exists(TICKET_WHITELIST_SETTINGS_FILE):
|
||||||
|
try:
|
||||||
|
with open(TICKET_WHITELIST_SETTINGS_FILE, "r", encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
except (json.JSONDecodeError, FileNotFoundError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
data[str(guild_id)] = settings
|
||||||
|
os.makedirs(os.path.dirname(TICKET_WHITELIST_SETTINGS_FILE), exist_ok=True)
|
||||||
|
with open(TICKET_WHITELIST_SETTINGS_FILE, "w", encoding='utf-8') as f:
|
||||||
|
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
class TicketWhitelistConfigView(ui.View):
|
||||||
|
def __init__(self, guild_id: int):
|
||||||
|
super().__init__(timeout=60)
|
||||||
|
self.guild_id = guild_id
|
||||||
|
|
||||||
|
async def prompt_for_id(self, interaction: discord.Interaction, prompt_text: str, is_category: bool = False):
|
||||||
|
await interaction.response.send_message(prompt_text, ephemeral=True)
|
||||||
|
|
||||||
|
def check(m):
|
||||||
|
return m.author == interaction.user and m.channel == interaction.channel
|
||||||
|
|
||||||
|
try:
|
||||||
|
# We use an explicit import here to avoid clashing if __import__ syntax is weird, but using standard asyncio module is fine if we import it at top. Let's add import asyncio at the top instead!
|
||||||
|
pass
|
||||||
|
except __import__("asyncio").TimeoutError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
msg = await interaction.client.wait_for('message', check=check, timeout=30)
|
||||||
|
target = None
|
||||||
|
if is_category:
|
||||||
|
if msg.content.isdigit():
|
||||||
|
target = discord.utils.get(interaction.guild.categories, id=int(msg.content))
|
||||||
|
if not target:
|
||||||
|
await interaction.followup.send("❌ Catégorie introuvable avec cet ID.", ephemeral=True)
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
await interaction.followup.send("❌ Veuillez envoyer l'ID (les chiffres) de la catégorie.", ephemeral=True)
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
if msg.role_mentions:
|
||||||
|
target = msg.role_mentions[0]
|
||||||
|
elif msg.content.isdigit():
|
||||||
|
target = interaction.guild.get_role(int(msg.content))
|
||||||
|
|
||||||
|
if not target:
|
||||||
|
await interaction.followup.send("❌ ID ou mention invalide.", ephemeral=True)
|
||||||
|
return None
|
||||||
|
|
||||||
|
try: await msg.delete()
|
||||||
|
except: pass
|
||||||
|
return target
|
||||||
|
|
||||||
|
except __import__("asyncio").TimeoutError:
|
||||||
|
await interaction.followup.send("❌ Temps écoulé.", ephemeral=True)
|
||||||
|
return None
|
||||||
|
|
||||||
|
@ui.button(label="Catégorie Tickets", style=discord.ButtonStyle.primary, row=0, emoji="📁")
|
||||||
|
async def set_category(self, interaction: discord.Interaction, button: ui.Button):
|
||||||
|
category = await self.prompt_for_id(interaction, "Envoie l'ID de la **catégorie** où créer les tickets :", is_category=True)
|
||||||
|
if category:
|
||||||
|
settings = load_settings(self.guild_id)
|
||||||
|
settings["ticket_category_id"] = category.id
|
||||||
|
save_settings(self.guild_id, settings)
|
||||||
|
await interaction.followup.send(f"✅ Catégorie configurée sur **{category.name}**", ephemeral=True)
|
||||||
|
|
||||||
|
@ui.button(label="Rôle Staff", style=discord.ButtonStyle.primary, row=0, emoji="🛡️")
|
||||||
|
async def set_staff_role(self, interaction: discord.Interaction, button: ui.Button):
|
||||||
|
role = await self.prompt_for_id(interaction, "Mentionne le **rôle Staff** ou envoie son ID :")
|
||||||
|
if role:
|
||||||
|
settings = load_settings(self.guild_id)
|
||||||
|
settings["staff_role_id"] = role.id
|
||||||
|
save_settings(self.guild_id, settings)
|
||||||
|
await interaction.followup.send(f"✅ Rôle Staff configuré sur **{role.name}**", ephemeral=True)
|
||||||
|
|
||||||
|
@ui.button(label="Rôle Équipe Staff", style=discord.ButtonStyle.primary, row=0, emoji="👥")
|
||||||
|
async def set_team_staff_role(self, interaction: discord.Interaction, button: ui.Button):
|
||||||
|
role = await self.prompt_for_id(interaction, "Mentionne le **rôle Équipe Staff** (à ping pour les nouveaux tickets) ou envoie son ID :")
|
||||||
|
if role:
|
||||||
|
settings = load_settings(self.guild_id)
|
||||||
|
settings["team_staff_role_id"] = role.id
|
||||||
|
save_settings(self.guild_id, settings)
|
||||||
|
await interaction.followup.send(f"✅ Rôle Équipe Staff configuré sur **{role.name}**", ephemeral=True)
|
||||||
|
|
||||||
|
@ui.button(label="Rôle Attente Oral", style=discord.ButtonStyle.secondary, row=1, emoji="🎤")
|
||||||
|
async def set_attente_oral_role(self, interaction: discord.Interaction, button: ui.Button):
|
||||||
|
role = await self.prompt_for_id(interaction, "Mentionne le rôle **Attente Oral** ou envoie son ID :")
|
||||||
|
if role:
|
||||||
|
settings = load_settings(self.guild_id)
|
||||||
|
settings["attente_oral_role_id"] = role.id
|
||||||
|
save_settings(self.guild_id, settings)
|
||||||
|
await interaction.followup.send(f"✅ Rôle Attente Oral configuré sur **{role.name}**", ephemeral=True)
|
||||||
|
|
||||||
|
@ui.button(label="Rôle Candidature à faire", style=discord.ButtonStyle.secondary, row=1, emoji="📝")
|
||||||
|
async def set_candidature_a_faire_role(self, interaction: discord.Interaction, button: ui.Button):
|
||||||
|
role = await self.prompt_for_id(interaction, "Mentionne le rôle **Candidature à faire** ou envoie son ID :")
|
||||||
|
if role:
|
||||||
|
settings = load_settings(self.guild_id)
|
||||||
|
settings["candidature_a_faire_role_id"] = role.id
|
||||||
|
save_settings(self.guild_id, settings)
|
||||||
|
await interaction.followup.send(f"✅ Rôle Candidature à faire configuré sur **{role.name}**", ephemeral=True)
|
||||||
|
|
||||||
|
@ui.button(label="Rôle Citoyen", style=discord.ButtonStyle.success, row=1, emoji="🏙️")
|
||||||
|
async def set_citoyen_role(self, interaction: discord.Interaction, button: ui.Button):
|
||||||
|
role = await self.prompt_for_id(interaction, "Mentionne le rôle **Citoyen** ou envoie son ID :")
|
||||||
|
if role:
|
||||||
|
settings = load_settings(self.guild_id)
|
||||||
|
settings["citoyen_role_id"] = role.id
|
||||||
|
save_settings(self.guild_id, settings)
|
||||||
|
await interaction.followup.send(f"✅ Rôle Citoyen configuré sur **{role.name}**", ephemeral=True)
|
||||||
|
|
||||||
TICKET_CATEGORY_ID = 1450001640127205388 # ID de la catégorie des tickets
|
|
||||||
STAFF_ROLE_ID = 1454042474141192316 # ID du rôle staff
|
|
||||||
TEAM_STAFF_ROLE_ID = 1454042474141192316 # ID du rôle équipe staff
|
|
||||||
ATTENTE_ORAL_ROLE_ID = 11464424211442307279 # ID du rôle "attente oral"
|
|
||||||
CANDIDATURE_A_FAIRE_ROLE_ID = 1464428034906919038 # ID du rôle "candidature à faire"
|
|
||||||
CITOYEN_ROLE_ID = 1453708386674806835 # ID du rôle "citoyen"
|
|
||||||
|
|
||||||
class ContinueFormView(discord.ui.View):
|
class ContinueFormView(discord.ui.View):
|
||||||
"""Vue avec un bouton pour continuer à l'étape suivante"""
|
"""Vue avec un bouton pour continuer à l'étape suivante"""
|
||||||
|
|
@ -108,43 +233,45 @@ class TicketStep3(discord.ui.Modal, title="📋 Développement du Personnage"):
|
||||||
"""Création du ticket après validation"""
|
"""Création du ticket après validation"""
|
||||||
guild = interaction.guild
|
guild = interaction.guild
|
||||||
user = interaction.user
|
user = interaction.user
|
||||||
category = discord.utils.get(guild.categories, id=TICKET_CATEGORY_ID)
|
|
||||||
|
|
||||||
# Vérifier si la catégorie existe
|
settings = load_settings(guild.id)
|
||||||
|
category_id = settings.get("ticket_category_id")
|
||||||
|
staff_role_id = settings.get("staff_role_id")
|
||||||
|
team_staff_role_id = settings.get("team_staff_role_id")
|
||||||
|
|
||||||
|
if not category_id or not staff_role_id or not team_staff_role_id:
|
||||||
|
return await interaction.response.send_message("❌ Erreur : Le système de tickets n'est pas entièrement configuré sur ce serveur (Catégorie, Rôle Staff ou Rôle Équipe Staff manquant).", ephemeral=True)
|
||||||
|
|
||||||
|
category = discord.utils.get(guild.categories, id=int(category_id))
|
||||||
if category is None:
|
if category is None:
|
||||||
await interaction.response.send_message("❌ Erreur : La catégorie des tickets n'existe pas !", ephemeral=True)
|
return await interaction.response.send_message("❌ Erreur : La catégorie des tickets configurée n'existe plus !", ephemeral=True)
|
||||||
return
|
|
||||||
|
|
||||||
# Vérifier si le rôle staff existe
|
staff_role = discord.utils.get(guild.roles, id=int(staff_role_id))
|
||||||
staff_role = discord.utils.get(guild.roles, id=STAFF_ROLE_ID)
|
team_staff_role = discord.utils.get(guild.roles, id=int(team_staff_role_id))
|
||||||
if staff_role is None:
|
|
||||||
await interaction.response.send_message("❌ Erreur : Le rôle staff n'existe pas !", ephemeral=True)
|
if staff_role is None or team_staff_role is None:
|
||||||
return
|
return await interaction.response.send_message("❌ Erreur : L'un des rôles staff configurés n'existe plus !", ephemeral=True)
|
||||||
|
|
||||||
# Vérifier si l'utilisateur a déjà un ticket ouvert
|
# Vérifier si l'utilisateur a déjà un ticket ouvert
|
||||||
for channel in category.text_channels:
|
for channel in category.text_channels:
|
||||||
if channel.topic == f"Ticket de {user.id}":
|
if channel.topic == f"Ticket de {user.id}":
|
||||||
await interaction.response.send_message(f"❌ Vous avez déjà un ticket ouvert : {channel.mention}", ephemeral=True)
|
return await interaction.response.send_message(f"❌ Vous avez déjà un ticket ouvert : {channel.mention}", ephemeral=True)
|
||||||
return
|
|
||||||
|
|
||||||
# Définir les permissions du salon du ticket
|
# Définir les permissions du salon du ticket
|
||||||
overwrites = {
|
overwrites = {
|
||||||
guild.default_role: discord.PermissionOverwrite(read_messages=False),
|
guild.default_role: discord.PermissionOverwrite(read_messages=False),
|
||||||
user: discord.PermissionOverwrite(read_messages=True, send_messages=True),
|
user: discord.PermissionOverwrite(read_messages=True, send_messages=True),
|
||||||
staff_role: discord.PermissionOverwrite(read_messages=True, send_messages=True),
|
staff_role: discord.PermissionOverwrite(read_messages=True, send_messages=True),
|
||||||
discord.utils.get(guild.roles, id=TEAM_STAFF_ROLE_ID): discord.PermissionOverwrite(read_messages=True, send_messages=False)
|
team_staff_role: discord.PermissionOverwrite(read_messages=True, send_messages=False)
|
||||||
}
|
}
|
||||||
|
|
||||||
# Créer un salon de ticket avec le nom [pseudo_du_membre]-écrit
|
# Créer un salon de ticket avec le nom [pseudo]-écrit
|
||||||
ticket_channel = await category.create_text_channel(
|
ticket_channel = await category.create_text_channel(
|
||||||
name=f"{user.name}-écrit",
|
name=f"{user.name}-écrit",
|
||||||
topic=f"Ticket de {user.id}",
|
topic=f"Ticket de {user.id}",
|
||||||
overwrites=overwrites
|
overwrites=overwrites
|
||||||
)
|
)
|
||||||
|
|
||||||
# Récupérer le rôle "équipe staff"
|
|
||||||
team_staff_role = discord.utils.get(guild.roles, id=TEAM_STAFF_ROLE_ID)
|
|
||||||
|
|
||||||
# Création de l'embed avec les infos du formulaire
|
# Création de l'embed avec les infos du formulaire
|
||||||
embed = discord.Embed(title="📩 Ticket Whitelist", color=0x00ff00)
|
embed = discord.Embed(title="📩 Ticket Whitelist", color=0x00ff00)
|
||||||
embed.add_field(name="📋 - Informations Personnelles", value=(
|
embed.add_field(name="📋 - Informations Personnelles", value=(
|
||||||
|
|
@ -171,7 +298,7 @@ class TicketStep3(discord.ui.Modal, title="📋 Développement du Personnage"):
|
||||||
), inline=False)
|
), inline=False)
|
||||||
|
|
||||||
# Envoyer l'embed avec les boutons d'actions pour les staff
|
# Envoyer l'embed avec les boutons d'actions pour les staff
|
||||||
await ticket_channel.send(embed=embed, view=TicketManagementView(user))
|
await ticket_channel.send(embed=embed, view=TicketManagementView())
|
||||||
|
|
||||||
# Envoyer un message pour mentionner l'équipe staff
|
# Envoyer un message pour mentionner l'équipe staff
|
||||||
await ticket_channel.send(f"{team_staff_role.mention} - Nouveau ticket créé !")
|
await ticket_channel.send(f"{team_staff_role.mention} - Nouveau ticket créé !")
|
||||||
|
|
@ -181,9 +308,8 @@ class TicketStep3(discord.ui.Modal, title="📋 Développement du Personnage"):
|
||||||
new_nickname = f"{self.step2_part1_data.prenom.value} {self.step2_part1_data.nom.value} | {self.step1_data.pseudo.value}"
|
new_nickname = f"{self.step2_part1_data.prenom.value} {self.step2_part1_data.nom.value} | {self.step1_data.pseudo.value}"
|
||||||
if len(new_nickname) > 32:
|
if len(new_nickname) > 32:
|
||||||
new_nickname = new_nickname[:32]
|
new_nickname = new_nickname[:32]
|
||||||
await user.edit(nick=new_nickname)
|
try: await user.edit(nick=new_nickname)
|
||||||
else:
|
except discord.Forbidden: pass
|
||||||
await interaction.followup.send("❌ Le bot n'a pas les permissions nécessaires pour modifier le pseudo.", ephemeral=True)
|
|
||||||
|
|
||||||
# Envoyer un message de confirmation
|
# Envoyer un message de confirmation
|
||||||
try:
|
try:
|
||||||
|
|
@ -194,63 +320,99 @@ class TicketStep3(discord.ui.Modal, title="📋 Développement du Personnage"):
|
||||||
class TicketManagementView(discord.ui.View):
|
class TicketManagementView(discord.ui.View):
|
||||||
"""Vue pour gérer le ticket avec les boutons Fermer, Réouvrir, Claim et Supprimer"""
|
"""Vue pour gérer le ticket avec les boutons Fermer, Réouvrir, Claim et Supprimer"""
|
||||||
|
|
||||||
def __init__(self, ticket_owner):
|
def __init__(self):
|
||||||
super().__init__(timeout=None)
|
super().__init__(timeout=None)
|
||||||
self.ticket_owner = ticket_owner
|
|
||||||
self.claimed_by = None # Stocke le staff qui claim le ticket
|
|
||||||
|
|
||||||
@discord.ui.button(label="🔒 Fermer le Ticket", style=discord.ButtonStyle.red, custom_id="close_ticket")
|
@discord.ui.button(label="🔒 Fermer le Ticket", style=discord.ButtonStyle.red, custom_id="close_ticket")
|
||||||
async def close_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
|
async def close_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
"""Ferme le ticket en désactivant l'envoi de messages pour le membre"""
|
"""Ferme le ticket en désactivant l'envoi de messages pour le membre"""
|
||||||
if not discord.utils.get(interaction.user.roles, id=STAFF_ROLE_ID):
|
settings = load_settings(interaction.guild.id)
|
||||||
await interaction.response.send_message("❌ Seuls les staffs peuvent fermer un ticket.", ephemeral=True)
|
staff_role_id = settings.get("staff_role_id")
|
||||||
return
|
|
||||||
|
|
||||||
await interaction.channel.set_permissions(self.ticket_owner, read_messages=False, send_messages=False)
|
if not discord.utils.get(interaction.user.roles, id=int(staff_role_id) if staff_role_id else 0) and not interaction.user.guild_permissions.administrator:
|
||||||
|
return await interaction.response.send_message("❌ Seuls les staffs peuvent fermer un ticket.", ephemeral=True)
|
||||||
|
|
||||||
|
topic = interaction.channel.topic or ""
|
||||||
|
if not topic.startswith("Ticket de "):
|
||||||
|
return await interaction.response.send_message("❌ Impossible de trouver le propriétaire du ticket à fermer.", ephemeral=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
owner_id = int(topic.split("Ticket de ")[1])
|
||||||
|
ticket_owner = interaction.guild.get_member(owner_id)
|
||||||
|
if not ticket_owner:
|
||||||
|
ticket_owner = await interaction.guild.fetch_member(owner_id)
|
||||||
|
except (IndexError, ValueError, discord.NotFound):
|
||||||
|
return await interaction.response.send_message("❌ Membre introuvable.", ephemeral=True)
|
||||||
|
|
||||||
|
await interaction.channel.set_permissions(ticket_owner, read_messages=False, send_messages=False)
|
||||||
await interaction.channel.edit(name="ticket-fermé")
|
await interaction.channel.edit(name="ticket-fermé")
|
||||||
await interaction.response.send_message("🔒 Le ticket a été fermé. Vous ne pouvez plus le voir.", ephemeral=True)
|
await interaction.response.send_message("🔒 Le ticket a été fermé. Vous ne pouvez plus le voir.", ephemeral=True)
|
||||||
|
|
||||||
# Mettre à jour la vue pour afficher uniquement les boutons Réouvrir et Supprimer
|
# Mettre à jour la vue pour afficher uniquement les boutons Réouvrir et Supprimer
|
||||||
new_view = ClosedTicketView(self.ticket_owner)
|
new_view = ClosedTicketView()
|
||||||
await interaction.channel.send("Le ticket a été fermé.", view=new_view)
|
await interaction.channel.send("Le ticket a été fermé.", view=new_view)
|
||||||
|
|
||||||
@discord.ui.button(label="👤 Prendre en charge le Ticket", style=discord.ButtonStyle.blurple, custom_id="claim_ticket")
|
@discord.ui.button(label="👤 Prendre en charge le Ticket", style=discord.ButtonStyle.blurple, custom_id="claim_ticket")
|
||||||
async def claim_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
|
async def claim_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
"""Un staff prend en charge le ticket"""
|
"""Un staff prend en charge le ticket"""
|
||||||
if not discord.utils.get(interaction.user.roles, id=STAFF_ROLE_ID):
|
settings = load_settings(interaction.guild.id)
|
||||||
await interaction.response.send_message("❌ Seuls les staffs peuvent claim un ticket.", ephemeral=True)
|
staff_role_id = settings.get("staff_role_id")
|
||||||
return
|
|
||||||
self.claimed_by = interaction.user
|
if not discord.utils.get(interaction.user.roles, id=int(staff_role_id) if staff_role_id else 0) and not interaction.user.guild_permissions.administrator:
|
||||||
await interaction.channel.set_permissions(discord.utils.get(interaction.guild.roles, id=STAFF_ROLE_ID), send_messages=False, read_messages=True)
|
return await interaction.response.send_message("❌ Seuls les staffs peuvent claim un ticket.", ephemeral=True)
|
||||||
await interaction.channel.set_permissions(self.claimed_by, read_messages=True, send_messages=True)
|
|
||||||
await interaction.response.send_message(f"👤 {self.claimed_by.mention} a claim ce ticket.", ephemeral=False)
|
staff_role = discord.utils.get(interaction.guild.roles, id=int(staff_role_id)) if staff_role_id else None
|
||||||
|
|
||||||
|
claimed_by = interaction.user
|
||||||
|
if staff_role:
|
||||||
|
await interaction.channel.set_permissions(staff_role, send_messages=False, read_messages=True)
|
||||||
|
|
||||||
|
await interaction.channel.set_permissions(claimed_by, read_messages=True, send_messages=True)
|
||||||
|
await interaction.response.send_message(f"👤 {claimed_by.mention} a claim ce ticket.", ephemeral=False)
|
||||||
|
|
||||||
class ClosedTicketView(discord.ui.View):
|
class ClosedTicketView(discord.ui.View):
|
||||||
"""Vue pour les tickets fermés avec les boutons Réouvrir et Supprimer"""
|
"""Vue pour les tickets fermés avec les boutons Réouvrir et Supprimer"""
|
||||||
|
|
||||||
def __init__(self, ticket_owner):
|
def __init__(self):
|
||||||
super().__init__(timeout=None)
|
super().__init__(timeout=None)
|
||||||
self.ticket_owner = ticket_owner
|
|
||||||
|
|
||||||
@discord.ui.button(label="♻ Réouvrir le Ticket", style=discord.ButtonStyle.green, custom_id="reopen_ticket")
|
@discord.ui.button(label="♻ Réouvrir le Ticket", style=discord.ButtonStyle.green, custom_id="reopen_ticket")
|
||||||
async def reopen_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
|
async def reopen_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
"""Réouvre le ticket pour le membre"""
|
"""Réouvre le ticket pour le membre"""
|
||||||
if not discord.utils.get(interaction.user.roles, id=STAFF_ROLE_ID):
|
settings = load_settings(interaction.guild.id)
|
||||||
await interaction.response.send_message("❌ Seuls les staffs peuvent réouvrir un ticket.", ephemeral=True)
|
staff_role_id = settings.get("staff_role_id")
|
||||||
return
|
|
||||||
await interaction.channel.set_permissions(self.ticket_owner, read_messages=True, send_messages=True)
|
if not discord.utils.get(interaction.user.roles, id=int(staff_role_id) if staff_role_id else 0) and not interaction.user.guild_permissions.administrator:
|
||||||
|
return await interaction.response.send_message("❌ Seuls les staffs peuvent réouvrir un ticket.", ephemeral=True)
|
||||||
|
|
||||||
|
topic = interaction.channel.topic or ""
|
||||||
|
if not topic.startswith("Ticket de "):
|
||||||
|
return await interaction.response.send_message("❌ Impossible de trouver le propriétaire du ticket à réouvrir.", ephemeral=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
owner_id = int(topic.split("Ticket de ")[1])
|
||||||
|
ticket_owner = interaction.guild.get_member(owner_id)
|
||||||
|
if not ticket_owner:
|
||||||
|
ticket_owner = await interaction.guild.fetch_member(owner_id)
|
||||||
|
except (IndexError, ValueError, discord.NotFound):
|
||||||
|
return await interaction.response.send_message("❌ Membre introuvable.", ephemeral=True)
|
||||||
|
|
||||||
|
await interaction.channel.set_permissions(ticket_owner, read_messages=True, send_messages=True)
|
||||||
await interaction.response.send_message("✅ Le ticket a été réouvert.", ephemeral=True)
|
await interaction.response.send_message("✅ Le ticket a été réouvert.", ephemeral=True)
|
||||||
|
|
||||||
# Mettre à jour la vue pour afficher tous les boutons
|
# Mettre à jour la vue pour afficher tous les boutons
|
||||||
new_view = TicketManagementView(self.ticket_owner)
|
new_view = TicketManagementView()
|
||||||
await interaction.channel.send("Le ticket a été réouvert.", view=new_view)
|
await interaction.channel.send("Le ticket a été réouvert.", view=new_view)
|
||||||
|
|
||||||
@discord.ui.button(label="❌ Supprimer le Ticket", style=discord.ButtonStyle.danger, custom_id="delete_ticket")
|
@discord.ui.button(label="❌ Supprimer le Ticket", style=discord.ButtonStyle.danger, custom_id="delete_ticket")
|
||||||
async def delete_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
|
async def delete_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
"""Supprime le salon du ticket"""
|
"""Supprime le salon du ticket"""
|
||||||
if not discord.utils.get(interaction.user.roles, id=STAFF_ROLE_ID):
|
settings = load_settings(interaction.guild.id)
|
||||||
await interaction.response.send_message("❌ Seuls les staffs peuvent supprimer un ticket.", ephemeral=True)
|
staff_role_id = settings.get("staff_role_id")
|
||||||
return
|
|
||||||
|
if not discord.utils.get(interaction.user.roles, id=int(staff_role_id) if staff_role_id else 0) and not interaction.user.guild_permissions.administrator:
|
||||||
|
return await interaction.response.send_message("❌ Seuls les staffs peuvent supprimer un ticket.", ephemeral=True)
|
||||||
|
|
||||||
await interaction.response.send_message("❌ Suppression du ticket...", ephemeral=True)
|
await interaction.response.send_message("❌ Suppression du ticket...", ephemeral=True)
|
||||||
await interaction.channel.delete()
|
await interaction.channel.delete()
|
||||||
|
|
||||||
|
|
@ -272,6 +434,7 @@ class Ticket(commands.Cog):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
||||||
@app_commands.command(name="ticket_whitelist", description="Envoie l'embed pour ouvrir un ticket de whitelist.")
|
@app_commands.command(name="ticket_whitelist", description="Envoie l'embed pour ouvrir un ticket de whitelist.")
|
||||||
|
@app_commands.checks.has_permissions(administrator=True)
|
||||||
async def ticket_whitelist(self, interaction: discord.Interaction):
|
async def ticket_whitelist(self, interaction: discord.Interaction):
|
||||||
"""Envoie l'embed avec le bouton d'ouverture de ticket"""
|
"""Envoie l'embed avec le bouton d'ouverture de ticket"""
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
|
|
@ -283,6 +446,34 @@ class Ticket(commands.Cog):
|
||||||
await interaction.channel.send(embed=embed, view=view)
|
await interaction.channel.send(embed=embed, view=view)
|
||||||
await interaction.response.send_message("✅ Embed des tickets envoyé.", ephemeral=True)
|
await interaction.response.send_message("✅ Embed des tickets envoyé.", ephemeral=True)
|
||||||
|
|
||||||
|
@app_commands.command(name="ticket_whitelist_config", description="Configurer le système de tickets whitelist (Admin)")
|
||||||
|
@app_commands.checks.has_permissions(administrator=True)
|
||||||
|
async def ticket_whitelist_config(self, interaction: discord.Interaction):
|
||||||
|
"""Interface de configuration des tickets"""
|
||||||
|
settings = load_settings(interaction.guild.id)
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="⚙️ Configuration Tickets Whitelist",
|
||||||
|
description="Utilisez les boutons ci-dessous pour lier la catégorie et les rôles.",
|
||||||
|
color=discord.Color.blue()
|
||||||
|
)
|
||||||
|
|
||||||
|
cat_id = settings.get("ticket_category_id")
|
||||||
|
s_role_id = settings.get("staff_role_id")
|
||||||
|
ts_role_id = settings.get("team_staff_role_id")
|
||||||
|
ao_role_id = settings.get("attente_oral_role_id")
|
||||||
|
caf_role_id = settings.get("candidature_a_faire_role_id")
|
||||||
|
c_role_id = settings.get("citoyen_role_id")
|
||||||
|
|
||||||
|
embed.add_field(name="📁 Catégorie Tickets", value=f"Id: {cat_id}" if cat_id else "❌ Non configuré", inline=True)
|
||||||
|
embed.add_field(name="🛡️ Staff", value=f"<@&{s_role_id}>" if s_role_id else "❌ Non configuré", inline=True)
|
||||||
|
embed.add_field(name="👥 Équipe Staff", value=f"<@&{ts_role_id}>" if ts_role_id else "❌ Non configuré", inline=True)
|
||||||
|
embed.add_field(name="🎤 Attente Oral", value=f"<@&{ao_role_id}>" if ao_role_id else "❌ Non configuré", inline=True)
|
||||||
|
embed.add_field(name="📝 Candidature à faire", value=f"<@&{caf_role_id}>" if caf_role_id else "❌ Non configuré", inline=True)
|
||||||
|
embed.add_field(name="🏙️ Citoyen", value=f"<@&{c_role_id}>" if c_role_id else "❌ Non configuré", inline=True)
|
||||||
|
|
||||||
|
await interaction.response.send_message(embed=embed, view=TicketWhitelistConfigView(interaction.guild.id), ephemeral=True)
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_message(self, message):
|
async def on_message(self, message):
|
||||||
"""Surveille les messages des staffs dans les tickets"""
|
"""Surveille les messages des staffs dans les tickets"""
|
||||||
|
|
@ -291,39 +482,47 @@ class Ticket(commands.Cog):
|
||||||
if not message.channel.topic or not message.channel.topic.startswith("Ticket de"):
|
if not message.channel.topic or not message.channel.topic.startswith("Ticket de"):
|
||||||
return
|
return
|
||||||
|
|
||||||
staff_role = discord.utils.get(message.guild.roles, id=STAFF_ROLE_ID)
|
settings = load_settings(message.guild.id)
|
||||||
|
staff_role_id = settings.get("staff_role_id")
|
||||||
|
attente_oral_role_id = settings.get("attente_oral_role_id")
|
||||||
|
candidature_a_faire_role_id = settings.get("candidature_a_faire_role_id")
|
||||||
|
citoyen_role_id = settings.get("citoyen_role_id")
|
||||||
|
|
||||||
|
if not staff_role_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
staff_role = discord.utils.get(message.guild.roles, id=int(staff_role_id))
|
||||||
|
|
||||||
if staff_role in message.author.roles and "validé" in message.content.lower():
|
if staff_role in message.author.roles and "validé" in message.content.lower():
|
||||||
try:
|
try:
|
||||||
user_id = message.channel.topic.split("Ticket de ")[1]
|
user_id = message.channel.topic.split("Ticket de ")[1]
|
||||||
user = await message.guild.fetch_member(user_id)
|
user = await message.guild.fetch_member(int(user_id))
|
||||||
|
|
||||||
|
if not attente_oral_role_id or not candidature_a_faire_role_id or not citoyen_role_id:
|
||||||
|
await message.channel.send("❌ Erreur de configuration : Impossible d'attribuer les rôles, ils ne sont pas tous définis dans `/ticket_whitelist_config`.", delete_after=10)
|
||||||
|
return
|
||||||
|
|
||||||
# Vérifier si le rôle "attente oral" est déjà attribué
|
# Vérifier si le rôle "attente oral" est déjà attribué
|
||||||
attente_oral_role = discord.utils.get(message.guild.roles, id=ATTENTE_ORAL_ROLE_ID)
|
attente_oral_role = discord.utils.get(message.guild.roles, id=int(attente_oral_role_id))
|
||||||
candidature_a_faire_role = discord.utils.get(message.guild.roles, id=CANDIDATURE_A_FAIRE_ROLE_ID)
|
candidature_a_faire_role = discord.utils.get(message.guild.roles, id=int(candidature_a_faire_role_id))
|
||||||
citoyen_role = discord.utils.get(message.guild.roles, id=CITOYEN_ROLE_ID)
|
citoyen_role = discord.utils.get(message.guild.roles, id=int(citoyen_role_id))
|
||||||
|
|
||||||
if attente_oral_role in user.roles:
|
if attente_oral_role in user.roles:
|
||||||
# Deuxième "validé" détecté
|
# Deuxième "validé" détecté
|
||||||
await user.remove_roles(attente_oral_role)
|
if attente_oral_role: await user.remove_roles(attente_oral_role)
|
||||||
await user.add_roles(citoyen_role)
|
if citoyen_role: await user.add_roles(citoyen_role)
|
||||||
await message.channel.edit(name=f"{user.name}-citoyen")
|
await message.channel.edit(name=f"{user.name}-citoyen")
|
||||||
else:
|
else:
|
||||||
# Premier "validé" détecté
|
# Premier "validé" détecté
|
||||||
await user.remove_roles(candidature_a_faire_role)
|
if candidature_a_faire_role: await user.remove_roles(candidature_a_faire_role)
|
||||||
await user.add_roles(attente_oral_role)
|
if attente_oral_role: await user.add_roles(attente_oral_role)
|
||||||
await message.channel.edit(name=f"{user.name}-oral")
|
await message.channel.edit(name=f"{user.name}-oral")
|
||||||
|
|
||||||
# Mettre à jour le pseudo du joueur si le bot a les permissions nécessaires
|
|
||||||
if message.guild.me.guild_permissions.manage_nicknames:
|
|
||||||
new_nickname = f"{self.step2_part1_data.prenom.value} {self.step2_part1_data.nom.value} | {self.step1_data.pseudo.value}"
|
|
||||||
if len(new_nickname) > 32:
|
|
||||||
new_nickname = new_nickname[:32]
|
|
||||||
await user.edit(nick=new_nickname)
|
|
||||||
else:
|
|
||||||
await message.channel.send("❌ Le bot n'a pas les permissions nécessaires pour modifier le pseudo.", delete_after=10)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Erreur lors du renommage du ticket: {e}")
|
print(f"Erreur lors du traitement du ticket : {e}")
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
await bot.add_cog(Ticket(bot))
|
await bot.add_cog(Ticket(bot))
|
||||||
|
bot.add_view(TicketView())
|
||||||
|
bot.add_view(TicketManagementView())
|
||||||
|
bot.add_view(ClosedTicketView())
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"date": "2026-03-01T00:26:14.746054+00:00",
|
"date": "2026-03-14T14:26:12.694595+00:00",
|
||||||
"trigger": "auto",
|
"trigger": "auto",
|
||||||
"serversCount": 1,
|
"serversCount": 1,
|
||||||
"membersCount": 13,
|
"membersCount": 13,
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"success": true
|
"success": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-03-01T00:25:45.072770+00:00",
|
"date": "2026-03-14T14:25:42.467679+00:00",
|
||||||
"trigger": "auto",
|
"trigger": "auto",
|
||||||
"serversCount": 1,
|
"serversCount": 1,
|
||||||
"membersCount": 13,
|
"membersCount": 13,
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
"success": true
|
"success": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-03-01T00:25:15.071380+00:00",
|
"date": "2026-03-14T14:25:11.992208+00:00",
|
||||||
"trigger": "auto",
|
"trigger": "auto",
|
||||||
"serversCount": 1,
|
"serversCount": 1,
|
||||||
"membersCount": 13,
|
"membersCount": 13,
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
"success": true
|
"success": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-03-01T00:24:45.388984+00:00",
|
"date": "2026-03-14T14:24:42.190744+00:00",
|
||||||
"trigger": "auto",
|
"trigger": "auto",
|
||||||
"serversCount": 1,
|
"serversCount": 1,
|
||||||
"membersCount": 13,
|
"membersCount": 13,
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
"success": true
|
"success": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-03-01T00:24:14.975026+00:00",
|
"date": "2026-03-14T14:24:12.614886+00:00",
|
||||||
"trigger": "auto",
|
"trigger": "auto",
|
||||||
"serversCount": 1,
|
"serversCount": 1,
|
||||||
"membersCount": 13,
|
"membersCount": 13,
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
"success": true
|
"success": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-03-01T00:23:44.713904+00:00",
|
"date": "2026-03-14T14:23:42.012764+00:00",
|
||||||
"trigger": "auto",
|
"trigger": "auto",
|
||||||
"serversCount": 1,
|
"serversCount": 1,
|
||||||
"membersCount": 13,
|
"membersCount": 13,
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
"success": true
|
"success": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-03-01T00:23:16.510199+00:00",
|
"date": "2026-03-14T14:23:12.416066+00:00",
|
||||||
"trigger": "auto",
|
"trigger": "auto",
|
||||||
"serversCount": 1,
|
"serversCount": 1,
|
||||||
"membersCount": 13,
|
"membersCount": 13,
|
||||||
|
|
@ -63,7 +63,7 @@
|
||||||
"success": true
|
"success": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-03-01T00:22:44.966638+00:00",
|
"date": "2026-03-14T14:22:42.200744+00:00",
|
||||||
"trigger": "auto",
|
"trigger": "auto",
|
||||||
"serversCount": 1,
|
"serversCount": 1,
|
||||||
"membersCount": 13,
|
"membersCount": 13,
|
||||||
|
|
@ -72,7 +72,7 @@
|
||||||
"success": true
|
"success": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-03-01T00:22:14.857332+00:00",
|
"date": "2026-03-14T14:22:12.128982+00:00",
|
||||||
"trigger": "auto",
|
"trigger": "auto",
|
||||||
"serversCount": 1,
|
"serversCount": 1,
|
||||||
"membersCount": 13,
|
"membersCount": 13,
|
||||||
|
|
@ -81,7 +81,7 @@
|
||||||
"success": true
|
"success": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-03-01T00:21:44.958489+00:00",
|
"date": "2026-03-14T14:21:41.879828+00:00",
|
||||||
"trigger": "auto",
|
"trigger": "auto",
|
||||||
"serversCount": 1,
|
"serversCount": 1,
|
||||||
"membersCount": 13,
|
"membersCount": 13,
|
||||||
|
|
@ -90,7 +90,7 @@
|
||||||
"success": true
|
"success": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-03-01T00:21:14.844468+00:00",
|
"date": "2026-03-14T14:21:25.894956+00:00",
|
||||||
"trigger": "auto",
|
"trigger": "auto",
|
||||||
"serversCount": 1,
|
"serversCount": 1,
|
||||||
"membersCount": 13,
|
"membersCount": 13,
|
||||||
|
|
@ -99,7 +99,7 @@
|
||||||
"success": true
|
"success": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-03-01T00:20:44.959093+00:00",
|
"date": "2026-03-14T14:20:56.179577+00:00",
|
||||||
"trigger": "auto",
|
"trigger": "auto",
|
||||||
"serversCount": 1,
|
"serversCount": 1,
|
||||||
"membersCount": 13,
|
"membersCount": 13,
|
||||||
|
|
@ -108,7 +108,7 @@
|
||||||
"success": true
|
"success": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-03-01T00:20:14.948095+00:00",
|
"date": "2026-03-14T14:20:25.382325+00:00",
|
||||||
"trigger": "auto",
|
"trigger": "auto",
|
||||||
"serversCount": 1,
|
"serversCount": 1,
|
||||||
"membersCount": 13,
|
"membersCount": 13,
|
||||||
|
|
@ -117,7 +117,61 @@
|
||||||
"success": true
|
"success": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-03-01T00:19:44.835750+00:00",
|
"date": "2026-03-14T14:19:55.416432+00:00",
|
||||||
|
"trigger": "auto",
|
||||||
|
"serversCount": 1,
|
||||||
|
"membersCount": 13,
|
||||||
|
"guildName": "Omega Kube Bêta Test",
|
||||||
|
"errors": [],
|
||||||
|
"success": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-03-14T14:19:25.281242+00:00",
|
||||||
|
"trigger": "auto",
|
||||||
|
"serversCount": 1,
|
||||||
|
"membersCount": 13,
|
||||||
|
"guildName": "Omega Kube Bêta Test",
|
||||||
|
"errors": [],
|
||||||
|
"success": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-03-14T14:18:55.315861+00:00",
|
||||||
|
"trigger": "auto",
|
||||||
|
"serversCount": 1,
|
||||||
|
"membersCount": 13,
|
||||||
|
"guildName": "Omega Kube Bêta Test",
|
||||||
|
"errors": [],
|
||||||
|
"success": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-03-14T14:18:25.672058+00:00",
|
||||||
|
"trigger": "auto",
|
||||||
|
"serversCount": 1,
|
||||||
|
"membersCount": 13,
|
||||||
|
"guildName": "Omega Kube Bêta Test",
|
||||||
|
"errors": [],
|
||||||
|
"success": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-03-14T14:17:55.409042+00:00",
|
||||||
|
"trigger": "auto",
|
||||||
|
"serversCount": 1,
|
||||||
|
"membersCount": 13,
|
||||||
|
"guildName": "Omega Kube Bêta Test",
|
||||||
|
"errors": [],
|
||||||
|
"success": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-03-14T14:17:25.447376+00:00",
|
||||||
|
"trigger": "auto",
|
||||||
|
"serversCount": 1,
|
||||||
|
"membersCount": 13,
|
||||||
|
"guildName": "Omega Kube Bêta Test",
|
||||||
|
"errors": [],
|
||||||
|
"success": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-03-14T14:16:55.368379+00:00",
|
||||||
"trigger": "auto",
|
"trigger": "auto",
|
||||||
"serversCount": 1,
|
"serversCount": 1,
|
||||||
"membersCount": 13,
|
"membersCount": 13,
|
||||||
|
|
|
||||||
101
serveur_flask.py
Normal file
101
serveur_flask.py
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
import os, subprocess, threading, requests
|
||||||
|
from flask import Flask, request, abort
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# --- CONFIGURATION ---
|
||||||
|
PROJECT_PATH = "/home/discord/Bot/Kuby_V2"
|
||||||
|
WEBHOOK_SECRET = "Nois1"
|
||||||
|
PROCESS_NAME = "kuby-bot"
|
||||||
|
DISCORD_LOG_URL = "https://discord.com/api/webhooks/1482148910888652910/IA9CcOWtjGswbuxMaOu_6uaclv5zojZo4ttxtV6RYyZzJ9gW7BF7xp_Zv3oPIjYEuh8o"
|
||||||
|
|
||||||
|
# Bot Local Proxy
|
||||||
|
BOT_LOCAL_URL = "http://127.0.0.1:5001/bot_event"
|
||||||
|
|
||||||
|
def send_discord_log(status, message, color, author=None, commit_msg=None):
|
||||||
|
embed = {
|
||||||
|
"title": f"🚀 Kuby V2 : {status}",
|
||||||
|
"description": message,
|
||||||
|
"color": color,
|
||||||
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||||
|
"fields": []
|
||||||
|
}
|
||||||
|
|
||||||
|
if author:
|
||||||
|
embed["fields"].append({"name": "👤 Auteur", "value": author, "inline": True})
|
||||||
|
if commit_msg:
|
||||||
|
embed["fields"].append({"name": "📝 Commit", "value": commit_msg, "inline": True})
|
||||||
|
|
||||||
|
payload = {"embeds": [embed]}
|
||||||
|
try:
|
||||||
|
requests.post(DISCORD_LOG_URL, json=payload, timeout=5)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[!] Erreur envoi Discord : {e}")
|
||||||
|
|
||||||
|
def run_mep_logic(author, commit_msg):
|
||||||
|
start_time = datetime.now()
|
||||||
|
send_discord_log("EN COURS", "Déploiement lancé sur la machine de guerre...", 3447003, author, commit_msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.chdir(PROJECT_PATH)
|
||||||
|
|
||||||
|
# 1. GIT
|
||||||
|
subprocess.run(["git", "fetch", "origin"], check=True)
|
||||||
|
subprocess.run(["git", "reset", "--hard", "origin/main"], check=True)
|
||||||
|
|
||||||
|
# 2. PM2
|
||||||
|
subprocess.run(f"pm2 restart {PROCESS_NAME}", shell=True, check=True, capture_output=True)
|
||||||
|
|
||||||
|
duration = (datetime.now() - start_time).total_seconds()
|
||||||
|
msg = f"✅ **Déploiement réussi en {duration:.2f}s**\nLe bot est à jour et redémarré."
|
||||||
|
send_discord_log("TERMINÉ", msg, 3066993, author, commit_msg)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"❌ **ERREUR de déploiement**\n```python\n{str(e)}\n```"
|
||||||
|
send_discord_log("ÉCHEC", error_msg, 15158332, author, commit_msg)
|
||||||
|
|
||||||
|
@app.route('/deploy', methods=['POST'])
|
||||||
|
def deploy():
|
||||||
|
# Sécurité
|
||||||
|
if request.headers.get('X-Gitlab-Token') != WEBHOOK_SECRET:
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
# Extraction des infos GitLab
|
||||||
|
data = request.json
|
||||||
|
author = "Inconnu"
|
||||||
|
commit_msg = "Pas de message"
|
||||||
|
|
||||||
|
if data and 'commits' in data and len(data['commits']) > 0:
|
||||||
|
author = data['commits'][0].get('author', {}).get('name', 'Inconnu')
|
||||||
|
commit_msg = data['commits'][0].get('message', 'Pas de message')
|
||||||
|
|
||||||
|
# Threading pour répondre vite à GitLab
|
||||||
|
threading.Thread(target=run_mep_logic, args=(author, commit_msg)).start()
|
||||||
|
|
||||||
|
return {"status": "accepted"}, 202
|
||||||
|
|
||||||
|
@app.route('/gitlab_webhook', methods=['POST'])
|
||||||
|
def gitlab_webhook():
|
||||||
|
# Protection stricte Anti-Crawler
|
||||||
|
user_agent = request.headers.get('User-Agent', '')
|
||||||
|
if 'GitLab' not in user_agent:
|
||||||
|
abort(404) # Retourne une 404 pour simuler que la page n'existe pas
|
||||||
|
|
||||||
|
if request.headers.get('X-Gitlab-Token') != WEBHOOK_SECRET:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
# Transférer la payload au bot Discord localement sur le port 5001
|
||||||
|
payload = request.json
|
||||||
|
try:
|
||||||
|
# Timeout très court car c'est en local
|
||||||
|
requests.post(BOT_LOCAL_URL, json=payload, timeout=2)
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"[!] Erreur de relais vers le bot Discord : {e}")
|
||||||
|
# On renvoie 200 à GitLab quand même car GitLab n'a pas à savoir l'état interne
|
||||||
|
|
||||||
|
return {"status": "received"}, 200
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print(f"[*] Webhook opérationnel. En attente de push ou d'events...")
|
||||||
|
app.run(host='0.0.0.0', port=5000)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue