From 1af99cba67c3a64c1c37cbfb32bff3f6a57d9b21 Mon Sep 17 00:00:00 2001 From: mortifia Date: Mon, 13 Jul 2026 02:28:22 +0200 Subject: [PATCH 1/6] fix: restaure la coloration ANSI du $help (#83) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Passe le paginator de MyHelpCommand sur un code block ```ansi et colore les en-têtes de catégorie, noms de commandes et descriptions avec des séquences ANSI "dim" supportées par le rendu Discord. Le padding d'alignement des noms de commande est calculé avant l'ajout des codes ANSI pour ne pas fausser la largeur visible. Closes #83 --- src/urpy/help.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/urpy/help.py b/src/urpy/help.py index 811ef33..4df5561 100644 --- a/src/urpy/help.py +++ b/src/urpy/help.py @@ -10,12 +10,21 @@ _ = lcl +# Couleurs "dim" (2;3x) rendues par le client Discord dans un code block ```ansi. +HEADING_COLOR = '\x1b[2;34;4m' # bleu, souligné +COMMAND_COLOR = '\x1b[2;33m' # jaune +DESCRIPTION_COLOR = '\x1b[2;36m' # cyan +RESET_COLOR = '\x1b[0m' + + class MyHelpCommand(commands.DefaultHelpCommand): def __init__(self, localization, **options): super().__init__( help=lcl('Shows this message'), commands_heading=lcl('Commands:'), - no_category=lcl('No Category'), **options) # TODO 'No Category' in french + no_category=lcl('No Category'), # TODO 'No Category' in french + paginator=commands.Paginator(prefix='```ansi', suffix='```'), + **options) global _ # This check is necessary cause HelpCommand is deeply copied every time in discord api. """ if _ is lcl: @@ -58,7 +67,7 @@ def add_indented_commands(self, instructions, *, heading, max_size=None): return if heading.startswith('\u200b'): heading = _(heading[1:-1]) + ':' - self.paginator.add_line(_(heading[:-1]) + heading[-1]) + self.paginator.add_line(f'{HEADING_COLOR}{_(heading[:-1])}{RESET_COLOR}{heading[-1]}') max_size = max_size or self.get_max_size(instructions) get_width = discord.utils._string_width # Erreur : 'Access to a protected member _string_width of a module'. @@ -67,7 +76,12 @@ def add_indented_commands(self, instructions, *, heading, max_size=None): # servirait un futur développeur. name = command.name width = max_size - (get_width(name) - len(name)) - entry = '{0}{1:<{width}} {2}'.format(self.indent * ' ', name, _(command.short_doc), width=width) + # le padding se calcule sur le nom brut : les codes ANSI ajoutes ensuite ne comptent pas + # dans la largeur visible, mais fausseraient l'alignement s'ils etaient inclus dans le format. + padded_name = '{0:<{width}}'.format(name, width=width) + entry = '{0}{1}{2}{3} {4}{5}{6}'.format( + self.indent * ' ', COMMAND_COLOR, padded_name, RESET_COLOR, + DESCRIPTION_COLOR, _(command.short_doc), RESET_COLOR) self.paginator.add_line(self.shorten_text(entry)) async def send_cog_help(self, cog): From 5ae9a988bd0f5b7e3446ea23a259b33b30bf7405 Mon Sep 17 00:00:00 2001 From: mortifia Date: Mon, 13 Jul 2026 02:36:51 +0200 Subject: [PATCH 2/6] fix: colore l'ensemble du $help, pas seulement la liste de commandes (#83) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Applique les mêmes couleurs ANSI à add_command_formatting (vue détaillée d'une commande), get_ending_note (note de bas de page) et à la description de catégorie dans send_cog_help, pour que le rendu soit cohérent sur toutes les vues du $help et pas seulement le listing principal. --- src/urpy/help.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/urpy/help.py b/src/urpy/help.py index 4df5561..7808ac2 100644 --- a/src/urpy/help.py +++ b/src/urpy/help.py @@ -40,25 +40,27 @@ def add_command_formatting(self, command): """ if command.description: - self.paginator.add_line(_(command.description), empty=True) + self.paginator.add_line(f'{DESCRIPTION_COLOR}{_(command.description)}{RESET_COLOR}', empty=True) signature = self.get_command_signature(command) - self.paginator.add_line(signature, empty=True) + self.paginator.add_line(f'{COMMAND_COLOR}{signature}{RESET_COLOR}', empty=True) # 'domain = getattr(command.cog, 'domain', None)' ; la variable n'est jamais utilisée. À laisser au cas où ça # servirait un futur développeur. if command.help: try: - self.paginator.add_line(_(command.help), empty=True) + self.paginator.add_line(f'{DESCRIPTION_COLOR}{_(command.help)}{RESET_COLOR}', empty=True) except RuntimeError: + self.paginator.add_line(DESCRIPTION_COLOR) for line in command.help.splitlines(): self.paginator.add_line(line) # TODO localization here - self.paginator.add_line() + self.paginator.add_line(RESET_COLOR) def get_ending_note(self): """:class:`str`: Returns help command's ending note. This is mainly useful to override for i18n purposes.""" command_name = self.invoked_with - return _(f"Type {self.clean_prefix}{command_name} command for more info on a command.\n" + note = _(f"Type {self.clean_prefix}{command_name} command for more info on a command.\n" f"You can also type {self.clean_prefix}{command_name} category for more info on a category.") + return f'{DESCRIPTION_COLOR}{note}{RESET_COLOR}' # Une erreur se produit sur 'self.clean_prefix'. # En effet, l'IDE demande la création de cette méthode dans la classe. @@ -88,7 +90,7 @@ async def send_cog_help(self, cog): # 'domain = getattr(cog, 'domain', None)' ; la variable n'est jamais utilisée. À laisser au cas où ça # servirait un futur développeur. if cog.description: - self.paginator.add_line(_(cog.description), empty=True) + self.paginator.add_line(f'{DESCRIPTION_COLOR}{_(cog.description)}{RESET_COLOR}', empty=True) filtered = await self.filter_commands(cog.get_commands(), sort=self.sort_commands) self.add_indented_commands(filtered, heading=_(self.commands_heading)) From 257d9650747390c407c73a289bcb8d6f071e6084 Mon Sep 17 00:00:00 2001 From: mortifia Date: Mon, 13 Jul 2026 02:52:22 +0200 Subject: [PATCH 3/6] feat: colore $version, $credit et $lang, pas seulement $help (#87) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ajoute des helpers partagés dans src/urpy/utils.py (colorize, ansi_block, colored_message + constantes de couleur) pour éviter de dupliquer la logique déjà écrite pour $help. help.py réutilise maintenant ces constantes au lieu de les redéfinir localement. $version/$credit (cog_about.py) : les numéros de version et les crédits sont colorés ; les titres/noms de service restent en clair car le calcul du soulignement '====='/'-----' de formatted_template se base sur leur longueur visible, que des codes ANSI fausseraient. $lang (cog_general.py) : message de succès en vert, message d'erreur en rouge. Ces helpers sont conçus pour être réutilisés par Bot_Planning_python et Bot_Presentation, qui en dépendent déjà via Bot_Base.src.urpy.utils. Closes #87 --- src/bot/cog_about.py | 14 ++++++++------ src/bot/cog_general.py | 7 +++++-- src/urpy/help.py | 8 +------- src/urpy/utils.py | 22 ++++++++++++++++++++++ 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/bot/cog_about.py b/src/bot/cog_about.py index 17fd658..67ceef9 100644 --- a/src/bot/cog_about.py +++ b/src/bot/cog_about.py @@ -34,15 +34,17 @@ async def send_info_msg(self, ctx, with_credits=False): services = "\n\n".join( formatted_template(templates, 'service_template.txt', name=cog.get_name(), - version=cog.get_version(), - credits=cog.get_credits() if with_credits else "") + version=colorize(cog.get_version(), COMMAND_COLOR), + credits=colorize(cog.get_credits(), DESCRIPTION_COLOR) if with_credits else "") for name, cog in self.bot.cogs.items() if isinstance(cog, MyCog) ) - # sends the message - await ctx.send(code_block(formatted_template(templates, 'version_template.txt', - version=self.bot.get_version(), + # sends the message. Le nom du bot et des services reste non colore : le padding des + # soulignements '====='/'-----' de formatted_template se calcule sur ces lignes, et des + # codes ANSI y fausseraient la longueur visible. + await ctx.send(ansi_block(formatted_template(templates, 'version_template.txt', + version=colorize(self.bot.get_version(), COMMAND_COLOR), name=self.bot.get_name(), services=services, - credits=self.bot.get_credits() if with_credits else ""))) + credits=colorize(self.bot.get_credits(), DESCRIPTION_COLOR) if with_credits else ""))) diff --git a/src/bot/cog_general.py b/src/bot/cog_general.py index bbead23..6ed28e1 100755 --- a/src/bot/cog_general.py +++ b/src/bot/cog_general.py @@ -2,6 +2,7 @@ from discord.ext import commands from Bot_Base.src.urpy.localization import Localization from Bot_Base.src.urpy.my_commands import MyBot +from Bot_Base.src.urpy.utils import colored_message, SUCCESS_COLOR, ERROR_COLOR import strings # UR_Bot © 2020 by "Association Union des Rôlistes & co" is licensed under Attribution-NonCommercial-ShareAlike 4.0 @@ -52,9 +53,11 @@ async def cancel(self, ctx): async def lang(self, ctx: commands.Context, language): """Switches to specified language""" if self.localization.set_user_language(ctx.author.id): - await ctx.send(_("Your language has successfully been set to {language}!").format(language=language)) + await ctx.send(colored_message( + _("Your language has successfully been set to {language}!").format(language=language), + SUCCESS_COLOR)) else: - await ctx.send(_("Sorry, I don't know this language!")) + await ctx.send(colored_message(_("Sorry, I don't know this language!"), ERROR_COLOR)) def add_to_command(self, command: str, *callbacks): """Adds a callback to the specified command. It will be called on command invocation.""" diff --git a/src/urpy/help.py b/src/urpy/help.py index 7808ac2..c40346d 100644 --- a/src/urpy/help.py +++ b/src/urpy/help.py @@ -6,17 +6,11 @@ # Ask a derogation at Contact.unionrolistes@gmail.com from Bot_Base.src.urpy.localization import lcl +from Bot_Base.src.urpy.utils import HEADING_COLOR, COMMAND_COLOR, DESCRIPTION_COLOR, RESET_COLOR _ = lcl -# Couleurs "dim" (2;3x) rendues par le client Discord dans un code block ```ansi. -HEADING_COLOR = '\x1b[2;34;4m' # bleu, souligné -COMMAND_COLOR = '\x1b[2;33m' # jaune -DESCRIPTION_COLOR = '\x1b[2;36m' # cyan -RESET_COLOR = '\x1b[0m' - - class MyHelpCommand(commands.DefaultHelpCommand): def __init__(self, localization, **options): super().__init__( diff --git a/src/urpy/utils.py b/src/urpy/utils.py index b8735fd..c5c93a1 100644 --- a/src/urpy/utils.py +++ b/src/urpy/utils.py @@ -58,10 +58,32 @@ async def get_informations(msg: discord.Message): return infos +# Couleurs "dim" (2;3x) rendues par le client Discord dans un code block ```ansi. +HEADING_COLOR = '\x1b[2;34;4m' # bleu, souligne +COMMAND_COLOR = '\x1b[2;33m' # jaune +DESCRIPTION_COLOR = '\x1b[2;36m' # cyan +SUCCESS_COLOR = '\x1b[2;32m' # vert +ERROR_COLOR = '\x1b[2;31m' # rouge +RESET_COLOR = '\x1b[0m' + + def code_block(s): return f"```{s}```" +def ansi_block(s): + return f"```ansi\n{s}```" + + +def colorize(s, color=DESCRIPTION_COLOR): + return f'{color}{s}{RESET_COLOR}' + + +def colored_message(s, color=SUCCESS_COLOR): + """Wraps a plain bot message in a colored ```ansi code block.""" + return ansi_block(colorize(s, color)) + + def edit_fmt(s): return f"\\|~ {s} ~|" From 799d33ac59372b9c819c83071cb155a996dc4f62 Mon Sep 17 00:00:00 2001 From: mortifia Date: Mon, 13 Jul 2026 03:05:35 +0200 Subject: [PATCH 4/6] style: harmonise $version/$credit avec le style de $help Remplace les soulignements ASCII litteraux '====='/'-----' par la coloration+soulignement ANSI (HEADING_COLOR, qui inclut deja le code de soulignement) : les titres/noms de service sont desormais colores comme les en-tetes de categorie de $help, et il n'y a plus besoin de calculer une longueur de soulignement a partir de la ligne precedente. --- src/bot/cog_about.py | 10 +++++----- src/bot/templates/service_template.txt | 1 - src/bot/templates/version_template.txt | 1 - 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/bot/cog_about.py b/src/bot/cog_about.py index 67ceef9..c5194f0 100644 --- a/src/bot/cog_about.py +++ b/src/bot/cog_about.py @@ -33,18 +33,18 @@ async def send_info_msg(self, ctx, with_credits=False): # generates the descriptions for all the subservices of the bot services = "\n\n".join( formatted_template(templates, 'service_template.txt', - name=cog.get_name(), + name=colorize(cog.get_name(), HEADING_COLOR), version=colorize(cog.get_version(), COMMAND_COLOR), credits=colorize(cog.get_credits(), DESCRIPTION_COLOR) if with_credits else "") for name, cog in self.bot.cogs.items() if isinstance(cog, MyCog) ) - # sends the message. Le nom du bot et des services reste non colore : le padding des - # soulignements '====='/'-----' de formatted_template se calcule sur ces lignes, et des - # codes ANSI y fausseraient la longueur visible. + # sends the message. Les titres sont colores+soulignes via ANSI (HEADING_COLOR inclut + # deja le code de soulignement) plutot que via des '====='/'-----' litteraux : les + # templates ne calculent donc plus de longueur de soulignement a partir de ces lignes. await ctx.send(ansi_block(formatted_template(templates, 'version_template.txt', version=colorize(self.bot.get_version(), COMMAND_COLOR), - name=self.bot.get_name(), + name=colorize(self.bot.get_name(), HEADING_COLOR), services=services, credits=colorize(self.bot.get_credits(), DESCRIPTION_COLOR) if with_credits else ""))) diff --git a/src/bot/templates/service_template.txt b/src/bot/templates/service_template.txt index a8b5d7c..99e9b25 100644 --- a/src/bot/templates/service_template.txt +++ b/src/bot/templates/service_template.txt @@ -1,4 +1,3 @@ {name} - ------ -> version : {version} {credits} \ No newline at end of file diff --git a/src/bot/templates/version_template.txt b/src/bot/templates/version_template.txt index 0b29378..62979d9 100644 --- a/src/bot/templates/version_template.txt +++ b/src/bot/templates/version_template.txt @@ -1,5 +1,4 @@ {name} -====== -> Version : {version} {credits} From 15d988ff096318cb05d55b2cce8d691ee6e4616f Mon Sep 17 00:00:00 2001 From: mortifia Date: Mon, 13 Jul 2026 03:06:41 +0200 Subject: [PATCH 5/6] docs: documente la configuration requise par commande MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ajoute une section README expliquant, pour chaque commande, le nom de salon attendu (en dur dans settings.py), le fichier où le modifier, et la permission Discord nécessaire. Cette information manquait et a directement causé des heures de diagnostic sur $prez (échec silencieux faute du salon 'presentation' et de la permission Gérer les webhooks). --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 4cc92a7..2e91783 100755 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ 2.3 [BOT_PRESENTATION](#bot_presentation) - 2.2.2 [Utilisation](#utilisation--1) +2.4 [CONFIGURATION DES COMMANDES](#configuration-des-commandes) + 3. [INSTALLER ET CONFIGURER LE PROJET EN ENTIER](#installer-et-configurer-le-projet-en-entier) - 3.1 [Prérequis](#prérequis-) - 3.2 [Démarche](#démarche-) @@ -91,6 +93,22 @@ En message privé, il reçoit un lien web vers un formulaire figurant des questi 3. Une fois publique, la présentation est encore modifiable ; il suffit d'y répondre sur discord avec la commande **$edit**. +--- +## Configuration des commandes +Certaines commandes nécessitent un salon Discord au nom précis, muni d'un webhook, ainsi que des permissions particulières pour le bot. Sans cette configuration, la commande échoue avec un message d'erreur explicite dans le salon ou en MP. + +| Commande | Salon requis | Configurable dans | Permission Discord nécessaire | +|---|---|---|---| +| **$jdr**, **$edit** (annonces de partie) | Un salon nommé exactement `planning-jdr`, muni d'au moins un webhook | `Bot_Planning_python/src/cog_planning/settings.py` (`announcement_channel`) | *Gérer les webhooks* sur ce salon | +| **$prez** | Un salon nommé exactement `presentation`, muni d'au moins un webhook | `Bot_Presentation/src/cog_presentation/settings.py` (`announcement_channel`) | *Gérer les webhooks* sur ce salon | +| **$cal**, **$site** | Aucun (le bot envoie un lien fixe en MP) | URLs `calendar_url`/`site_url` dans `cog_planning/settings.py` | Aucune | +| Toutes | — | — | *Gérer les messages* recommandée : sans elle, les messages temporaires (confirmations envoyées dans le salon) ne s'autodétruisent plus après quelques secondes, mais les commandes continuent de fonctionner normalement | + +Le nom du salon attendu (`announcement_channel`) est fixé dans le code : créez un salon portant exactement ce nom, ou modifiez la valeur dans le `settings.py` correspondant avant de builder l'image. + +Voir aussi la section [Démarche](#démarche-) ci-dessous pour la liste complète des permissions à accorder au bot lors de son invitation sur un serveur. + + --- --- ## Installer et configurer le projet en entier From 49aa2b69d641f4b346134cb373cc5ddcb26c8a29 Mon Sep 17 00:00:00 2001 From: mortifia Date: Mon, 13 Jul 2026 03:18:09 +0200 Subject: [PATCH 6/6] =?UTF-8?q?fix:=20r=C3=A9indente=20correctement=20les?= =?UTF-8?q?=20valeurs=20multi-lignes=20dans=20formatted=5Ftemplate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quand une valeur substituée (ex: credits.txt, plusieurs contributeurs sur plusieurs lignes) est injectée dans un template, seule sa première ligne reprenait l'indentation de la ligne du template — les lignes suivantes se retrouvaient collées à la marge gauche. Corrigé en réappliquant l'indentation à chaque ligne interne de la valeur substituée. Ajoute Mortifia aux crédits (travail de dockerisation, API planning et coloration ANSI sur Bot_Base). --- src/bot/info/credits.txt | 3 ++- src/urpy/utils.py | 13 ++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/bot/info/credits.txt b/src/bot/info/credits.txt index 2746973..51e6758 100755 --- a/src/bot/info/credits.txt +++ b/src/bot/info/credits.txt @@ -1,2 +1,3 @@ Lyss -Maestro#8364 \ No newline at end of file +Maestro#8364 +Mortifia \ No newline at end of file diff --git a/src/urpy/utils.py b/src/urpy/utils.py index c5c93a1..3e60418 100644 --- a/src/urpy/utils.py +++ b/src/urpy/utils.py @@ -112,7 +112,18 @@ def formatted_template(template_pckg, template_name, **kwargs): if match.group(4): new_line += match.group(4)[0] * size_last_line # formats text that doesn't symbolize an underline - new_line += match.group(5).format(**kwargs) + formatted = match.group(5).format(**kwargs) + # une valeur substituee (ex: credits multi-auteurs) peut contenir plusieurs + # lignes ; sans reappliquer l'indentation de la ligne du template a chacune + # d'elles, seule la premiere serait indentee. Le saut de ligne final de la + # ligne elle-meme (pas celui d'une valeur substituee) est laisse de cote pour + # ne pas indenter ce qui suit sur la ligne suivante du template. + indent = match.group(1) + if '\n' in formatted: + has_trailing_newline = formatted.endswith('\n') + body = formatted[:-1] if has_trailing_newline else formatted + formatted = body.replace('\n', '\n' + indent) + ('\n' if has_trailing_newline else '') + new_line += formatted size_last_line = len(new_line.strip())