Aller au contenu

Conversion du Format des Messages

Lorsque vous ajoutez un plugin, posez-vous deux questions :

  1. Quel format l’appelant a-t-il fourni ?
  2. Quel format le service accepte-t-il ?

L’appelant décrit le premier avec body_format. Votre plugin décrit le second avec notify_format. Apprise effectue la conversion avant d’appeler send().

Dans send(), body_format correspond au format du corps reçu. Ce n’est plus la valeur d’origine de l’appelant. Par exemple, un même message HTML peut parvenir sous forme de texte brut à un plugin, de Markdown à un autre et rester en HTML pour un troisième.

Le format d’entrée dépend de la façon dont la notification a été envoyée :

InterfaceValeur par défaut lorsqu’aucun format n’est déclaré
CLItext : la CLI définit toujours un format d’entrée ; remplacez-le avec --input-format
Bibliothèque Pythonnone : sauf si body_format est passé à notify() ou prédéfini dans AppriseAsset
Apprise APInone : le formatage est entièrement optionnel ; définissez format dans la charge utile de la requête pour changer ce comportement

Consultez Formatage pour le comportement visible par l’utilisateur.

Pour chaque plugin, Apprise :

  1. Lit le format d’entrée déclaré par l’appelant.
  2. Choisit un format accepté par le plugin.
  3. Convertit le corps lorsque le format d’entrée diffère de celui du plugin.
  4. Appelle le plugin avec le corps converti et le format choisi.

Si l’appelant n’a déclaré aucun format source, Apprise laisse le corps inchangé. Dans ce cas, body_passthrough vaut True. Sinon, il vaut False.

