diff --git a/commandes/bug_report.py b/commandes/bug_report.py index 7a97449..3504473 100644 --- a/commandes/bug_report.py +++ b/commandes/bug_report.py @@ -1,7 +1,8 @@ import discord from discord import app_commands -from discord.ext import commands, tasks +from discord.ext import commands from utils.gitlab_client import gitlab_client +from aiohttp import web import logging import json import os @@ -109,61 +110,139 @@ class FeatureSuggestionModal(discord.ui.Modal, title="Suggérer une fonctionnali class BugReport(commands.Cog): def __init__(self, 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): - self.check_gitlab_issues.cancel() - - @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 + 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) - - if not reports: - return - 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) - if user: - try: - is_suggestion = "[SUGGESTION]" in issue.get('title', '') - title_type = "Suggestion Implémentée !" if is_suggestion else "🛠️ Bug Résolu !" - msg_type = "suggestion" if is_suggestion else "bug" - status_val = "Implémenté / Terminé" if is_suggestion else "Terminé / Résolu" - - embed = discord.Embed( - title=title_type, - description=f"Bonne nouvelle ! Votre {msg_type} (**{issue.get('title')}**) a été marqué comme terminé sur GitLab.", - color=discord.Color.green() - ) - embed.add_field(name="Statut", value=status_val) - embed.add_field(name="Prochaine étape", value="La mise à jour arrive prochainement !") - embed.set_footer(text="Merci de votre aide pour améliorer le bot.") - 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: - for iid in issues_to_remove: - del reports[iid] - with open(REPORTS_FILE, "w") as f: - json.dump(reports, f, indent=4) + 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"Erreur lors du cycle de vérification GitLab: {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=[ @@ -172,11 +251,17 @@ class BugReport(commands.Cog): 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]): + 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)) @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()) async def setup(bot): diff --git a/serveur_flask.py b/serveur_flask.py new file mode 100644 index 0000000..9f57112 --- /dev/null +++ b/serveur_flask.py @@ -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)