Pipeline des formats de message
Lors de l’écriture d’un plugin, vous déclarez notify_format pour indiquer à Apprise le format attendu par votre service en amont. Lorsqu’une notification arrive, Apprise résout le format déclaré par l’appelant (body_format), convertit le corps si nécessaire, puis le transmet — déjà dans notify_format — à votre plugin. Au moment où send() s’exécute, le corps est pré-formaté. Votre code n’a pas besoin de le reconvertir.
| Valeur | Signification | Définie par |
|---|---|---|
body_format | Format du message source | notify(), AppriseAsset, CLI ou appelant API |
notify_format | Format préparé pour le service en amont | Classe du plugin, éventuellement remplacé par ?format= |
Ces valeurs sont indépendantes. Une même source HTML peut devenir du texte brut pour un plugin, du Markdown pour un autre et rester en HTML pour un troisième — le tout dans un seul appel à notify().
Comment body_format est Résolu
Section intitulée « Comment body_format est Résolu »Le format source n’est pas toujours None. Il dépend de l’interface qui déclenche la notification :
| Interface | Valeur par défaut lorsqu’aucun format n’est déclaré |
|---|---|
| CLI | text — la CLI définit toujours un format source ; remplacez-le avec --input-format |
| Bibliothèque Python | none — sauf si body_format est passé à notify() ou prédéfini dans AppriseAsset |
| Apprise API | none — sauf si format est inclus dans la charge utile de la requête |
Consultez Formatage pour la perspective utilisateur sur ces valeurs par défaut et leur impact sur la livraison vers des destinations mixtes.
Flux de Conversion
Section intitulée « Flux de Conversion »Pour chaque plugin sélectionné, Apprise :
- Résout
body_formatdepuis l’appel ànotify(). Lorsque la bibliothèque Python ou l’API sont utilisées sans format, cette valeur estNone. Lorsque la CLI est utilisée sans--input-format, cette valeur estTEXT. Sibody_formatn’est pas fourni ànotify(), il utiliseAppriseAsset.body_format, dont la valeur par défaut estNone. - Lit
notify_formatdepuis l’instance du plugin (valeur de la classe ou remplacement par?format=). - Lorsque
body_formatn’est pasNone, appelleconvert_between(body_format, notify_format, body)et remplace le corps par le résultat. Les résultats sont mis en cache par format de destination unique afin que la même conversion ne soit pas répétée pour plusieurs plugins partageant un format. - Appelle
plugin.notify(body=<converted>, body_format=<original_format>). Le corps qui parvient au plugin est déjà dansnotify_format. Le contenu avant conversion n’est pas conservé ni transmis séparément.body_formatne transporte que l’identifiant de format (par exempleNotifyFormat.HTML) — pas une copie du texte original du corps.
Apprise.notify(body=..., body_format=HTML) | | convert_between(HTML, plugin.notify_format, body) | (ignoré lorsque body_format vaut None) vplugin.notify( body=<déjà converti vers notify_format>, body_format=HTML, # le tag de format d'origine -- pas le corps original)La conversion du titre suit le même chemin, mais uniquement lorsque le plugin ne possède pas de champ titre natif (title_maxlen <= 0). Lorsque title_maxlen > 0, le titre est transmis tel quel et le plugin est responsable de tout formatage supplémentaire.
Lorsque le Format Source N’est Pas Déclaré
Section intitulée « Lorsque le Format Source N’est Pas Déclaré »Lorsque body_format vaut None (bibliothèque Python ou appel API sans format), Apprise ignore entièrement la conversion :
apobj.notify(body="<b>Bonjour</b>")Le plugin reçoit "<b>Bonjour</b>" inchangé ainsi que body_format=None. Déclarer un notify_format sur la classe du plugin ne modifie pas ce comportement — la conversion automatique n’a lieu que lorsque l’appelant déclare un format source.
La CLI ne produit pas cet état. Elle fournit toujours body_format=TEXT sauf si l’appelant utilise --input-format pour le remplacer.
Sélection de la Destination du Plugin
Section intitulée « Sélection de la Destination du Plugin »NotifyBase utilise le texte brut par défaut. Remplacez notify_format lorsque le service en amont accepte nativement une autre représentation :
from apprise import NotifyFormatfrom apprise.plugins.base import NotifyBase
class NotifyExample(NotifyBase): notify_format = NotifyFormat.MARKDOWNLa valeur effective est toujours accessible via self.notify_format.
NotifyFormat.TEXT est la valeur par défaut lorsqu’un plugin ne déclare aucun format.
Source body_format | Corps transmis au plugin |
|---|---|
TEXT | Texte inchangé |
MARKDOWN | Source Markdown inchangée (aucune conversion destructive) |
HTML | Balises supprimées ; structure de bloc réduite en texte brut |
None | Corps inchangé ; aucune conversion effectuée |
La conversion Markdown vers texte ne comporte actuellement aucune étape destructive ; la syntaxe Markdown arrive telle quelle. Si le service en amont interprète encore des caractères Markdown sur sa route texte, échappez-les dans send().
Définissez notify_format = NotifyFormat.MARKDOWN lorsque le service en amont accepte Markdown.
Source body_format | Corps transmis au plugin |
|---|---|
TEXT | Texte inchangé (le texte brut est déjà du Markdown valide) |
MARKDOWN | Markdown inchangé |
HTML | Converti en Markdown standard |
None | Corps inchangé ; aucune conversion effectuée |
La conversion HTML produit toujours du Markdown standard. Les services utilisant leur propre syntaxe Markdown personnalisée (Telegram MarkdownV2, Slack mrkdwn, WhatsApp, Google Chat) doivent traduire ce Markdown standard vers leur propre format à l’intérieur du plugin — l’étape de conversion intégrée d’Apprise est intentionnellement générique.
Définissez notify_format = NotifyFormat.HTML lorsque le service en amont accepte HTML.
Source body_format | Corps transmis au plugin |
|---|---|
TEXT | Texte échappé pour HTML avec sauts de ligne convertis en <br> |
MARKDOWN | HTML rendu |
HTML | HTML inchangé |
None | Corps inchangé ; aucune conversion effectuée |
Le plugin est responsable de tout assainissement supplémentaire, du filtrage des balises autorisées et de l’encapsulation de la charge utile exigés par l’API en amont.
Remplacement Facultatif avec ?format=
Section intitulée « Remplacement Facultatif avec ?format= »Sans remplacement, le notify_format de la classe du plugin est utilisé :
example://host/tokenUn paramètre format= dans la requête change la destination pour cette instance uniquement :
example://host/token?format=textexample://host/token?format=markdownexample://host/token?format=htmlLa destination effective est toujours self.notify_format, qu’elle provienne de la classe ou d’un remplacement URL. La conversion automatique exige toujours un body_format source différent de None.
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 ont seulement besoin de self.notify_format et n’ont pas besoin de suivre le remplacement séparément.
Ce que Reçoit le Plugin
Section intitulée « Ce que Reçoit le Plugin »Au moment où send() s’exécute, la conversion et le dimensionnement sont déjà effectués. Ce que reçoit votre plugin :
body— déjà converti versself.notify_formatet dimensionné selonoverflow_mode. AvecSPLIT, chaque appel àsend()reçoit un bloc d’au plusbody_maxlencaractères. AvecTRUNCATE, le corps est limité àbody_maxlen. AvecUPSTREAM(valeur par défaut), le corps arrive inchangé et peut dépasser la limite déclarée.title— tronqué àtitle_maxlencaractères. Lorsquetitle_maxlen <= 0, le titre a été fusionné dansbodypar la classe de base et arrive sous forme de chaîne vide.body_format— le format de départ du contenu (par exempleNotifyFormat.HTML) ; pas le texte original du corps lui-même
Les plugins lisent body_format pour décider si un travail supplémentaire est nécessaire. Si body_format est HTML et notify_format est MARKDOWN, le corps a été converti de HTML en Markdown standard par Apprise — et le plugin peut avoir besoin de traduire ce Markdown vers la syntaxe propre au service. Si body_format était déjà MARKDOWN lors de l’envoi par l’appelant, le corps est le Markdown de l’appelant et peut généralement être transmis tel quel.
Adaptation Propre au Service dans les Plugins
Section intitulée « Adaptation Propre au Service dans les Plugins »Apprise convertit le corps en Markdown standard. Ce que le plugin fait ensuite de ce Markdown est entièrement de sa responsabilité. Plusieurs services réels nécessitent des transformations supplémentaires au-delà de ce que fournit la conversion intégrée d’Apprise :
- Slack utilise
mrkdwnplutô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
Garder l’étape de conversion générique signifie qu’un plugin peut être supprimé et tout le comportement propre au service disparaît avec lui. Les exemples ci-dessous montrent les schémas les plus pratiques.
Gérez la traduction directement dans send(). Le corps est déjà converti au moment où send() s’exécute — vérifiez simplement les deux valeurs de format pour décider si un travail supplémentaire est nécessaire. Si notify_format est MARKDOWN et body_format est HTML, Apprise a converti le corps de HTML en Markdown standard avant d’appeler votre plugin, et vous pouvez maintenant le traduire vers la syntaxe propre au service. Slack utilise cette approche :
def send(self, body, title="", notify_type=..., body_format=None, **kwargs): if ( self.notify_format == NotifyFormat.MARKDOWN and body_format == NotifyFormat.HTML ): # Apprise a converti ce contenu de HTML en Markdown standard. # Traduire maintenant vers le dialecte attendu par ce service. body = self._commonmark_to_service_dialect(body)
# ... reste de send() ...Pour un exemple complet en situation réelle, consultez le code source du plugin Slack.
Certains services acceptent plusieurs formats dans la même requête. L’email en est l’exemple le plus clair : le plugin définit notify_format = NotifyFormat.HTML, donc le corps arrive en HTML. Comme de nombreux clients email affichent aussi une version en texte brut, le plugin en dérive une à partir du corps qu’il possède déjà :
from ..conversion import convert_between
def send(self, body, title="", notify_type=..., body_format=None, **kwargs): # body est déjà dans self.notify_format (HTML). # Construire une version texte brut pour les clients ne pouvant pas # afficher HTML. text_body = convert_between(NotifyFormat.HTML, NotifyFormat.TEXT, body)
payload = { "html": body, "text": text_body, } # ... envoyer la charge utile ...Le même schéma s’applique à tout service acceptant plusieurs représentations. Pushover et Brevo l’utilisent tous les deux.
Gestion du Débordement après Adaptation
Section intitulée « Gestion du Débordement après Adaptation »Définissez overflow_mode sur la classe pour déclarer comment la classe de base gère le contenu qui dépasse body_maxlen ou title_maxlen. Chaque appel à send() reçoit du contenu déjà dimensionné en conséquence — ne redécoupez pas ni ne retronquez dans send() :
| Mode | Appels à send() | Garantie sur le corps |
|---|---|---|
UPSTREAM (défaut) | Un seul | Inchangé ; peut dépasser body_maxlen |
TRUNCATE | Un seul | Limité à body_maxlen |
SPLIT | Un par bloc | Chaque bloc ≤ body_maxlen |
from apprise.common import OverflowMode
class NotifyExample(NotifyBase): body_maxlen = 4096 title_maxlen = 255 overflow_mode = OverflowMode.SPLITLe paramètre URL ?overflow= permet aux utilisateurs de remplacer le mode par instance, mais la valeur par défaut de la classe doit correspondre à ce que l’API en amont peut réellement accepter.
Frontières des Plugins
Section intitulée « Frontières des Plugins »apprise/conversion.pyconvertit entre texte, HTML et Markdown standard. Il ne doit contenir aucune règle de syntaxe propre à un service.- La syntaxe propre au service, les règles d’échappement, les champs de charge utile et les modes API appartiennent exclusivement au fichier plugin.
- Supprimer un fichier plugin doit supprimer tout le comportement de ce service sans aucune modification requise dans le code de conversion partagé.
- Les utilitaires génériques sans connaissance propre à un service peuvent résider dans
conversion.py. Exports actuels :build_backtick_run_index,find_unescaped_run,commonmark_escape_link_url,commonmark_scan_angle_dest,commonmark_emphasis_run,commonmark_force_close_spans,commonmark_prepend_title.
Liste de Contrôle d’Implémentation
Section intitulée « Liste de Contrôle d’Implémentation »Les sections précédentes couvrent l’ensemble du sujet. Cette liste résume les décisions clés pour référence rapide :
- Déclarez
notify_formatsur la classe pour indiquer à Apprise vers quel format convertir avant que votre plugin s’exécute. Omettez-le pour accepter du texte brut (TEXT). - Ne reconvertissez pas le corps dans
send(). Il est déjà dansself.notify_format. Utilisezbody_formatpour vérifier ce qu’était le contenu à l’origine, pas pour déclencher une nouvelle passe de conversion. - Vérifiez
body_formatpour décider si une mise en forme propre au service est nécessaire. Si votre service utilise sa propre syntaxe Markdown (Slack mrkdwn, Telegram MarkdownV2, WhatsApp), appliquez cette traduction uniquement lorsquebody_formatestHTML— c’est-à-dire que le corps était initialement du HTML et qu’Apprise l’a converti en Markdown standard. - Pour les charges utiles multi-format (comme l’email avec des parties HTML et texte séparées), appelez
convert_between()danssend()pour dériver les représentations supplémentaires à partir du corps déjà converti. - Traitez
?format=comme un remplacement facultatif par instance. La destination effective est toujoursself.notify_formatquelle que soit son origine. La plupart des plugins n’ont pas besoin de distinguer si la valeur provient de la classe ou d’un paramètre URL. - Définissez
overflow_mode,body_maxlenettitle_maxlensur la classe pour déclarer les limites et la stratégie de découpage de votre service en amont. Chaque appel àsend()reçoit du contenu déjà dimensionné selon ces limites — aucune modification supplémentaire de la charge utile n’est requise danssend(). - Testez la matrice complète :
body_format=None, chaque format source pris en charge, les remplacements?format=par URL et les messages découpés ou tronqués.
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 :