Merge branch 'dev' into 'main'
Derniers ajouts en date See merge request Omega_Kube/kuby!18
This commit is contained in:
commit
e87ac6f86e
3 changed files with 202 additions and 90 deletions
|
|
@ -12,6 +12,7 @@ kuby_logger = logging.getLogger("KubyBot")
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
REPORTS_FILE = os.path.join(BASE_DIR, "data", "gitlab_reports.json")
|
REPORTS_FILE = os.path.join(BASE_DIR, "data", "gitlab_reports.json")
|
||||||
|
|
||||||
|
|
||||||
def save_report(issue_iid, user_id):
|
def save_report(issue_iid, user_id):
|
||||||
os.makedirs(os.path.dirname(REPORTS_FILE), exist_ok=True)
|
os.makedirs(os.path.dirname(REPORTS_FILE), exist_ok=True)
|
||||||
try:
|
try:
|
||||||
|
|
@ -22,9 +23,9 @@ def save_report(issue_iid, user_id):
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
kuby_logger.warning("REPORTS_FILE corrupted, re-initializing to avoid tracking loss.")
|
kuby_logger.warning("REPORTS_FILE corrupted, re-initializing to avoid tracking loss.")
|
||||||
|
|
||||||
data[str(issue_iid)] = user_id
|
data[str(issue_iid)] = user_id
|
||||||
|
|
||||||
temp_file = f"{REPORTS_FILE}.tmp"
|
temp_file = f"{REPORTS_FILE}.tmp"
|
||||||
with open(temp_file, "w", encoding="utf-8") as f:
|
with open(temp_file, "w", encoding="utf-8") as f:
|
||||||
json.dump(data, f, indent=4)
|
json.dump(data, f, indent=4)
|
||||||
|
|
@ -32,48 +33,75 @@ def save_report(issue_iid, user_id):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
kuby_logger.error(f"Error saving report to JSON: {e}")
|
kuby_logger.error(f"Error saving report to JSON: {e}")
|
||||||
|
|
||||||
|
|
||||||
class BugReportModal(disnake.ui.Modal):
|
class BugReportModal(disnake.ui.Modal):
|
||||||
def __init__(self, priority_choice):
|
def __init__(self, priority_choice):
|
||||||
super().__init__(title="Signaler un Bug", components=[self.bug_title, self.description])
|
super().__init__(title="Signaler un Bug", components=[self.bug_title, self.description])
|
||||||
self.priority_choice = priority_choice
|
|
||||||
|
# Sécurisation: éviter les valeurs None qui finissent dans GitLab
|
||||||
|
if priority_choice is None:
|
||||||
|
self.priority_choice = type(
|
||||||
|
"PriorityChoice",
|
||||||
|
(),
|
||||||
|
{"name": "Normal", "value": "Normal"},
|
||||||
|
)()
|
||||||
|
else:
|
||||||
|
self.priority_choice = priority_choice
|
||||||
|
|
||||||
bug_title = disnake.ui.TextInput(
|
bug_title = disnake.ui.TextInput(
|
||||||
label="Titre du bug",
|
label="Titre du bug",
|
||||||
placeholder="Décrivez brièvement le problème...",
|
placeholder="Décrivez brièvement le problème...",
|
||||||
required=True,
|
required=True,
|
||||||
max_length=100
|
max_length=100,
|
||||||
)
|
)
|
||||||
|
|
||||||
description = disnake.ui.TextInput(
|
description = disnake.ui.TextInput(
|
||||||
label="Description détaillée",
|
label="Description détaillée",
|
||||||
style=disnake.TextInputStyle.paragraph,
|
style=disnake.TextInputStyle.paragraph,
|
||||||
placeholder="Que s'est-il passé ? Comment reproduire le bug ?",
|
placeholder="Que s'est-il passé ? Comment reproduire le bug ?",
|
||||||
required=True,
|
required=True,
|
||||||
max_length=1000
|
max_length=1000,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def callback(self, interaction: disnake.Interaction):
|
async def callback(self, interaction: disnake.Interaction):
|
||||||
await interaction.response.send_message("Envoi de votre rapport de bug à GitLab...", ephemeral=True)
|
await interaction.response.send_message("Envoi de votre rapport de bug à GitLab...", ephemeral=True)
|
||||||
|
|
||||||
|
bug_title = (self.bug_title.value or "").strip()
|
||||||
|
if not bug_title:
|
||||||
|
bug_title = "Sans titre"
|
||||||
|
|
||||||
|
priority_name = getattr(self.priority_choice, "name", None) or "Normal"
|
||||||
|
priority_value = getattr(self.priority_choice, "value", None) or "Normal"
|
||||||
|
|
||||||
|
description_value = self.description.value or ""
|
||||||
|
|
||||||
description_text = f"**Rapporté par:** {interaction.user} ({interaction.user.id})\n"
|
description_text = f"**Rapporté par:** {interaction.user} ({interaction.user.id})\n"
|
||||||
description_text += f"**Priorité:** {self.priority_choice.name}\n\n"
|
description_text += f"**Priorité:** {priority_name}\n\n"
|
||||||
description_text += f"**Description:**\n{self.description.value}"
|
description_text += f"**Description:**\n{description_value}"
|
||||||
|
|
||||||
# Mapping des priorités vers des labels GitLab stylisés (Scoped Labels)
|
labels = ["bug", "manual-report", f"Priority::{priority_value}"]
|
||||||
labels = ["bug", "manual-report", f"Priority::{self.priority_choice.value}"]
|
|
||||||
|
|
||||||
result = await gitlab_client.create_issue(
|
result = await gitlab_client.create_issue(
|
||||||
title=f"[MANUAL] {self.bug_title.value}",
|
title=f"[MANUAL] {bug_title}",
|
||||||
description=description_text,
|
description=description_text,
|
||||||
labels=labels
|
labels=labels,
|
||||||
)
|
)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
issue_iid = result.get("iid")
|
issue_iid = result.get("iid")
|
||||||
save_report(issue_iid, interaction.user.id)
|
save_report(issue_iid, interaction.user.id)
|
||||||
await interaction.edit_original_response(content=f"✅ Votre bug a été signalé avec succès ! Gameur a bien été averti et le bug sera réglé dans la prochaine mise à jour. [Voir l'issue]({result.get('web_url')})")
|
await interaction.edit_original_response(
|
||||||
|
content=(
|
||||||
|
"✅ Votre bug a été signalé avec succès ! "
|
||||||
|
"Gameur a bien été averti et le bug sera réglé dans la prochaine mise à jour. "
|
||||||
|
f"[Voir l'issue]({result.get('web_url')})"
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await interaction.edit_original_response(content="❌ Une erreur est survenue lors de l'envoi du rapport à GitLab. Veuillez contacter un administrateur.")
|
await interaction.edit_original_response(
|
||||||
|
content="❌ Une erreur est survenue lors de l'envoi du rapport à GitLab. Veuillez contacter un administrateur."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FeatureSuggestionModal(disnake.ui.Modal):
|
class FeatureSuggestionModal(disnake.ui.Modal):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
@ -83,37 +111,49 @@ class FeatureSuggestionModal(disnake.ui.Modal):
|
||||||
label="Titre de la suggestion",
|
label="Titre de la suggestion",
|
||||||
placeholder="Que voulez-vous ajouter ?",
|
placeholder="Que voulez-vous ajouter ?",
|
||||||
required=True,
|
required=True,
|
||||||
max_length=100
|
max_length=100,
|
||||||
)
|
)
|
||||||
|
|
||||||
description = disnake.ui.TextInput(
|
description = disnake.ui.TextInput(
|
||||||
label="Détails de la fonctionnalité",
|
label="Détails de la fonctionnalité",
|
||||||
style=disnake.TextInputStyle.paragraph,
|
style=disnake.TextInputStyle.paragraph,
|
||||||
placeholder="Décrivez comment cela devrait fonctionner...",
|
placeholder="Décrivez comment cela devrait fonctionner...",
|
||||||
required=True,
|
required=True,
|
||||||
max_length=1000
|
max_length=1000,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def callback(self, interaction: disnake.Interaction):
|
async def callback(self, interaction: disnake.Interaction):
|
||||||
await interaction.response.send_message("Envoi de votre suggestion à GitLab...", ephemeral=True)
|
await interaction.response.send_message("Envoi de votre suggestion à GitLab...", ephemeral=True)
|
||||||
|
|
||||||
|
suggestion_title = (self.suggestion_title.value or "").strip() or "Sans titre"
|
||||||
|
description_value = self.description.value or ""
|
||||||
|
|
||||||
description_text = f"**Suggéré par:** {interaction.user} ({interaction.user.id})\n\n"
|
description_text = f"**Suggéré par:** {interaction.user} ({interaction.user.id})\n\n"
|
||||||
description_text += f"**Description:**\n{self.description.value}"
|
description_text += f"**Description:**\n{description_value}"
|
||||||
|
|
||||||
labels = ["enhancement", "manual-suggestion"]
|
labels = ["enhancement", "manual-suggestion"]
|
||||||
|
|
||||||
result = await gitlab_client.create_issue(
|
result = await gitlab_client.create_issue(
|
||||||
title=f"[SUGGESTION] {self.suggestion_title.value}",
|
title=f"[SUGGESTION] {suggestion_title}",
|
||||||
description=description_text,
|
description=description_text,
|
||||||
labels=labels
|
labels=labels,
|
||||||
)
|
)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
issue_iid = result.get("iid")
|
issue_iid = result.get("iid")
|
||||||
save_report(issue_iid, interaction.user.id)
|
save_report(issue_iid, interaction.user.id)
|
||||||
await interaction.edit_original_response(content=f"✅ Votre suggestion a été envoyée avec succès ! Merci de contribuer à l'amélioration du bot. [Voir l'issue]({result.get('web_url')})")
|
await interaction.edit_original_response(
|
||||||
|
content=(
|
||||||
|
"✅ Votre suggestion a été envoyée avec succès ! "
|
||||||
|
"Merci de contribuer à l'amélioration du bot. "
|
||||||
|
f"[Voir l'issue]({result.get('web_url')})"
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await interaction.edit_original_response(content="❌ Une erreur est survenue lors de l'envoi de la suggestion à GitLab.")
|
await interaction.edit_original_response(
|
||||||
|
content="❌ Une erreur est survenue lors de l'envoi de la suggestion à GitLab."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BugReport(commands.Cog):
|
class BugReport(commands.Cog):
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
|
|
@ -124,20 +164,24 @@ class BugReport(commands.Cog):
|
||||||
self.web_app.router.add_post('/bot_event', self.handle_bot_event)
|
self.web_app.router.add_post('/bot_event', self.handle_bot_event)
|
||||||
self.runner = web.AppRunner(self.web_app)
|
self.runner = web.AppRunner(self.web_app)
|
||||||
await self.runner.setup()
|
await self.runner.setup()
|
||||||
|
|
||||||
max_retries = 5
|
max_retries = 5
|
||||||
retry_delay = 2
|
retry_delay = 2
|
||||||
|
|
||||||
for attempt in range(1, max_retries + 1):
|
for attempt in range(1, max_retries + 1):
|
||||||
try:
|
try:
|
||||||
self.site = web.TCPSite(self.runner, '127.0.0.1', 5001)
|
self.site = web.TCPSite(self.runner, '127.0.0.1', 5001)
|
||||||
await self.site.start()
|
await self.site.start()
|
||||||
kuby_logger.info(f"Internal Bot Webhook Server started on 127.0.0.1:5001 (Attempt {attempt})")
|
kuby_logger.info(
|
||||||
|
f"Internal Bot Webhook Server started on 127.0.0.1:5001 (Attempt {attempt})"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if e.errno == 98: # Address already in use
|
if e.errno == 98: # Address already in use
|
||||||
if attempt < max_retries:
|
if attempt < max_retries:
|
||||||
kuby_logger.warning(f"Port 5001 already in use, retrying in {retry_delay}s... ({attempt}/{max_retries})")
|
kuby_logger.warning(
|
||||||
|
f"Port 5001 already in use, retrying in {retry_delay}s... ({attempt}/{max_retries})"
|
||||||
|
)
|
||||||
await asyncio.sleep(retry_delay)
|
await asyncio.sleep(retry_delay)
|
||||||
else:
|
else:
|
||||||
kuby_logger.error(f"Failed to bind to port 5001 after {max_retries} attempts.")
|
kuby_logger.error(f"Failed to bind to port 5001 after {max_retries} attempts.")
|
||||||
|
|
@ -147,7 +191,6 @@ class BugReport(commands.Cog):
|
||||||
|
|
||||||
def cog_unload(self):
|
def cog_unload(self):
|
||||||
if hasattr(self, 'runner'):
|
if hasattr(self, 'runner'):
|
||||||
# On utilise create_task car cog_unload est synchrone
|
|
||||||
async def cleanup():
|
async def cleanup():
|
||||||
try:
|
try:
|
||||||
if hasattr(self, 'site'):
|
if hasattr(self, 'site'):
|
||||||
|
|
@ -156,16 +199,14 @@ class BugReport(commands.Cog):
|
||||||
kuby_logger.info("Internal Bot Webhook Server stopped and cleaned up.")
|
kuby_logger.info("Internal Bot Webhook Server stopped and cleaned up.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
kuby_logger.error(f"Error during webhook server cleanup: {e}")
|
kuby_logger.error(f"Error during webhook server cleanup: {e}")
|
||||||
|
|
||||||
self.bot.loop.create_task(cleanup())
|
self.bot.loop.create_task(cleanup())
|
||||||
|
|
||||||
async def handle_bot_event(self, request):
|
async def handle_bot_event(self, request):
|
||||||
# Sécurité interne désactivée comme demandé (le port 5001 n'est accessible que via localhost)
|
|
||||||
provided_token = request.headers.get("X-Gitlab-Token")
|
provided_token = request.headers.get("X-Gitlab-Token")
|
||||||
# On ignore la vérification du token pour faciliter le relais local
|
# On ignore la vérification du token pour faciliter le relais local
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# On attend que le bot soit prêt si on vient de rebooter
|
|
||||||
if not self.bot.is_ready():
|
if not self.bot.is_ready():
|
||||||
kuby_logger.info("Webhook received but bot is not ready yet. Waiting up to 10s...")
|
kuby_logger.info("Webhook received but bot is not ready yet. Waiting up to 10s...")
|
||||||
try:
|
try:
|
||||||
|
|
@ -174,7 +215,7 @@ class BugReport(commands.Cog):
|
||||||
kuby_logger.warning("Bot still not ready after 10s. Proceeding anyway (best effort).")
|
kuby_logger.warning("Bot still not ready after 10s. Proceeding anyway (best effort).")
|
||||||
|
|
||||||
payload = await request.json()
|
payload = await request.json()
|
||||||
|
|
||||||
event_type = payload.get("object_kind")
|
event_type = payload.get("object_kind")
|
||||||
if event_type not in ["issue", "note"]:
|
if event_type not in ["issue", "note"]:
|
||||||
return web.json_response({"status": "ignored"}, status=200)
|
return web.json_response({"status": "ignored"}, status=200)
|
||||||
|
|
@ -220,41 +261,58 @@ class BugReport(commands.Cog):
|
||||||
if event_type == "issue":
|
if event_type == "issue":
|
||||||
action = payload.get("object_attributes", {}).get("action")
|
action = payload.get("object_attributes", {}).get("action")
|
||||||
changes = payload.get("changes", {})
|
changes = payload.get("changes", {})
|
||||||
|
|
||||||
# Check for label changes
|
|
||||||
if "labels" in changes:
|
if "labels" in changes:
|
||||||
curr_labels = [l.get("title") for l in changes["labels"].get("current", []) if l.get("title")]
|
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"]]
|
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:
|
if display_labels:
|
||||||
current_labels_str = ", ".join(display_labels)
|
current_labels_str = ", ".join(display_labels)
|
||||||
else:
|
else:
|
||||||
current_labels_str = "Mis à jour"
|
current_labels_str = "Mis à jour"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# V2 Components migration
|
|
||||||
components = [
|
components = [
|
||||||
disnake.ui.Container(
|
disnake.ui.Container(
|
||||||
disnake.ui.Section(
|
disnake.ui.Section(
|
||||||
"🛠️ Mise à jour de votre signalement !",
|
"🛠️ Mise à jour de votre signalement !",
|
||||||
accessory=disnake.ui.Thumbnail(self.bot.user.display_avatar.url)
|
accessory=disnake.ui.Thumbnail(self.bot.user.display_avatar.url),
|
||||||
),
|
),
|
||||||
disnake.ui.Separator(divider=True),
|
disnake.ui.Separator(divider=True),
|
||||||
disnake.ui.TextDisplay(f"Le statut de votre rapport **{issue_title}** a été mis à jour par l'équipe."),
|
disnake.ui.TextDisplay(
|
||||||
disnake.ui.TextDisplay(f"📌 **Nouveaux Labels / Statuts :** {current_labels_str}")
|
f"Le statut de votre rapport **{issue_title}** a été mis à jour par l'équipe."
|
||||||
|
),
|
||||||
|
disnake.ui.TextDisplay(
|
||||||
|
f"📌 **Nouveaux Labels / Statuts :** {current_labels_str}"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await user.send(components=components)
|
await user.send(components=components)
|
||||||
kuby_logger.info(f"Notification de label envoyée à {user} ({user.id}) pour l'issue #{issue_iid}")
|
kuby_logger.info(
|
||||||
|
f"Notification de label envoyée à {user} ({user.id}) pour l'issue #{issue_iid}"
|
||||||
|
)
|
||||||
if dev and dev.id != user.id:
|
if dev and dev.id != user.id:
|
||||||
await dev.send(f"✅ [SUIVI] L'utilisateur {user} ({user.id}) a bien reçu la notification de statut **{current_labels_str}** pour l'issue #{issue_iid} ({issue_title}).")
|
await dev.send(
|
||||||
|
f"✅ [SUIVI] L'utilisateur {user} ({user.id}) a bien reçu la notification de statut "
|
||||||
|
f"**{current_labels_str}** pour l'issue #{issue_iid} ({issue_title})."
|
||||||
|
)
|
||||||
except (disnake.Forbidden, disnake.HTTPException) as e:
|
except (disnake.Forbidden, disnake.HTTPException) as e:
|
||||||
if isinstance(e, disnake.Forbidden) or (isinstance(e, disnake.HTTPException) and e.code == 50007):
|
if isinstance(e, disnake.Forbidden) or (
|
||||||
kuby_logger.warning(f"Impossible d'envoyer un MP à {user} ({user.id}) - MPs désactivés (Code: {getattr(e, 'code', 'Unknown')})")
|
isinstance(e, disnake.HTTPException) and e.code == 50007
|
||||||
|
):
|
||||||
|
kuby_logger.warning(
|
||||||
|
f"Impossible d'envoyer un MP à {user} ({user.id}) - MPs désactivés (Code: {getattr(e, 'code', 'Unknown')})"
|
||||||
|
)
|
||||||
if dev and dev.id != user.id:
|
if dev and dev.id != user.id:
|
||||||
await dev.send(f"⚠️ [DM BLOQUÉ] {user} ({user.id}) a ses MPs fermés. Impossible de notifier pour l'issue #{issue_iid}.")
|
await dev.send(
|
||||||
|
f"⚠️ [DM BLOQUÉ] {user} ({user.id}) a ses MPs fermés. Impossible de notifier pour l'issue #{issue_iid}."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
kuby_logger.error(f"Erreur HTTP lors de l'envoi du MP de statut à {user}: {e}")
|
kuby_logger.error(f"Erreur HTTP lors de l'envoi du MP de statut à {user}: {e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -262,35 +320,46 @@ class BugReport(commands.Cog):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
kuby_logger.error(f"Erreur inattendue dans la notification de label : {e}")
|
kuby_logger.error(f"Erreur inattendue dans la notification de label : {e}")
|
||||||
|
|
||||||
# Check if it was closed
|
|
||||||
state = payload.get("object_attributes", {}).get("state")
|
state = payload.get("object_attributes", {}).get("state")
|
||||||
is_closing_now = action == "close" or (state == "closed" and "state_id" in changes)
|
is_closing_now = action == "close" or (state == "closed" and "state_id" in changes)
|
||||||
|
|
||||||
if is_closing_now:
|
if is_closing_now:
|
||||||
try:
|
try:
|
||||||
components = [
|
components = [
|
||||||
disnake.ui.Container(
|
disnake.ui.Container(
|
||||||
disnake.ui.Section(
|
disnake.ui.Section(
|
||||||
"🛠️ Bug / Suggestion Terminé(e) !",
|
"🛠️ Bug / Suggestion Terminé(e) !",
|
||||||
accessory=disnake.ui.Thumbnail(self.bot.user.display_avatar.url)
|
accessory=disnake.ui.Thumbnail(self.bot.user.display_avatar.url),
|
||||||
),
|
),
|
||||||
disnake.ui.Separator(divider=True),
|
disnake.ui.Separator(divider=True),
|
||||||
disnake.ui.TextDisplay(f"Bonne nouvelle ! Votre signalement **{issue_title}** a été marqué comme terminé ou résolu."),
|
disnake.ui.TextDisplay(
|
||||||
disnake.ui.TextDisplay("🚀 **Prochaine étape :** La mise à jour arrive prochainement (ou est déjà là) !"),
|
f"Bonne nouvelle ! Votre signalement **{issue_title}** a été marqué comme terminé ou résolu."
|
||||||
disnake.ui.TextDisplay("✨ Merci pour votre aide !")
|
),
|
||||||
|
disnake.ui.TextDisplay(
|
||||||
|
"🚀 **Prochaine étape :** La mise à jour arrive prochainement (ou est déjà là) !"
|
||||||
|
),
|
||||||
|
disnake.ui.TextDisplay("✨ Merci pour votre aide !"),
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await user.send(components=components)
|
await user.send(components=components)
|
||||||
kuby_logger.info(f"Notification de clôture envoyée à {user} ({user.id}) pour l'issue #{issue_iid}")
|
kuby_logger.info(
|
||||||
|
f"Notification de clôture envoyée à {user} ({user.id}) pour l'issue #{issue_iid}"
|
||||||
|
)
|
||||||
if dev and dev.id != user.id:
|
if dev and dev.id != user.id:
|
||||||
await dev.send(f"✅ [SUIVI] L'utilisateur {user} ({user.id}) a bien reçu la notification de CLÔTURE pour l'issue #{issue_iid}.")
|
await dev.send(
|
||||||
|
f"✅ [SUIVI] L'utilisateur {user} ({user.id}) a bien reçu la notification de CLÔTURE pour l'issue #{issue_iid}."
|
||||||
|
)
|
||||||
except (disnake.Forbidden, disnake.HTTPException) as e:
|
except (disnake.Forbidden, disnake.HTTPException) as e:
|
||||||
if isinstance(e, disnake.Forbidden) or (isinstance(e, disnake.HTTPException) and e.code == 50007):
|
if isinstance(e, disnake.Forbidden) or (
|
||||||
|
isinstance(e, disnake.HTTPException) and e.code == 50007
|
||||||
|
):
|
||||||
kuby_logger.warning(f"Impossible d'envoyer le MP de clôture à {user} ({user.id}).")
|
kuby_logger.warning(f"Impossible d'envoyer le MP de clôture à {user} ({user.id}).")
|
||||||
if dev and dev.id != user.id:
|
if dev and dev.id != user.id:
|
||||||
await dev.send(f"⚠️ [DM BLOQUÉ] Impossible d'annoncer la clôture à {user} ({user.id}) pour l'issue #{issue_iid} (DMs désactivés).")
|
await dev.send(
|
||||||
|
f"⚠️ [DM BLOQUÉ] Impossible d'annoncer la clôture à {user} ({user.id}) pour l'issue #{issue_iid} (DMs désactivés)."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
kuby_logger.error(f"Erreur HTTP lors de l'envoi du MP de clôture à {user}: {e}")
|
kuby_logger.error(f"Erreur HTTP lors de l'envoi du MP de clôture à {user}: {e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -301,40 +370,42 @@ class BugReport(commands.Cog):
|
||||||
elif event_type == "note":
|
elif event_type == "note":
|
||||||
note_attr = payload.get("object_attributes", {})
|
note_attr = payload.get("object_attributes", {})
|
||||||
is_system = note_attr.get("system", False)
|
is_system = note_attr.get("system", False)
|
||||||
|
|
||||||
if not is_system:
|
if not is_system:
|
||||||
note_body = note_attr.get("note", "")
|
note_body = note_attr.get("note", "")
|
||||||
author_name = payload.get("user", {}).get("name", "Développeur")
|
author_name = payload.get("user", {}).get("name", "Développeur")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
components = [
|
components = [
|
||||||
disnake.ui.Container(
|
disnake.ui.Container(
|
||||||
disnake.ui.Section(
|
disnake.ui.Section(
|
||||||
"💬 Nouveau commentaire sur votre signalement",
|
"💬 Nouveau commentaire sur votre signalement",
|
||||||
accessory=disnake.ui.Thumbnail(self.bot.user.display_avatar.url)
|
accessory=disnake.ui.Thumbnail(self.bot.user.display_avatar.url),
|
||||||
),
|
),
|
||||||
disnake.ui.Separator(divider=True),
|
disnake.ui.Separator(divider=True),
|
||||||
disnake.ui.TextDisplay(f"Le développeur a répondu à votre rapport **{issue_title}** :"),
|
disnake.ui.TextDisplay(
|
||||||
|
f"Le développeur a répondu à votre rapport **{issue_title}** :"
|
||||||
|
),
|
||||||
disnake.ui.TextDisplay(f">>> {note_body}"),
|
disnake.ui.TextDisplay(f">>> {note_body}"),
|
||||||
disnake.ui.Separator(divider=True),
|
disnake.ui.Separator(divider=True),
|
||||||
disnake.ui.TextDisplay(f"✍️ **Par :** {author_name}")
|
disnake.ui.TextDisplay(f"✍️ **Par :** {author_name}"),
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await user.send(components=components)
|
await user.send(components=components)
|
||||||
kuby_logger.info(f"Commentaire GitLab envoyé à {user} ({user.id}) pour l'issue #{issue_iid}")
|
kuby_logger.info(
|
||||||
|
f"Commentaire GitLab envoyé à {user} ({user.id}) pour l'issue #{issue_iid}"
|
||||||
|
)
|
||||||
if dev and dev.id != user.id:
|
if dev and dev.id != user.id:
|
||||||
await dev.send(f"✅ [SUIVI] L'utilisateur {user} ({user.id}) a bien reçu votre commentaire pour l'issue #{issue_iid}.")
|
await dev.send(
|
||||||
except (disnake.Forbidden, disnake.HTTPException) as e:
|
f"✅ [SUIVI] L'utilisateur {user} ({user.id}) a bien reçu votre commentaire pour l'issue #{issue_iid}."
|
||||||
if isinstance(e, disnake.Forbidden) or (isinstance(e, disnake.HTTPException) and e.code == 50007):
|
)
|
||||||
kuby_logger.warning(f"Impossible d'envoyer le commentaire en MP à {user} ({user.id}).")
|
except (disnake.Forbidden, disnake.HTTPException):
|
||||||
if dev and dev.id != user.id:
|
if dev and dev.id != user.id:
|
||||||
await dev.send(f"⚠️ [DM BLOQUÉ] Impossible d'envoyer votre commentaire à {user} ({user.id}) pour l'issue #{issue_iid} (DMs désactivés).")
|
await dev.send(
|
||||||
else:
|
f"⚠️ [DM BLOQUÉ] Impossible d'envoyer votre commentaire à {user} ({user.id}) pour l'issue #{issue_iid} (DMs désactivés)."
|
||||||
kuby_logger.error(f"Erreur HTTP lors de l'envoi du MP de commentaire à {user}: {e}")
|
)
|
||||||
except Exception as e:
|
|
||||||
kuby_logger.error(f"Erreur lors de l'envoi du MP de commentaire à {user}: {e}")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
kuby_logger.error(f"Erreur inattendue dans la notification de commentaire : {e}")
|
kuby_logger.error(f"Erreur inattendue dans la notification de commentaire : {e}")
|
||||||
|
|
||||||
|
|
@ -344,20 +415,39 @@ class BugReport(commands.Cog):
|
||||||
kuby_logger.error(f"Error handling bot event: {e}")
|
kuby_logger.error(f"Error handling bot event: {e}")
|
||||||
return web.json_response({"status": "error", "message": str(e)}, status=500)
|
return web.json_response({"status": "error", "message": str(e)}, status=500)
|
||||||
|
|
||||||
@commands.slash_command(name="signaler_bug", description="Signaler un bug aux développeurs")
|
@commands.slash_command(
|
||||||
async def signaler_bug(self, interaction: disnake.ApplicationCommandInteraction,
|
name="signaler_bug",
|
||||||
priority: str = commands.Param(description="Priorité du bug", choices={"Basse": "Low", "Normale": "Normal", "Haute": "High", "Urgente": "Urgent"})):
|
description="Signaler un bug aux développeurs",
|
||||||
|
)
|
||||||
|
async def signaler_bug(
|
||||||
|
self,
|
||||||
|
interaction: disnake.ApplicationCommandInteraction,
|
||||||
|
priority: str = commands.Param(
|
||||||
|
description="Priorité du bug",
|
||||||
|
choices={"Basse": "Low", "Normale": "Normal", "Haute": "High", "Urgente": "Urgent"},
|
||||||
|
),
|
||||||
|
):
|
||||||
# Simulate app_commands.Choice behavior for existing code
|
# Simulate app_commands.Choice behavior for existing code
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
Choice = namedtuple('Choice', ['name', 'value'])
|
Choice = namedtuple('Choice', ['name', 'value'])
|
||||||
# find the name from the value
|
mapping = {"Basse": "Low", "Normale": "Normal", "Haute": "High", "Urgente": "Urgent"}
|
||||||
p_name = [k for k, v in {"Basse": "Low", "Normale": "Normal", "Haute": "High", "Urgente": "Urgent"}.items() if v == priority][0]
|
reverse = {v: k for k, v in mapping.items()}
|
||||||
|
|
||||||
|
# priority vient du value GitLab (Low/Normal/High/Urgent)
|
||||||
|
p_name = reverse.get(priority, "Normal")
|
||||||
choice = Choice(name=p_name, value=priority)
|
choice = Choice(name=p_name, value=priority)
|
||||||
|
|
||||||
await interaction.response.send_modal(BugReportModal(choice))
|
await interaction.response.send_modal(BugReportModal(choice))
|
||||||
|
|
||||||
@commands.slash_command(name="suggerer_fonctionnalite", description="Proposer une nouvelle fonctionnalité pour le bot")
|
@commands.slash_command(
|
||||||
|
name="suggerer_fonctionnalite",
|
||||||
|
description="Proposer une nouvelle fonctionnalité pour le bot",
|
||||||
|
)
|
||||||
async def suggerer_fonctionnalite(self, interaction: disnake.ApplicationCommandInteraction):
|
async def suggerer_fonctionnalite(self, interaction: disnake.ApplicationCommandInteraction):
|
||||||
await interaction.response.send_modal(FeatureSuggestionModal())
|
await interaction.response.send_modal(FeatureSuggestionModal())
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
def setup(bot):
|
||||||
bot.add_cog(BugReport(bot))
|
bot.add_cog(BugReport(bot))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -246,12 +246,28 @@ class Moderation(commands.Cog):
|
||||||
action, mid = cid.replace("modpan_", "").split(":")
|
action, mid = cid.replace("modpan_", "").split(":")
|
||||||
member = interaction.guild.get_member(int(mid))
|
member = interaction.guild.get_member(int(mid))
|
||||||
if not member: return await interaction.response.send_message("❌ Membre introuvable.", ephemeral=True)
|
if not member: return await interaction.response.send_message("❌ Membre introuvable.", ephemeral=True)
|
||||||
|
|
||||||
|
# Vérifications des permissions et de la hiérarchie
|
||||||
|
if member.top_role >= interaction.user.top_role and interaction.user.id != interaction.guild.owner_id:
|
||||||
|
return await interaction.response.send_message("❌ Permissions insuffisantes : ce membre a un rôle supérieur ou égal au vôtre.", ephemeral=True)
|
||||||
|
|
||||||
|
perms = interaction.user.guild_permissions
|
||||||
|
if action == "ban" and not perms.ban_members:
|
||||||
|
return await interaction.response.send_message("❌ Vous n'avez pas la permission de bannir des membres.", ephemeral=True)
|
||||||
|
elif action == "kick" and not perms.kick_members:
|
||||||
|
return await interaction.response.send_message("❌ Vous n'avez pas la permission d'expulser des membres.", ephemeral=True)
|
||||||
|
elif action in ["timeout", "warn", "clear"] and not perms.moderate_members:
|
||||||
|
return await interaction.response.send_message("❌ Vous n'avez pas la permission de gérer les sanctions (moderate_members).", ephemeral=True)
|
||||||
|
|
||||||
if action == "clear":
|
if action == "clear":
|
||||||
await self.clearwarns(interaction, member, "Nettoyage Panel")
|
await self.clearwarns(interaction, member, "Nettoyage Panel")
|
||||||
await self.send_modpanel_v2(interaction, member, edit=True)
|
await self.send_modpanel_v2(interaction, member, edit=True)
|
||||||
else:
|
else:
|
||||||
await interaction.response.send_modal(ModReasonModal(self, action.upper(), member))
|
await interaction.response.send_modal(ModReasonModal(self, action.upper(), member))
|
||||||
elif cid.startswith("modcfg_"):
|
elif cid.startswith("modcfg_"):
|
||||||
|
if not interaction.user.guild_permissions.administrator:
|
||||||
|
return await interaction.response.send_message("❌ Vous devez être administrateur pour modifier cette configuration.", ephemeral=True)
|
||||||
|
|
||||||
act = cid.replace("modcfg_", "")
|
act = cid.replace("modcfg_", "")
|
||||||
if act in ["logs", "board"]:
|
if act in ["logs", "board"]:
|
||||||
col = "log_channel_id" if act == "logs" else "board_channel_id"
|
col = "log_channel_id" if act == "logs" else "board_channel_id"
|
||||||
|
|
|
||||||
|
|
@ -1200,9 +1200,9 @@ class CloseDelayView(disnake.ui.View):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to send feedback DM: {e}")
|
print(f"Failed to send feedback DM: {e}")
|
||||||
|
|
||||||
# Send transcript to log channel
|
# Send transcript to log channel
|
||||||
if self.config.log_channel_id:
|
if self.config.log_channel_id:
|
||||||
try:
|
try:
|
||||||
log_channel = interaction.guild.get_channel(self.config.log_channel_id)
|
log_channel = interaction.guild.get_channel(self.config.log_channel_id)
|
||||||
if log_channel:
|
if log_channel:
|
||||||
log_embed = disnake.Embed(
|
log_embed = disnake.Embed(
|
||||||
|
|
@ -1396,7 +1396,13 @@ class CloseTicketModal(disnake.ui.Modal):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get ticket creator
|
# Get ticket creator
|
||||||
|
# Fetch le créateur pour affichage fiable même si absent du cache
|
||||||
ticket_creator = interaction.guild.get_member(self.ticket.user_id)
|
ticket_creator = interaction.guild.get_member(self.ticket.user_id)
|
||||||
|
if not ticket_creator:
|
||||||
|
try:
|
||||||
|
ticket_creator = await interaction.guild.fetch_member(self.ticket.user_id)
|
||||||
|
except Exception:
|
||||||
|
ticket_creator = None
|
||||||
creator_mention = ticket_creator.mention if ticket_creator else f"Utilisateur {self.ticket.user_id}"
|
creator_mention = ticket_creator.mention if ticket_creator else f"Utilisateur {self.ticket.user_id}"
|
||||||
|
|
||||||
log_embed.add_field(name="Créateur", value=creator_mention, inline=True)
|
log_embed.add_field(name="Créateur", value=creator_mention, inline=True)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue