import discord from discord import app_commands from discord.ext import commands from utils.gitlab_client import gitlab_client from aiohttp import web import logging import json import os import asyncio kuby_logger = logging.getLogger("KubyBot") REPORTS_FILE = "data/gitlab_reports.json" def save_report(issue_iid, user_id): os.makedirs("data", exist_ok=True) try: if os.path.exists(REPORTS_FILE): with open(REPORTS_FILE, "r") as f: data = json.load(f) else: data = {} data[str(issue_iid)] = user_id with open(REPORTS_FILE, "w") as f: json.dump(data, f, indent=4) except Exception as e: kuby_logger.error(f"Error saving report to JSON: {e}") class BugReportModal(discord.ui.Modal, title="Signaler un Bug"): def __init__(self, priority_choice): super().__init__() self.priority_choice = priority_choice bug_title = discord.ui.TextInput( label="Titre du bug", placeholder="Décrivez brièvement le problème...", required=True, max_length=100 ) description = discord.ui.TextInput( label="Description détaillée", style=discord.TextStyle.paragraph, placeholder="Que s'est-il passé ? Comment reproduire le bug ?", required=True, max_length=1000 ) async def on_submit(self, interaction: discord.Interaction): await interaction.response.send_message("Envoi de votre rapport de bug à GitLab...", ephemeral=True) description_text = f"**Rapporté par:** {interaction.user} ({interaction.user.id})\n" description_text += f"**Priorité:** {self.priority_choice.name}\n\n" description_text += f"**Description:**\n{self.description.value}" # Mapping des priorités vers des labels GitLab stylisés (Scoped Labels) labels = ["bug", "manual-report", f"Priority::{self.priority_choice.value}"] result = await gitlab_client.create_issue( title=f"[MANUAL] {self.bug_title.value}", description=description_text, labels=labels ) if result: issue_iid = result.get("iid") 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')})") else: await interaction.edit_original_response(content="❌ Une erreur est survenue lors de l'envoi du rapport à GitLab. Veuillez contacter un administrateur.") class FeatureSuggestionModal(discord.ui.Modal, title="Suggérer une fonctionnalité"): suggestion_title = discord.ui.TextInput( label="Titre de la suggestion", placeholder="Que voulez-vous ajouter ?", required=True, max_length=100 ) description = discord.ui.TextInput( label="Détails de la fonctionnalité", style=discord.TextStyle.paragraph, placeholder="Décrivez comment cela devrait fonctionner...", required=True, max_length=1000 ) async def on_submit(self, interaction: discord.Interaction): await interaction.response.send_message("Envoi de votre suggestion à GitLab...", ephemeral=True) description_text = f"**Suggéré par:** {interaction.user} ({interaction.user.id})\n\n" description_text += f"**Description:**\n{self.description.value}" labels = ["enhancement", "manual-suggestion"] result = await gitlab_client.create_issue( title=f"[SUGGESTION] {self.suggestion_title.value}", description=description_text, labels=labels ) if result: issue_iid = result.get("iid") 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')})") else: await interaction.edit_original_response(content="❌ Une erreur est survenue lors de l'envoi de la suggestion à GitLab.") class BugReport(commands.Cog): def __init__(self, bot): self.bot = bot 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): if hasattr(self, 'runner'): self.bot.loop.create_task(self.runner.cleanup()) async def handle_bot_event(self, request): 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: reports = json.load(f) user_id = reports.get(str(issue_iid)) if not user_id: return web.json_response({"status": "untracked issue"}, status=200) user = self.bot.get_user(user_id) if not user: try: user = await self.bot.fetch_user(user_id) except: pass 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( title="🛠️ Mise à jour de votre signalement !", 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() ) embed.add_field(name="Prochaine étape", value="La mise à jour arrive prochainement (ou est déjà là) !") embed.set_footer(text="Merci pour votre aide !") await user.send(embed=embed) if dev: await dev.send(f"✅ [FIABLE] L'utilisateur {user} ({user.id}) a bien reçu la notification de CLÔTURE pour l'issue #{issue_iid}.") except Exception as 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.choices(priority=[ app_commands.Choice(name="Basse", value="Low"), app_commands.Choice(name="Normale", value="Normal"), app_commands.Choice(name="Haute", value="High"), app_commands.Choice(name="Urgente", value="Urgent"), ]) async def signaler_bug(self, interaction: discord.Interaction, priority: app_commands.Choice[str]): await interaction.response.send_modal(BugReportModal(priority)) @app_commands.command(name="suggerer_fonctionnalite", description="Proposer une nouvelle fonctionnalité pour le bot") async def suggerer_fonctionnalite(self, interaction: discord.Interaction): await interaction.response.send_modal(FeatureSuggestionModal()) async def setup(bot): await bot.add_cog(BugReport(bot))