Développement de Plugins
Introduction
Section intitulée « Introduction »Les plugins Apprise, aussi appelés services, sont des classes Python qui
héritent de NotifyBase. La façon la plus rapide d’en créer un nouveau consiste
à partir d’un plugin fonctionnel proche de votre intégration cible, puis à
adapter sa forme d’URL, son parsing et sa logique d’envoi.
Ce modèle est idéal pour :
- écrire vers une destination locale (
stdout, fichier, syslog) ; - prototyper un schéma d’URL avant d’ajouter la couche réseau.
from .base import NotifyBasefrom ..common import NotifyTypefrom ..locale import gettext_lazy as _
class NotifyDemo(NotifyBase): service_name = _("Apprise Demo Notification") protocol = "demo" setup_url = "https://appriseit.com/services/demo/"
# Disable throttling for a purely local plugin request_rate_per_sec = 0
templates = ( "{schema}://", )
def url(self, *args, **kwargs): params = self.url_parameters(*args, **kwargs) return "{schema}://?{params}".format( schema=self.protocol, params=self.urlencode(params), )
def send(self, body, title="", notify_type=NotifyType.INFO, **kwargs): self.throttle() print(f"{notify_type} - {title} - {body}") return True
@staticmethod def parse_url(url): # demo:// has no host, so verify_host must be False return NotifyBase.parse_url(url, verify_host=False)Ce modèle est idéal pour :
- les intégrations de type webhook ;
- les API REST avec jetons et authentification facultative.
import jsonimport requests
from ..common import NotifyTypefrom ..url import PrivacyModefrom ..utils import parse_boolfrom ..locale import gettext_lazy as _from .base import NotifyBase
class NotifyDemoHTTP(NotifyBase): service_name = _("Apprise Demo Notification") protocol = "demo" secure_protocol = "demos" setup_url = "https://appriseit.com/services/demo/"
templates = ( "{schema}://{host}/{apikey}", "{schema}://{host}:{port}/{apikey}", "{schema}://{user}@{host}/{apikey}", "{schema}://{user}@{host}:{port}/{apikey}", "{schema}://{user}:{password}@{host}/{apikey}", "{schema}://{user}:{password}@{host}:{port}/{apikey}", )
template_tokens = dict( NotifyBase.template_tokens, **{ "host": { "name": _("Hostname"), "type": "string", "required": True, }, "port": { "name": _("Port"), "type": "int", "min": 1, "max": 65535, }, "user": { "name": _("Username"), "type": "string", }, "password": { "name": _("Password"), "type": "string", "private": True, }, "apikey": { "name": _("API Key"), "type": "string", "private": True, }, }, )
def __init__(self, apikey, **kwargs): super().__init__(**kwargs)
self.apikey = apikey if not self.apikey: raise TypeError(f"An invalid API key ({apikey}) was specified.")
@property def url_identifier(self): """A stable tuple used by url_id() and persistent storage.
It should uniquely identify the URL configuration so stored/cached data does not clobber different configurations.
Do not include: - targets (channels, recipients, endpoints) - most GET parameters, unless they fundamentally change upstream behaviour """ default_port = 443 if self.secure else 80 return ( self.secure_protocol if self.secure else self.protocol, self.user, self.password, self.host, self.port if self.port else default_port, str(self.apikey), )
def url(self, privacy=False, *args, **kwargs): params = self.url_parameters(privacy=privacy, *args, **kwargs)
auth = "" if self.user and self.password: auth = "{user}:{password}@".format( user=self.quote(self.user, safe=""), password=self.pprint( self.password, privacy, mode=PrivacyMode.Secret, safe="" ), ) elif self.user: auth = "{user}@".format(user=self.quote(self.user, safe=""))
default_port = 443 if self.secure else 80 schema = self.secure_protocol if self.secure else self.protocol port = ( "" if self.port is None or self.port == default_port else f":{self.port}" )
return "{schema}://{auth}{hostname}{port}/{apikey}?{params}".format( schema=schema, auth=auth, hostname=self.host, port=port, apikey=self.quote(self.apikey, safe=""), params=self.urlencode(params), )
def send(self, body, title="", notify_type=NotifyType.INFO, **kwargs): headers = { "User-Agent": self.app_id, "Content-Type": "application/json", "Authorization": f"Bearer {self.apikey}", }
payload = { "type": notify_type.value, "title": title, "body": body, }
self.throttle()
# prepare our port formatting port = f":{self.port}" if self.port else ""
try: r = requests.post( f"http://{self.host}{port}", data=json.dumps(payload), headers=headers, verify=self.verify_certificate, timeout=self.request_timeout, allow_redirects=self.redirects, ) if r.status_code < 200 or r.status_code >= 300: return False
except requests.RequestException: return False
return True
@staticmethod def parse_url(url): results = NotifyBase.parse_url(url) if not results: return results
try: results["apikey"] = NotifyDemoHTTP.split_path(results["fullpath"])[0] except IndexError: results["apikey"] = None
return resultsCopiez-collez ce point de départ. Recherchez TODO: et complétez les zones manquantes.
## TODO: Rename this file and class.# TODO: Add unit tests and update documentation.#import refrom typing import Any
from ..common import NotifyTypefrom ..locale import gettext_lazy as _from ..url import PrivacyModefrom .base import NotifyBase
class NotifyMyService(NotifyBase): """TODO: One line summary of the service."""
# TODO: Update these 3 fields service_name = _("My Service") protocol = "myservice" secure_protocol = "myservices"
# TODO: Add a public service landing page when available service_url = "https://example.invalid/" setup_url = "https://appriseit.com/services/myservice/"
# TODO: Update with valid URL shapes users can type templates = ( "{schema}://{host}/{token}", "{schema}://{host}:{port}/{token}", "{schema}://{user}:{password}@{host}/{token}", "{schema}://{user}:{password}@{host}:{port}/{token}", )
# Tokens must map to __init__ arguments (directly or via map_to) template_tokens = dict(NotifyBase.template_tokens, **{ "token": { "name": _("Access Token"), "type": "string", "private": True, "required": True, # Optional validation, must be a 2-tuple: (pattern, flags) "regex": (r"^.+$", None), }, })
# Optional query string arguments. # If you define an argument here, it should be consumed in parse_url(). template_args = dict(NotifyBase.template_args, **{ "mode": { "name": _("Mode"), "type": "choice:string", "values": ("a", "b", "c"), "default": "a", }, "batch": { "name": _("Batch"), "type": "bool", "default": False, }, # Example alias, it must point to a real template_token or template_arg: "t": { "alias_of": "token", }, })
# Optional key/value injection patterns: # ?+Header=Value&-param=value&:extra=value template_kwargs = { "+": {"name": _("Header"), "prefix": "+"}, "-": {"name": _("GET Parameter"), "prefix": "-"}, ":": {"name": _("Payload Extra"), "prefix": ":"}, }
def __init__( self, token: str, mode: str | None = None, batch: bool | None = None, headers: dict[str, str] | None = None, params: dict[str, str] | None = None, payload: dict[str, str] | None = None, **kwargs: Any, ) -> None: super().__init__(**kwargs)
# TODO: Store required token(s) self.token = token if not self.token: raise TypeError(_("An invalid access token was specified."))
# TODO: Apply defaults using template_args where reasonable self.mode = ( self.template_args["mode"]["default"] if mode is None else str(mode).lower() ) if self.mode not in self.template_args["mode"]["values"]: raise TypeError(_("Invalid mode specified."))
self.batch = ( self.template_args["batch"]["default"] if batch is None else parse_bool(batch) )
# TODO: Store any custom key/value injections self.headers = headers or {} self.params = params or {} self.payload = payload or {}
def __len__(self) -> int: """Return how many targets this plugin will notify.
If you do not override this, the base implementation returns 1. """ # TODO: If you support targets, return len(self.targets) or 1 return 1
@property def url_identifier(self) -> tuple[Any, ...]: """Return a tuple that uniquely identifies this URL configuration.
This powers persistent storage, caching, and stable url_id() values. Do not include: - targets (channels, recipients, endpoints) - most GET parameters """ default_port = 443 if self.secure else 80 return ( self.secure_protocol if self.secure else self.protocol, self.user, self.password, self.host, self.port if self.port else default_port, self.token, )
def url(self, privacy: bool = False, *args: Any, **kwargs: Any) -> str: """Return a URL that can recreate the same object.""" params: dict[str, Any] = { "mode": self.mode, "batch": "yes" if self.batch else "no", }
# Add common URL parameters (format, overflow, emojis, store, tz, verify, rto, cto, etc.) params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
# Add key/value injections (optional) params.update({f"+{k}": v for k, v in self.headers.items()}) params.update({f"-{k}": v for k, v in self.params.items()}) params.update({f":{k}": v for k, v in self.payload.items()})
auth = "" if self.user and self.password: auth = "{user}:{password}@".format( user=self.quote(self.user, safe=""), password=self.pprint( self.password, privacy, mode=PrivacyMode.Secret, safe="" ), ) elif self.user: auth = "{user}@".format(user=self.quote(self.user, safe=""))
default_port = 443 if self.secure else 80 schema = self.secure_protocol if self.secure else self.protocol port = ( "" if self.port is None or self.port == default_port else f":{self.port}" )
return "{schema}://{auth}{host}{port}/{token}?{params}".format( schema=schema, auth=auth, host=self.host, port=port, token=self.pprint(self.token, privacy, mode=PrivacyMode.Secret, safe=""), params=self.urlencode(params), )
def send( self, body: str, title: str = "", notify_type: NotifyType = NotifyType.INFO, **kwargs: Any, ) -> bool: """TODO: Implement the upstream call(s).""" self.throttle()
# TODO: Replace with real implementation self.logger.debug( "Sending %s notification to %s", notify_type, self.host ) return True
@staticmethod def parse_url(url: str) -> dict[str, Any] | None: """Parse a URL into __init__ arguments.
Guideline: - Every template_args entry should be extracted here - Return keys should match __init__ parameters """ results = NotifyBase.parse_url(url) if not results: return results
# token is the first path element try: results["token"] = NotifyMyService.split_path(results["fullpath"])[0] except IndexError: results["token"] = None
# Example: consume query string args qsd = results.get("qsd", {}) if "mode" in qsd: results["mode"] = NotifyMyService.unquote(qsd.get("mode"))
if "batch" in qsd: results["batch"] = parse_bool(qsd.get("batch"))
# We allow for 't=' as defined in our kwargs as a token # The general rule of thumb is kwargs trump everything - always # so even if a token is defined above, we'll over-ride it now if "t" in qsd: results["token"] = NotifyMyService.unquote(qsd.get("t"))
# Consume key/value injections, if you support them results["headers"] = { NotifyMyService.unquote(k): NotifyMyService.unquote(v) for k, v in results.get("qsd+", {}).items() } results["params"] = { NotifyMyService.unquote(k): NotifyMyService.unquote(v) for k, v in results.get("qsd-", {}).items() } results["payload"] = { NotifyMyService.unquote(k): NotifyMyService.unquote(v) for k, v in results.get("qsd:", {}).items() }
return resultsVue d’Ensemble du Modèle
Section intitulée « Vue d’Ensemble du Modèle »Parsing d’URL
Section intitulée « Parsing d’URL »Apprise détermine d’abord le schema:// qui lui a été fourni, puis appelle
la méthode YourPlugin.parse_url() du plugin de notification pour effectuer
le reste du traitement.
En général, le premier appel effectué dans votre fonction parse_url() consiste
à appeler la méthode parente (issue de NotifyBase). Cela permet à Apprise
d’extraire les éléments d’URL communs comme schema, host, port, user,
password et fullpath. Votre parse_url() doit se concentrer sur les
éléments spécifiques à votre service, puis renvoyer un dict dont les clés
correspondent à la signature de votre __init__().
Une règle importante, vérifiée par les tests unitaires, est que les métadonnées
de vos templates doivent correspondre proprement aux arguments de __init__(),
soit directement (même nom), soit via map_to.
Limitation de Débit, Vérification, Redirections et Timeouts
Section intitulée « Limitation de Débit, Vérification, Redirections et Timeouts »URLBase définit la vérification SSL, le comportement des redirections et les
timeouts socket communs en tant qu’arguments d’URL. Passez toujours les
attributs correspondants dans chaque appel requests.* de votre plugin :
verifycontrôleverify_certificate— à passer viaverify=self.verify_certificateredirectcontrôleredirects— à passer viaallow_redirects=self.redirectsrtocontrôle le timeout de lecture socketctocontrôle le timeout de connexion socket
url_identifier
Section intitulée « url_identifier »La propriété url_identifier sert à distinguer une configuration d’une autre
de manière unique, afin que les données en cache ou persistantes n’écrasent pas
des configurations différentes. C’est aussi ainsi qu’Apprise génère un identifiant
unique cohérent et stable à partir d’une URL.
- Incluez le schéma ou protocole, les identifiants et l’identité de connexion amont.
- Excluez les cibles (canaux, destinataires, endpoints).
- Excluez la plupart des paramètres GET. N’incluez un paramètre GET que s’il change fondamentalement le comportement de la communication amont.
Si deux URL décrivent la même configuration effective, elles devraient générer
le même url_identifier, et donc le même url_id().
Notez également l’interrupteur de stockage :
- Si
store=noest défini,url_id()renvoieNone, eturl()doit conserverstore=nodans sa sortie.
Exemple :
@propertydef url_identifier(self): return ( self.secure_protocol if self.secure else self.protocol, self.user, self.password, self.host, self.port if self.port else (443 if self.secure else 80), )Activation du Stockage Persistant (storage_mode)
Section intitulée « Activation du Stockage Persistant (storage_mode) »Si votre plugin effectue des consultations répétées en amont (jetons OAuth, appels de découverte, identifiants résolus, vérifications de capacités), vous devriez envisager d’activer le stockage persistant. Cela vous évitera de refaire ces vérifications plus tard et accélérera le plugin.
Pour l’activer, définissez un storage_mode au niveau de la classe :
from apprise.common import PersistentStoreMode
class NotifyMyService(NotifyBase): # ... storage_mode = PersistentStoreMode.AUTORecommandations :
- Utilisez
PersistentStoreMode.AUTOpour la plupart des plugins. - Utilisez
PersistentStoreMode.FLUSHuniquement lorsque les données mises en cache sont coûteuses à recalculer et que vous voulez une meilleure durabilité. - Conservez le comportement par défaut (mémoire uniquement) si votre plugin ne doit jamais persister d’état, ou si le cache apporte peu de bénéfice.
Combiné à un url_identifier bien formé (identité de configuration uniquement),
cela permet à plusieurs instances visant des destinataires différents de
réutiliser le même cache pour une même configuration amont.
Cibles et __len__
Section intitulée « Cibles et __len__ »__len__() permet aux auteurs et aux outils de savoir combien de cibles sont
chargées dans un plugin.
- Si vous ne la redéfinissez pas, l’implémentation de base renvoie
1. - Redéfinissez-la dans les plugins qui prennent en charge plusieurs cibles, généralement en renvoyant
len(self.targets)avec un minimum de1.
Requirements et Dépendances Optionnelles
Section intitulée « Requirements et Dépendances Optionnelles »Si votre plugin a besoin de paquets supplémentaires, déclarez-les dans
requirements afin qu’Apprise puisse indiquer ce qui est requis ou recommandé.
from ..locale import gettext_lazy as _
requirements = { "details": _("This plugin requires cryptography for message signing."), "packages_required": ["cryptography>=42"], "packages_recommended": ["orjson>=3"],}runtime_deps() : Indice d’Éviction Mémoire
Section intitulée « runtime_deps() : Indice d’Éviction Mémoire »Si votre plugin importe conditionnellement une bibliothèque tierce lourde,
redéfinissez aussi runtime_deps() pour indiquer au gestionnaire de plugins
quels noms de paquets de premier niveau ont été chargés.
class NotifyMyService(NotifyBase): # ...
@staticmethod def runtime_deps(): return ("mylibrary",)La valeur renvoyée est un tuple de noms de paquets de premier niveau
importables : la même chaîne que vous passeriez à import, et non le nom
du paquet pip (par exemple "paho" pour paho-mqtt).
Le gestionnaire de notifications
maintient un compteur de références pour chaque bibliothèque déclarée. Lorsque
tous les plugins qui utilisent une bibliothèque donnée sont désactivés et que
N_MGR.evict_on_disable vaut True, le gestionnaire retire cette bibliothèque,
ainsi que tous ses sous-modules, de sys.modules, libérant ainsi les objets
Python associés en mémoire.
- Renvoyez la valeur par défaut de la classe de base
()(tuple vide) si votre plugin n’a pas de dépendances runtime optionnelles justifiant une éviction. - Les sous-modules sont gérés automatiquement ; ne déclarez que le nom de premier niveau.
- Cela est distinct de
requirements, qui nomme des paquets pip à des fins de reporting humainement lisible.runtime_deps()nomme les paquets importables pour la gestion mémoire à l’exécution.
Valeurs par Défaut de la Base Plugin
Section intitulée « Valeurs par Défaut de la Base Plugin »La section suivante récapitule les valeurs par défaut des variables définies automatiquement dans votre plugin, sauf surcharge explicite.
Valeurs par Défaut de URLBase
Section intitulée « Valeurs par Défaut de URLBase »NotifyBase hérite de l’objet URLBase, qui définit les valeurs par défaut suivantes :
| Attribut | Défaut | Rôle | Quand le Surcharger |
|---|---|---|---|
request_rate_per_sec | 0 | Intervalle de limitation de débit de base (secondes). 0 désactive la limitation au niveau de URLBase. | À surcharger en général dans NotifyBase ou votre plugin, pas dans URLBase. |
socket_connect_timeout | 4.0 | Timeout de connexion par défaut, utilisé quand cto n’est pas fourni. | Si votre service nécessite régulièrement des handshakes TCP plus longs. |
socket_read_timeout | 4.0 | Timeout de lecture par défaut, utilisé quand rto n’est pas fourni. | Si votre service répond lentement ou diffuse des réponses en streaming. |
verify_certificate | True | Vérification des certificats SSL, utilisée quand verify n’est pas fourni. | Seulement si votre service tourne dans des réseaux contrôlés avec certificats auto-signés et que verify=no vous convient. |
redirects | True | Suivi des redirections HTTP, utilisé quand redirect n’est pas fourni. Se rabat sur asset.http_redirects. | Définir à False sur l’asset pour désactiver les redirections globalement si votre environnement l’exige. |
templates | () | Templates de documentation. | Presque toujours, afin que les outils et la documentation décrivent correctement le plugin. |
template_tokens | {} | Métadonnées décrivant les jetons du chemin URL. | Presque toujours, pour les schémas non triviaux. |
template_args | { verify, redirect, rto, cto } | Arguments d’URL communs et leurs valeurs par défaut. | En général, on les étend plutôt que de les remplacer. |
template_kwargs | {} | Métadonnées pour les arguments clé/valeur préfixés. | Si vous prenez en charge +headers, :payload, -params, etc. |
Valeurs par Défaut de NotifyBase
Section intitulée « Valeurs par Défaut de NotifyBase »Votre plugin devrait hériter de NotifyBase, ce qui lui donne les valeurs par défaut suivantes :
| Attribut | Défaut | Rôle | Quand le Surcharger |
|---|---|---|---|
enabled | True | Si False, le plugin n’est pas utilisé. | À désactiver pour des raisons spécifiques à la plateforme ou aux dépendances. |
category | "native" | Classe l’origine du plugin (native vs custom). | En général, laissez tel quel. |
requirements.details | None | Texte de requirements lisible humainement. | Si vous devez expliquer des paquets optionnels ou requis. |
requirements.packages_required | [] | Paquets requis pour le fonctionnement complet. | Si votre plugin nécessite des bibliothèques supplémentaires. |
requirements.packages_recommended | [] | Paquets optionnels qui améliorent le fonctionnement. | Si le plugin peut tourner sans eux, mais en tire bénéfice. |
service_url | None | URL du fournisseur ou du produit amont. | Pour les services publics, définissez-la. |
setup_url | None | Page de configuration Apprise pour votre service. | Définissez-la vers votre page appriseit.com/services/<service>/. |
request_rate_per_sec | 5.5 | Intervalle de limitation de débit par défaut (secondes). | Ajustez-le selon les limites du fournisseur, ou mettez 0 en local. |
image_size | None | Taille d’image préférée, pour le pré-redimensionnement. | À définir si votre service attend une taille précise. |
body_maxlen | 32768 | Nombre maximal de caractères du corps avant troncature. | À régler selon les contraintes amont. |
title_maxlen | 250 | Nombre maximal de caractères du titre. Mettez 0 si les titres ne sont pas pris en charge. | Mettez 0 pour les endpoints sans titre, ou ajustez selon le fournisseur. |
body_max_line_count | 0 | Nombre maximal de lignes conservées. 0 désactive la troncature par lignes. | Si le service amont est sensible au nombre de lignes. |
persistent_storage | True | Autorise l’utilisation du stockage persistant. | Si votre plugin ne doit jamais stocker d’identifiants ni d’état. |
storage_mode | memory | Mode de stockage persistant par défaut. | Rare, mais ajustable pour des comportements particuliers. |
timezone | None | Utilise le fuseau détecté par le serveur quand None. | Si votre service doit toujours fonctionner dans un fuseau précis. |
notify_format | text | Format de message par défaut. | Si votre service privilégie Markdown ou HTML. |
overflow_mode | upstream | Stratégie de dépassement par défaut. | Si vous voulez qu’Apprise découpe, tronque ou modifie le dépassement. |
interpret_emojis | False | Interprétation des emojis. | Si le service amont bénéficie d’une conversion des emojis. |
attachment_support | False | Activation des pièces jointes. | Définissez True si vous acceptez les pièces jointes. |
default_html_tag_id | "b" | Utilisé pour injecter le titre dans le corps pour les services sans titre. | Rare, sauf si vous souhaitez un formatage différent. |
Templates et Métadonnées
Section intitulée « Templates et Métadonnées »Types de Templates
Section intitulée « Types de Templates »Apprise valide les types à l’aide d’un motif strict :
((choice|list):)?(string|bool|int|float).
| Type | Utilisé dans | Signification | Directives requises | Notes |
|---|---|---|---|---|
string | tokens, args | Une seule chaîne | aucune | Courant pour les noms d’hôte, jetons et noms. |
int | tokens, args | Un seul entier | aucune | Utilisez min et max pour borner les ports, comptes, etc. |
float | tokens, args | Un seul flottant | aucune | Utilisez min et max pour fixer des bornes. |
bool | tokens, args | Un booléen | default (requis pour args) | Les args booléens doivent fournir une valeur par défaut. |
choice:string | tokens, args | Une valeur parmi un ensemble fixe | values | Les entrées choice doivent fournir values, et default doit en faire partie s’il est défini. |
choice:int | tokens, args | Un entier parmi un ensemble fixe | values | Utile pour des sélecteurs de mode ou des énumérations. |
choice:float | tokens, args | Un flottant parmi un ensemble fixe | values | Rare, mais pris en charge. |
list:string | tokens, args | Une liste de chaînes | delim | Les entrées de liste doivent fournir des délimiteurs. |
list:int | tokens, args | Une liste d’entiers | delim | Découpe puis conversion en entier. |
list:float | tokens, args | Une liste de flottants | delim | Découpe puis conversion en flottant. |
Autres règles importantes :
choice:booln’est pas autorisé ; utilisezboolà la place.regexdoit être un 2-tuple(pattern, option), et les motifs doivent commencer par^et se terminer par$.- Si
requiredouprivatene sont pas fournis, ils valentFalsepar défaut. - Si
valuesest un dictionnaire, il est converti en liste de clés.
Modèles d’Organisation des Templates
Section intitulée « Modèles d’Organisation des Templates »Ces exemples se concentrent uniquement sur la manière dont les métadonnées
de template, __init__() et parse_url() s’articulent.
Aucun jeton n’est requis lorsque les templates n’incluent pas d’entrées
personnalisées {token}.
class MyPlugin(NotifyBase): ## Plugin variables here (intentionally omitted)
secure_protocol = "foobar"
templates = ("{schema}://",)
def __init__(self, *args, **kwargs): # Rest of code here super().__init__(*args, **kwargs)Si vous n’utilisez que des jetons intégrés comme host, port, user
et password, vous pouvez omettre template_tokens. Ces jetons sont déjà
connus du framework.
class MyPlugin(NotifyBase): ## Plugin variables here (intentionally omitted)
secure_protocol = "foobar"
templates = ("{schema}://",)
def __init__(self, host, *args, **kwargs): # Rest of code here super().__init__(host=host, *args, **kwargs)Si vous définissez un template_token, template_arg ou template_kwarg,
vous devez le prendre en charge dans parse_url(), car le dict renvoyé
par parse_url() sert à initialiser la classe.
import re
from apprise.utils import parse_bool # example helper
class MyPlugin(NotifyBase): ## Plugin variables here (intentionally omitted)
secure_protocol = "foobar"
templates = ("{schema}://{token}",)
template_tokens = dict( NotifyBase.template_tokens, **{ "token": { "name": _("Auth Token"), "type": "string", "private": True, "required": True, "regex": (r"^[a-z0-9]+$", "i"), }, }, )
template_args = dict( NotifyBase.template_args, **{ "image": { "name": _("Include Image"), "type": "bool", "default": True, "map_to": "include_image", }, }, )
def __init__(self, host, token, include_image, *args, **kwargs): # Rest of code here super().__init__(host=host, *args, **kwargs)
@staticmethod def parse_url(url): results = NotifyBase.parse_url(url, verify_host=False)
# Token is in the host position in this example results["token"] = MyPlugin.unquote(results["host"])
results["include_image"] = parse_bool(results["qsd"].get( "image", MyPlugin.template_args['image']['default']))
return resultsPlusieurs noms de jetons peuvent pointer vers le même argument __init__()
via map_to. Les tests vérifient que toutes les valeurs de map_to
correspondent à des arguments valides de __init__() ou à l’un des mots-clés
reconnus par le framework.
import re
from apprise.utils import parse_bool # example helper
class MyPlugin(NotifyBase): ## Plugin variables here (intentionally omitted)
secure_protocol = "foobar"
templates = ("{schema}://{targets}",)
template_tokens = dict( NotifyBase.template_tokens, **{ "target_user": { "name": _("Target User"), "type": "string", "map_to": "targets", }, "target_stream": { "name": _("Target Stream"), "type": "string", "map_to": "targets", }, "targets": { "name": _("Targets"), "type": "list:string", "delim": ("/",), }, }, )
template_args = dict( NotifyBase.template_args, **{ "to": { "alias_of": "targets", }, }, )
def __init__(self, targets, *args, **kwargs): # Rest of code here super().__init__(*args, **kwargs)
@staticmethod def parse_url(url): results = NotifyBase.parse_url(url, verify_host=False)
# Store our targets results["targets"] = [MyPlugin.unquote(results["host"])] results["targets"].extend(MyPlugin.split_path(results["fullpath"]))
# Support an alias that is easier to express in YAML if "to" in results["qsd"] and len(results["qsd"]["to"]): results["targets"] += list( filter( bool, re.split(r"[ \t\r\n,#\\/]+", MyPlugin.unquote(results["qsd"]["to"])), ) )
return resultsRéférence des Directives de Template
Section intitulée « Référence des Directives de Template »Les tests unitaires imposent les clés autorisées et les contraintes de type.
| Directive | Utilisée dans | Signification |
|---|---|---|
name | tokens, args, kwargs | Libellé lisible humainement, généralement enveloppé dans gettext_lazy(). |
type | tokens, args | Type de valeur, validé par l’expression régulière stricte des types. |
required | tokens, args | Marque une entrée comme obligatoire. Si omis, vaut False. |
private | tokens, args | Marque une entrée comme sensible. Si omis, vaut False. |
default | args | Valeur par défaut utilisée quand l’URL ne précise rien. Requise pour les args bool. |
values | types choice | Valeurs autorisées pour les types choice, requises pour tout type choice:*. |
min, max | int, float | Bornes pour les types numériques. |
regex | tokens, args | Regex de validation, toujours sous la forme (pattern, option) avec ancrage ^...$. |
delim | types list | Délimiteurs utilisés pour découper les listes, requis pour les types list:*. |
prefix | kwargs | Requis pour les kwargs, doit être l’un de :, + ou -. |
map_to | tokens, args, kwargs | Associe une clé à un autre nom d’argument __init__(). Les tests vérifient qu’il s’agit d’un argument valide ou d’un mot-clé reconnu. |
alias_of | args, kwargs | Déclare un alias pour un token ou argument existant, souvent pour faciliter la configuration YAML. |
group | tokens | Utilisé pour regrouper plusieurs jetons mappés dans une entrée de type liste. |
Les cibles map_to reconnues par le framework incluent les champs d’URL
courants et les arguments partagés, même s’ils ne figurent pas dans le
__init__() de votre plugin, comme user, password, host, port,
schema, fullpath, format, overflow, emojis, tz, verify,
cto, rto et store.
Règles de Mapping
Section intitulée « Règles de Mapping »Apprise utilise les métadonnées de vos templates comme un contrat, et les tests en garantissent la cohérence :
- Chaque élément de
template_tokensdoit correspondre à un véritable argument de__init__(), soit directement (même clé), soit viamap_to. - Les alias (
alias_of) sont autorisés dans les args et kwargs, mais pas dans les tokens. - Chaque
alias_ofdoit pointer vers un vrai token ou arg, et ne peut pas simplement se pointer lui-même, sauf s’il existe aussi dans les tokens. - Pour les entrées kwargs,
prefixest obligatoire et doit être l’un de:,+ou-.
Règle d’Héritage de alias_of
Section intitulée « Règle d’Héritage de alias_of »Les entrées alias ne doivent jamais redéclarer des propriétés héritées
de leur cible. Les clés suivantes sont interdites sur toute entrée contenant
alias_of :
private, type, required, regex, default, min, max, map_to, prefix, group
Ces propriétés sont résolues à l’exécution en suivant la chaîne d’alias.
Des consommateurs comme le générateur d’URL les lisent
depuis la cible, et non depuis l’alias lui-même. Les seuls ajouts autorisés
aux côtés de alias_of sont :
| Clé supplémentaire | Quand elle est autorisée |
|---|---|
name | Requise lorsque alias_of liste plusieurs cibles, afin de fournir un libellé de regroupement pour l’interface. |
delim | Autorisée lorsque la cible aliasée est de type list:*, afin de surcharger le ou les délimiteurs. |
Approches valides :
-
Alias simple :
alias_ofuniquement"t": {"alias_of": "token"}, -
Alias multiples :
alias_of+namepour le libellé de regroupement"tok": {"name": _("Token"),"alias_of": ("access_token", "token_a", "token_b"),}, -
Alias de liste avec surcharge du délimiteur
"to": {"alias_of": "targets","delim": (",", " "),},
Des propriétés supplémentaires en dehors de celles identifiées ci-dessus ne peuvent pas être utilisées. Par exemple, ceci n’est pas valide :
# do NOT redeclare inherited properties"t": { "alias_of": "token", "private": True, # <-- This should have been defined in what is being inherited},Exigences d’Aller-Retour
Section intitulée « Exigences d’Aller-Retour »Deux fonctions doivent travailler ensemble :
parse_url()doit extraire chaque argument exposé viatemplate_argsettemplate_kwargs.url()doit émettre une URL capable de recréer le même objet, et doit générer le mêmeurl_identifierlorsqu’il est réinstancié.
Référence du Schéma de Template
Section intitulée « Référence du Schéma de Template »Ce tableau documente les directives les plus courantes prises en charge par
template_tokens, template_args et template_kwargs.
| Directive | Utilisée dans | Signification |
|---|---|---|
name | tokens, args, kwargs | Libellé lisible humainement, généralement enveloppé dans gettext_lazy(). |
type | tokens, args | Type de valeur, comme string, int, bool, choice:string, list:string. |
required | tokens, args | Marque une entrée comme obligatoire pour l’initialisation ou la validation. |
default | args | Valeur par défaut utilisée quand l’URL ne précise rien. Requise pour les types bool. |
private | tokens, args | Marque une valeur comme sensible et destinée à être masquée dans les vues de confidentialité. |
regex | tokens, args | Un 2-tuple (pattern, flags) utilisé pour valider les valeurs. Les motifs doivent être ancrés avec ^ et $. |
values | types choice | Valeurs autorisées pour les types choice:*. Si un default est fourni, il doit appartenir à values. |
delim | types list | Délimiteurs autorisés pour les types list:*. |
prefix | kwargs | Requis pour les entrées kwargs ; définit le préfixe d’injection :, + ou -. |
map_to | tokens, args, kwargs | Associe la clé de directive à un autre nom d’argument __init__(). |
alias_of | args, kwargs | Fournit un nom alternatif se comportant exactement comme un autre token ou argument. |
templates
Section intitulée « templates »- Un tuple de motifs d’URL (chaînes) montrant les formes valides.
- Utilisez
{schema}dans les templates, et gardez des jetons cohérents d’un template à l’autre.
Il s’agit à la fois de documentation et de métadonnées structurées pouvant être utilisées par les outils pour décrire les formes d’URL prises en charge.
template_tokens
Section intitulée « template_tokens »Variables issues de la structure centrale de l’URL, comme :
schema://credentials/direction/?options= | | | variables here |template_args
Section intitulée « template_args »Les args décrivent les arguments de query string. NotifyBase fournit déjà
format, overflow, emojis, store et tz. URLBase fournit déjà
verify, rto et cto.
Modèles fréquents :
- utilisez
alias_ofpour ajouter des synonymes ; - utilisez
map_topour faire correspondre des noms d’arguments orientés utilisateur avec les paramètres de votre__init__(); - utilisez
defaultpour documenter clairement le comportement.
template_kwargs
Section intitulée « template_kwargs »Les kwargs servent aux arguments préfixés pouvant apparaître plusieurs fois, typiquement pour l’injection clé/valeur :
+est souvent utilisé pour les headers ;-est souvent utilisé pour les paramètres d’URL ;:est souvent utilisé pour les champs supplémentaires du payload.
Chaque entrée template_kwargs exige un prefix et un name, et ces
informations sont aussi exposées dans le générateur d’URL
sous la forme d’un tableau Paramètres Personnalisés où les utilisateurs peuvent
ajouter autant de lignes clé/valeur qu’ils le souhaitent.
template_kwargs = dict( NotifyBase.template_kwargs, **{ "headers": { "name": _("HTTP Header"), "prefix": "+", }, "params": { "name": _("GET Parameter"), "prefix": "-", }, "payload": { "name": _("Payload Extra"), "prefix": ":", }, },)Dans une URL, cela ressemble à :
?+X-Custom-Header=foo&-debug=1&:extra=bar
Requirements Et Dépendances Optionnelles
Section intitulée « Requirements Et Dépendances Optionnelles »Si votre plugin nécessite des paquets supplémentaires, déclarez-les dans
requirements afin qu’Apprise puisse indiquer ce qui est requis ou recommandé.
Exemple :
from ..locale import gettext_lazy as _
requirements = { "details": _("This plugin requires cryptography for message signing."), "packages_required": ["cryptography>=42"], "packages_recommended": ["orjson>=3"],}Prise en Charge des Pièces Jointes
Section intitulée « Prise en Charge des Pièces Jointes »Les plugins qui prennent en charge les pièces jointes doivent déclarer explicitement cette prise en charge en définissant l’attribut de classe suivant :
class NotifyExample(NotifyBase): attachment_support = TrueUne fois activée, les pièces jointes sont fournies au plugin sous la forme
d’une liste d’objets AppriseAttachment. Ces objets abstraient l’accès aux
fichiers et peuvent reposer sur des fichiers locaux, des données en mémoire
ou des ressources HTTP distantes.
Les détails complets sur la gestion des pièces jointes, le cycle de vie, les limites de taille et les modèles d’implémentation courants sont documentés ici :
Il est fortement recommandé aux auteurs de consulter cette documentation avant d’implémenter cette prise en charge, car une gestion incorrecte peut facilement entraîner une consommation mémoire excessive ou des envois échoués.
Intégration avec le Générateur d’URL
Section intitulée « Intégration avec le Générateur d’URL »Le générateur d’URL Apprise construit son formulaire entièrement à partir des métadonnées exportées par votre plugin. Des métadonnées bien formées offrent une expérience plus propre et conviviale.
Checklist de Qualité pour le Générateur d’URL
Section intitulée « Checklist de Qualité pour le Générateur d’URL »| Élément à définir | Pourquoi c’est important |
|---|---|
service_name | Affiché comme nom du service dans les résultats de recherche. |
service_url | Alimente le lien “Site du service” à côté du formulaire. |
setup_url | Alimente le lien “Guide de configuration” pour les instructions pas à pas. |
name sur chaque token/arg | Apparaît comme libellé du champ du formulaire. Sans lui, la clé brute est affichée. |
private: True sur les secrets | Rend le champ comme un mot de passe avec bouton d’affichage, et empêche la valeur de fuiter dans la barre d’adresse du navigateur. |
required: True sur les tokens obligatoires | Marque les champs d’un astérisque (*) et bloque la génération d’URL tant qu’ils ne sont pas remplis. |
regex sur les tokens validés | Fournit des indications de format côté client (non imposées dans le navigateur, mais exposées dans les métadonnées du schéma). |
default sur les args optionnels | Affiché comme texte indicatif dans les listes déroulantes et les champs numériques. |
templates ordonnés simple → complexe | Le générateur d’URL utilise par défaut le template le plus complexe afin que tous les champs soient visibles dès le départ. |
Ordre des Templates
Section intitulée « Ordre des Templates »Le générateur d’URL choisit par défaut le template le plus complexe
(celui qui contient le plus de champs {token}). À mesure que l’utilisateur
remplit des champs, il bascule vers le template le plus simple qui couvre
tous les jetons renseignés.
Ordonnez votre tuple templates du plus simple au plus complexe :
templates = ( "{schema}://{token}", # simplest — no targets "{schema}://{token}/{targets}", # adds optional targets "{schema}://{botname}@{token}/{targets}", # most complex)L’Importance du Flag private
Section intitulée « L’Importance du Flag private »Marquez chaque clé d’API, jeton, mot de passe ou secret avec "private": True
dans template_tokens comme dans template_args. Le générateur d’URL :
- rend le champ comme un mot de passe avec un bouton d’affichage ;
- affiche
••••••dans l’URL assemblée à la place de la vraie valeur ; - exclut la valeur de la barre d’adresse du navigateur afin que les liens partagés n’exposent jamais les secrets.
Les entrées alias (alias_of) héritent du flag private de leur cible :
ne le redéclarez pas sur l’alias.
service_url vs setup_url
Section intitulée « service_url vs setup_url »| Attribut | Rôle |
|---|---|
service_url | Lien vers la page d’accueil du fournisseur ou du produit amont (par exemple https://discord.com/). Affiché comme “Site du service” dans le générateur d’URL. |
setup_url | Lien vers le guide d’intégration spécifique à Apprise sur appriseit.com (par exemple https://appriseit.com/services/discord/). Affiché comme “Guide de configuration”. |
Les deux devraient toujours être définis. Les plugins génériques ou purement
locaux (par exemple MQTT, notifications Windows) peuvent omettre service_url
s’il n’existe pas d’URL amont pertinente.
Format et Dépassement
Section intitulée « Format et Dépassement »Notez également que NotifyBase fournit des drapeaux de comportement au niveau
de l’URL, comme format, overflow, emojis et le stockage persistant (store).
Questions ou commentaires ?
Documentation
Vous avez repéré une faute de frappe ou une erreur ? Signalez-la ou proposez une correction .
Problèmes Techniques
Vous rencontrez un problème avec le code ? Ouvrez un ticket sur GitHub :