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 diff --git a/src/bot/cog_about.py b/src/bot/cog_about.py index 17fd658..c5194f0 100644 --- a/src/bot/cog_about.py +++ b/src/bot/cog_about.py @@ -33,16 +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(), - version=cog.get_version(), - credits=cog.get_credits() if with_credits else "") + 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 - await ctx.send(code_block(formatted_template(templates, 'version_template.txt', - version=self.bot.get_version(), - name=self.bot.get_name(), + # 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=colorize(self.bot.get_name(), HEADING_COLOR), 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/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/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} diff --git a/src/urpy/help.py b/src/urpy/help.py index 811ef33..c40346d 100644 --- a/src/urpy/help.py +++ b/src/urpy/help.py @@ -6,6 +6,7 @@ # 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 @@ -15,7 +16,9 @@ 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: @@ -31,25 +34,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. @@ -58,7 +63,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,14 +72,19 @@ 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): # '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)) diff --git a/src/urpy/utils.py b/src/urpy/utils.py index b8735fd..3e60418 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} ~|" @@ -90,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())