Apprise.notify(body=..., body_format=HTML)
|
| cible = plugin.resolve_format(HTML)
| convert_between(HTML, cible, body)
| (ignoré lorsque le body_format de l'appelant vaut None)
v
plugin.notify(
body=<déjà converti vers le format résolu>,
body_format=cible, # format choisi ; jamais None ici
body_passthrough=False, # l'appelant a déclaré HTML ci-dessus
)

Lorsqu’un service ne possède pas de champ distinct pour le titre (title_maxlen <= 0), Apprise ajoute le titre au corps avant l’envoi. Sinon, le titre est transmis séparément.

Acceptez body_format et body_passthrough dans send(). Apprise a déjà calculé ces deux valeurs :

def send(self, body, title="", notify_type=..., body_format=None,
body_passthrough=None, **kwargs):
if body_format == NotifyFormat.MARKDOWN:
...

Vous n’avez normalement pas besoin d’appeler resolve_format() vous-même. Les appels directs à plugin.notify() ou plugin.send() constituent l’exception : ils ne passent pas par l’étape de conversion d’Apprise. Si vous appelez un plugin directement, préparez le corps dans le format attendu par ce plugin.

Lorsqu’Aucun Format d’Entrée N’est Déclaré

Section intitulée « Lorsqu’Aucun Format d’Entrée N’est Déclaré »

Lorsque le body_format de l’appelant de la bibliothèque Python vaut None, Apprise ignore entièrement la conversion :

apobj.notify(body="<b>Bonjour</b>")

Le plugin reçoit <b>Bonjour</b> sans modification et body_passthrough=True. Apprise applique tout de même le traitement normal du titre et du dépassement.

La CLI ne produit pas cet état. Elle fournit toujours body_format=TEXT sauf si l’appelant utilise --input-format pour le remplacer.

NotifyBase utilise le texte brut par défaut. Définissez notify_format lorsque le service accepte un autre format :

from apprise import NotifyFormat
from apprise.plugins.base import NotifyBase
class NotifyExample(NotifyBase):
notify_format = NotifyFormat.MARKDOWN

La plupart des plugins prennent en charge un seul format. Dans ce cas, self.notify_format suffit. Si un service accepte plusieurs formats, utilisez body_format dans send() pour connaître celui choisi par Apprise.

NotifyFormat.TEXT est la valeur par défaut lorsqu’un plugin ne déclare aucun format.

Source body_formatCorps transmis au plugin
TEXTTexte inchangé
MARKDOWNSource Markdown inchangée (aucune conversion destructive)
HTMLBalises supprimées ; structure de bloc réduite en texte brut
NoneCorps inchangé ; aucune conversion effectuée

La conversion Markdown vers texte ne modifie pas les caractères Markdown. Si le service les interprète, échappez-les dans send().

Si un service accepte plusieurs formats, déclarez-les dans l’ordre. Le premier élément est la valeur par défaut. Un tuple est le choix le plus clair pour un paramètre de classe :

from apprise import NotifyFormat
from apprise.plugins.base import NotifyBase
class NotifyExample(NotifyBase):
# Le premier élément est la valeur par défaut -- utilisée si rien
# d'autre n'en choisit une autre.
notify_format = (NotifyFormat.HTML, NotifyFormat.MARKDOWN)

Lisez body_format dans send() pour connaître le format choisi par Apprise :

def send(self, body, title="", notify_type=..., body_format=None,
body_passthrough=None, **kwargs):
if body_format == NotifyFormat.MARKDOWN:
...
else:
...

Vous pouvez aussi définir send_text(), send_html() ou send_markdown() au lieu d’utiliser une condition dans une seule méthode send(). Apprise appelle la méthode correspondant au format choisi et se replie sur send() lorsqu’aucune méthode ne correspond :

class NotifyExample(NotifyBase):
notify_format = (NotifyFormat.HTML, NotifyFormat.MARKDOWN)
def send_html(self, body, title="", notify_type=..., index=0, total=1, **kwargs):
...
def send_markdown(self, body, title="", notify_type=..., index=0, total=1, **kwargs):
...
# send() peut toujours servir de méthode de repli.

La plupart des plugins n’ont besoin que d’un format et peuvent ignorer cette section. Les plugins de relais, comme Custom JSON/XML/Form et Apprise API, peuvent prendre en charge les trois formats. Un relais ne doit transmettre le champ format que si body_passthrough vaut False ; sinon, il choisirait un format à la place de l’appelant.

Un outil qui explore Apprise().details() (un générateur d’URL, par exemple) peut lire les formats déclarés d’un service via args.format.supported — voir L’argument format pour le détail complet du schéma.

Sans remplacement, Apprise utilise notify_format, ou son premier élément pour un plugin multi-format :

example://host/token

Le paramètre format= change le format pour une seule URL de plugin :

example://host/token?format=text
example://host/token?format=markdown
example://host/token?format=html

Pour un plugin à format unique, le remplacement devient le format de cette instance. Pour un plugin multi-format, la valeur demandée doit apparaître dans notify_format. Une valeur non prise en charge produit un avertissement, puis Apprise applique ses règles de sélection habituelles.

Lorsque la logique du plugin doit distinguer un remplacement URL de la valeur par défaut de la classe, capturez la présence de format avant d’appeler NotifyBase.__init__ :

def __init__(self, **kwargs):
# Indique si l'appelant a explicitement défini un format.
self.format_overridden = "format" in kwargs
super().__init__(**kwargs)

La plupart des plugins n’ont pas besoin de suivre cette information séparément.

Au moment où send() s’exécute, Apprise a préparé les valeurs suivantes :

  • body : converti et dimensionné selon overflow_mode. Apprise peut ajouter des caractères Markdown de fermeture lorsqu’il découpe du texte mis en forme.
  • title : tronqué à title_maxlen caractères. Lorsque title_maxlen <= 0, le titre a été fusionné dans body par la classe de base et arrive sous forme de chaîne vide.
  • body_format : le format de body, par exemple NotifyFormat.HTML ; il n’est jamais None à ce stade.
  • body_passthrough : True lorsqu’aucun format d’entrée n’a été déclaré et que la conversion automatique a été ignorée. Le traitement normal du titre et du dépassement s’applique toujours.

Utilisez body_format pour choisir le bon mode d’envoi. N’utilisez body_passthrough que si votre plugin doit distinguer une entrée intacte d’un contenu préparé par Apprise.

Apprise convertit le contenu en Markdown standard. Certains services utilisent une autre syntaxe :

  • Slack utilise mrkdwn plutôt que le Markdown standard
  • Telegram utilise MarkdownV2, sa propre syntaxe personnalisée
  • WhatsApp (via Evolution) et Google Chat ont chacun leur propre format
  • Email peut inclure à la fois du HTML et du texte brut dans le même message

Conservez ces règles dans le plugin du service. Le code de conversion partagé ne doit rien connaître de Slack, Telegram ou d’un autre service précis.

Surchargez dialect_convert() pour traduire le Markdown standard vers la syntaxe du service avant l’exécution de send() :

class NotifyExample(NotifyBase):
def dialect_convert(self, body, body_format=None, *args, **kwargs):
"""Adapte une partie préparée à la syntaxe du service."""
if body_format != NotifyFormat.MARKDOWN:
# Ce service n'a un dialecte propre que pour Markdown ;
# tout le reste passe inchangé.
return body
return self._commonmark_to_example_dialect(body)

Apprise ignore cette méthode lorsque body_passthrough vaut True. Retournez les formats non gérés sans les modifier. N’enregistrez aucun état et n’envoyez aucune notification ici. Apprise peut appeler cette méthode plusieurs fois pendant la préparation du message.

L’échappement propre au service peut allonger le texte. Pour SPLIT et TRUNCATE, Apprise vérifie de nouveau la taille après dialect_convert(). Le plugin n’a donc pas besoin de découper lui-même le corps.

Les services de discussion sans syntaxe de titre peuvent réutiliser commonmark_headings_to_bold(). Cet utilitaire transforme # Alerte en **Alerte** avant l’adaptation au dialecte et ne modifie pas le code en ligne ou délimité.

Les analyses de destination des liens nommés partagent un budget de travail pour l’ensemble du message. Une fois ce budget épuisé, chaque destination suivante fait l’objet d’une vérification courte et limitée ; les liens incorrects plus longs restent en texte brut afin d’éviter des analyses excessives.

Gestion du Débordement avant l’Adaptation au Service

Section intitulée « Gestion du Débordement avant l’Adaptation au Service »

Définissez overflow_mode pour choisir le comportement appliqué lorsque le contenu dépasse body_maxlen ou title_maxlen. Apprise s’en charge avant l’exécution de send().

ModeAppels à send()Garantie sur le corps
UPSTREAM (défaut)Un seulInchangé ; peut dépasser body_maxlen
TRUNCATEUn seulAu mieux près de body_maxlen
SPLITUn par blocAu mieux près de body_maxlen
from apprise.common import OverflowMode
class NotifyExample(NotifyBase):
body_maxlen = 4096
title_maxlen = 255
overflow_mode = OverflowMode.SPLIT

Apprise essaie de préserver la lisibilité du Markdown déclaré lorsqu’une découpe traverse sa mise en forme. L’échappement propre au service peut ensuite ajouter des caractères ; SPLIT et TRUNCATE vérifient donc de nouveau la taille après dialect_convert(). UPSTREAM n’impose pas la limite : le service reçoit le corps complet et le gère lui-même.

Le paramètre URL ?overflow= permet de changer le mode pour une seule URL de plugin. La valeur par défaut de la classe doit correspondre aux capacités du service.

Connaître Sa Position Parmi Plusieurs Appels à send()

Section intitulée « Connaître Sa Position Parmi Plusieurs Appels à send() »

Un appel à notify() peut produire plusieurs appels à send(). Acceptez index et total si votre plugin doit savoir si un bloc est le premier ou le dernier :

def send(self, body, title="", notify_type=..., index=0, total=1, **kwargs):
is_first = index == 0
is_last = index == total - 1
# ... reste de send() ...

Un message non découpé utilise index=0 et total=1. La plupart des plugins n’ont besoin d’aucun traitement particulier.

Le placement des pièces jointes n’utilise ni index ni total. Apprise transmet les pièces jointes à un seul appel à send() et choisit le premier par défaut. Pour les placer sur le dernier appel, surchargez _attachment_send_index() :

def _attachment_send_index(self, total: int) -> int:
# Place la pièce jointe sur le dernier appel plutôt que le premier.
return total - 1

Telegram utilise cette surcharge lorsqu’une pièce jointe doit suivre tous les blocs de texte.

  • apprise.conversion gère la conversion générale entre texte, HTML et Markdown.
  • La syntaxe, l’échappement et les charges utiles propres à un service appartiennent au plugin.
  • Surchargez dialect_convert() pour un format propre au service. Ne découpez pas le texte converti dans le plugin ; Apprise s’en charge déjà.
  • La suppression d’un plugin doit supprimer tout le comportement de ce service sans modifier le code de conversion partagé.
Questions ou commentaires ?

Documentation

Vous avez repéré une faute de frappe ou une erreur ?

Problèmes Techniques

Vous rencontrez un problème avec le code ? Ouvrez un ticket sur GitHub :

Conçu avec amour depuis le Canada