Intégrations
L’API Apprise est conçue comme une passerelle de notification unique. Au lieu de construire des webhooks directs vers Discord, Slack, l’email et les SMS dans chaque projet, envoyez vos messages à l’API Apprise et laissez-la router le message.
Points de Terminaison
Section intitulée « Points de Terminaison »| Si vous voulez… | Approche recommandée |
|---|---|
| Éviter d’intégrer Python dans votre application | API Apprise (Avec État ou Sans État) |
| Garder la logique de notification hors de votre codebase | API Apprise (Avec État) |
| Utiliser Apprise comme simple passerelle HTTP | API Apprise (Sans État) |
| Envoyer des notifications depuis des scripts ou la CLI | CLI Apprise ou Bibliothèque Python Apprise |
Sans État
Section intitulée « Sans État »Si vous ne pouvez pas, ou ne souhaitez pas, stocker la configuration côté serveur, vous pouvez également utiliser des notifications sans état :
POST /notify
Les appels sans état incluent les URL de destination Apprise dans la charge utile de la requête.
Consultez la référence des endpoints pour la liste complète des chemins pris en charge.
Avec État
Section intitulée « Avec État »Pour les notifications avec état, recommandées pour la plupart des intégrations, vous appelez un seul point de terminaison :
POST /notify/{KEY}
Ici, {KEY} identifie une configuration enregistrée sur le serveur.
Votre code client reste petit et stable, tandis que l’instance API Apprise conserve les véritables URL de notification ainsi que les éventuels tags de routage.
Construire l’URL de Requête
Section intitulée « Construire l’URL de Requête »Étant donné :
scheme:httpouhttpshost: le nom d’hôte ou l’adresse IP de l’API Appriseport: optionnel ; omettez-le pour 80 (http) ou 443 (https)key: votre clé de configuration enregistrée
Construisez ensuite :
BASE = {scheme}://{host}- Si un port non standard est utilisé :
BASE = {scheme}://{host}:{port} NOTIFY = {BASE}/notify/{key}
Champs de Payload Courants
Section intitulée « Champs de Payload Courants »L’API Apprise accepte dans de nombreux cas les payloads de formulaire comme les payloads JSON. Pour les intégrations, JSON est généralement le plus simple.
body(requis) : contenu du messagetitle(optionnel) : titre du messagetype(optionnel) :info(par défaut),success,warningoufailureformat(optionnel) :text(par défaut),markdownouhtmltag(optionnel, avec état) : route vers un sous-ensemble d’URL enregistrées
Les tags n’ont de sens que pour les appels avec état (/notify/{KEY}), parce que le serveur sait déjà quelles URL appartiennent à cette clé.
La valeur tag suit les règles suivantes :
Valeur de tag | Services sélectionnés |
|---|---|
"TagA" | Possède TagA |
"TagA TagB" | Possède TagA ET TagB |
"TagA+TagB" | Possède TagA ET TagB |
"TagA&TagB" | Possède TagA ET TagB |
"TagA,TagB" | Possède TagA OU TagB |
"TagA|TagB" | Possède TagA OU TagB |
"TagA TagC,TagB" | Possède (TagA ET TagC) OU TagB |
Pièces Jointes
Section intitulée « Pièces Jointes »Pour envoyer des pièces jointes, utilisez multipart/form-data.
- Utilisez
attachcomme nom de champ recommandé. Les aliasattachmentetattachmentssont également acceptés. - La valeur de la pièce jointe peut être :
- un upload de fichier local
- une URL distante que l’API Apprise télécharge puis transmet
- Les charges utiles JSON qui fournissent des pièces jointes distantes ou encodées utilisent les mêmes noms de champ.
Hooks de Mapping du Payload
Section intitulée « Hooks de Mapping du Payload »Si un outil tiers ne peut pas modifier ses clés JSON, l’API Apprise peut faire correspondre les champs entrants aux champs Apprise via des paramètres de requête préfixés par deux-points (:).
Règles de Mapping
Section intitulée « Règles de Mapping »| Syntaxe | Effet |
|---|---|
?:incoming_field=apprise_field | Renomme incoming_field en apprise_field |
?:incoming_field= | Supprime incoming_field du payload |
?:apprise_field=literal value | Force apprise_field à une chaîne fixe |
Exemple de champ plat — un outil envoie {"message": "Server Down"} :
POST /notify/{KEY}?:message=bodyMapping de Champs Imbriqués / Sous-champs
Section intitulée « Mapping de Champs Imbriqués / Sous-champs »Lorsqu’un payload tiers contient des objets imbriqués ou des tableaux, l’API Apprise peut les parcourir en utilisant la notation pointée pour les objets imbriqués et la notation entre crochets pour les éléments de tableau. Les deux syntaxes peuvent être librement mélangées du côté source de la règle de mapping.
Objets Imbriqués (Notation Pointée)
Section intitulée « Objets Imbriqués (Notation Pointée) »Parcourez des dictionnaires imbriqués avec un chemin séparé par des points :
# Payload from a monitoring tool:# {# "event": { "title": "CPU spike", "state": "critical" },# "component": { "name": "web-server-01" }# }curl -X POST \ -H "Content-Type: application/json" \ -d '{"event":{"title":"CPU spike","state":"critical"},"component":{"name":"web-server-01"}}' \ "http://apprise-host/notify/{KEY}?:event.title=title&:event.state=type&:component.name=body"Éléments de Tableau (Notation entre Crochets)
Section intitulée « Éléments de Tableau (Notation entre Crochets) »Ajoutez [N] après un nom de clé pour déréférencer l’index N de ce tableau. Les indices multiples et le mélange avec la notation pointée sont tous deux pris en charge :
# Payload from a forum / project-management webhook (e.g. Scoold / Para):# {# "items": [ { "title": "New post", "objectURI": "https://example.com/q/1234" } ]# }curl -g -X POST \ -H "Content-Type: application/json" \ -d '{"items":[{"title":"New post","objectURI":"https://example.com/q/1234"}]}' \ "http://apprise-host/notify/{KEY}?:items[0].title=title&:items[0].objectURI=body"Référence rapide des expressions de chemin :
| Clé source | Résout vers |
|---|---|
items[0] | Premier élément de items |
items[0].objectURI | Propriété .objectURI du premier élément |
matrix[0][1] | Ligne 0, colonne 1 d’un tableau 2D |
a[0][1][2].value[3] | Tableaux imbriqués -> dict -> tableau (6 traversées) |
Règles et limites :
- Seul le côté source de la règle peut utiliser la notation de chemin ; la cible doit être un champ Apprise plat (
title,body,type,format,tag, etc.). Ndans[N]doit être un entier positif ou nul. Les formeskey[abc](non entière) etkey[0/key0](crochet non apparié) sont rejetées immédiatement.- Si une étape est introuvable — clé absente, index hors limites ou nœud non liste à une étape d’index — le serveur renvoie 400 et journalise un
WARNING. Aucune notification n’est envoyée, ce qui rend les règles mal configurées visibles dans les logs au lieu d’être ignorées silencieusement. - La profondeur correspond au nombre total d’opérations de traversée : chaque recherche de clé dans un dict et chaque déréférencement d’index dans un tableau comptent pour une étape.
items[0].objectURI= 3 étapes ;a[0][1][2].value[3]= 6 étapes. - La profondeur maximale de traversée est de 5 par défaut. Définissez
APPRISE_WEBHOOK_MAPPING_MAX_DEPTHpour modifier cette limite.
Exemples
Section intitulée « Exemples »# Notification minimale sans état avec curlcurl -X POST "http://localhost:8000/notify" \ -H "Content-Type: application/json" \ -d '{ "urls": [ "discord://TOKEN/CHANNEL", "mailto://user:pass@example.com" ], "body": "Hello from Apprise (stateless)" }'# Notification sans état avec une pièce jointecurl -X POST "http://localhost:8000/notify" \ -F 'payload={ "urls": ["discord://TOKEN/CHANNEL"], "title": "Build Complete", "body": "Artifact attached" };type=application/json' \ -F "attach=@/path/to/file.txt"L’exemple suivant s’appuie sur la bibliothèque requests :
from __future__ import annotations
from dataclasses import dataclassfrom urllib.parse import quote
import requests
@dataclass(frozen=True)class AppriseStatelessApi: scheme: str host: str port: int | None = None base_path: str = ""
@property def notify_url(self) -> str: origin = f"{self.scheme}://{self.host}:{self.port}" if self.port else f"{self.scheme}://{self.host}" prefix = self.base_path.strip("/") path = f"/{prefix}/notify/" if prefix else f"/notify/" return f"{origin}{path}"
api = AppriseStatelessApi(scheme="http", host="localhost", port=8000)payload = { "urls": ["discord://TOKEN/CHANNEL", "mailto://user:pass@example.com"], "body": "Hello from Apprise (stateless)",}
resp = requests.post( api.notify_url, json=payload, headers={"Accept": "application/json"}, timeout=30,)resp.raise_for_status()
# An attachment example:
files = { "attach": open("/path/to/file.txt", "rb")}payload = { "urls": "discord://TOKEN/CHANNEL,mailto://user:pass@example.com", "body": "Artifact attached"}
resp = requests.post( api.url, json=payload, headers={"Accept": "application/json"}, files=files, timeout=30,)resp.raise_for_status()L’exemple suivant s’appuie sur la bibliothèque aiohttp :
from __future__ import annotations
from dataclasses import dataclassfrom pathlib import Pathfrom typing import Anyfrom urllib.parse import quote
import asyncioimport aiohttp
@dataclass(frozen=True)class AppriseStatelessApi: scheme: str host: str port: int | None = None base_path: str = ""
@property def notify_url(self) -> str: origin = f"{self.scheme}://{self.host}:{self.port}" if self.port else f"{self.scheme}://{self.host}" prefix = self.base_path.strip("/") path = f"/{prefix}/notify/" if prefix else f"/notify/" return f"{origin}{path}"
async def apprise_notify_json(url: str, payload: dict[str, Any]) -> None: timeout = aiohttp.ClientTimeout(total=30) async with aiohttp.ClientSession(timeout=timeout, headers={"Accept": "application/json"}) as session: async with session.post(url, json=payload) as resp: resp.raise_for_status()
async def apprise_notify_with_attachment(url: str, body: str, file_path: str) -> None: path = Path(file_path) form = aiohttp.FormData() form.add_field("body", body) # Le nom du champ peut être attach, attachment ou attachments form.add_field("attach", path.read_bytes(), filename=path.name)
timeout = aiohttp.ClientTimeout(total=60) async with aiohttp.ClientSession(timeout=timeout, headers={"Accept": "application/json"}) as session: async with session.post(url, data=form) as resp: resp.raise_for_status()
# Utilisation :api = AppriseStatelessApi(scheme="http", host="localhost", port=8000)
# Envoyer notre notification :asyncio.run(apprise_notify_json(api.notify_url, {"body": "Hello"}))
# Envoyer notre notification avec une pièce jointe :asyncio.run(apprise_notify_with_attachment( api.notify_url, { "title": "Strange abuse detected", "body": "See logs for details", "type": "warning", "tag": "cyber-team", }, '/var/log/nginx/access.log'))// Appel stateless à l'API Apprise avec fetch (TypeScript)const scheme = "http";const host = "localhost";const port = 8000;
const url = `${scheme}://${host}:${port}/notify`;
const payload = { urls: ["discord://TOKEN/CHANNEL", "slack://TOKEN/CHANNEL"], body: "Hello from Apprise (stateless)",};
await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload),});// Appel stateless à l'API Apprise avec fetch (JavaScript vanilla)const scheme = "http";const host = "localhost";const port = 8000;
fetch(`${scheme}://${host}:${port}/notify`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ urls: ["discord://TOKEN/CHANNEL"], body: "Hello from Apprise (stateless)", }),});<?php// Appel stateless à l'API Apprise avec PHP cURL
$payload = json_encode([ "urls" => [ "discord://TOKEN/CHANNEL", "mailto://user:pass@example.com" ], "body" => "Hello from Apprise (stateless)"]);
$ch = curl_init("http://localhost:8000/notify");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Content-Type: application/json"], CURLOPT_POSTFIELDS => $payload,]);
curl_exec($ch);curl_close($ch);package main
import ( "bytes" "encoding/json" "net/http")
func main() { payload := map[string]any{ "urls": []string{ "discord://TOKEN/CHANNEL", "mailto://user:pass@example.com", }, "body": "Hello from Apprise (stateless)", }
data, _ := json.Marshal(payload)
http.Post( "http://localhost:8000/notify", "application/json", bytes.NewBuffer(data), )}import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.net.http.HttpRequest.BodyPublishers;
public class AppriseNotifyStateless { public static void main(String[] args) { String url = "http://localhost:8000/notify";
// Payload JSON avec plusieurs URL String jsonPayload = """ { "urls": [ "discord://TOKEN/CHANNEL", "mailto://user:pass@example.com" ], "body": "Hello from Apprise (stateless)" } """;
HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Content-Type", "application/json") .POST(BodyPublishers.ofString(jsonPayload)) .build();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body) .thenAccept(System.out::println) .join(); }}Voici un exemple avec pièces jointes :
// Note: We construct a manual multipart body here for standard Java 11+import java.io.IOException;import java.nio.file.Files;import java.nio.file.Path;import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.util.UUID;
public class AppriseAttachment { public static void main(String[] args) throws IOException, InterruptedException { String url = "http://localhost:8000/notify"; String boundary = "---Boundary" + UUID.randomUUID().toString(); Path file = Path.of("/path/to/file.txt");
// Liste d'URL séparées par des virgules String targetUrls = "discord://TOKEN/CHANNEL,mailto://user:pass@example.com";
// Construire le body multipart String body = "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"urls\"\r\n\r\n" + targetUrls + "\r\n" + "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"body\"\r\n\r\n" + "Artifact attached (Stateless)\r\n" + "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"attach\"; filename=\"" + file.getFileName() + "\"\r\n" + "Content-Type: application/octet-stream\r\n\r\n" + Files.readString(file) + "\r\n" + "--" + boundary + "--";
HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Content-Type", "multipart/form-data; boundary=" + boundary) .POST(HttpRequest.BodyPublishers.ofString(body)) .build();
client.send(request, HttpResponse.BodyHandlers.discarding()); }}using System.Text;using System.Text.Json;
var url = "http://localhost:8000/notify";
var payload = new{ urls = new[] { "discord://TOKEN/CHANNEL", "mailto://user:pass@example.com" }, body = "Hello from Apprise (stateless)"};
using var client = new HttpClient();var json = JsonSerializer.Serialize(payload);var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);response.EnsureSuccessStatusCode();Voici un exemple utilisant des pièces jointes :
using System.Net.Http.Headers;
var url = "http://localhost:8000/notify";var filePath = "/path/to/file.txt";
using var client = new HttpClient();using var form = new MultipartFormDataContent();
// 1. Add URLs (Comma separated string)form.Add(new StringContent("discord://TOKEN/CHANNEL,mailto://user:pass@example.com"), "urls");
// 2. Add Bodyform.Add(new StringContent("Artifact attached (Stateless)"), "body");
// 3. Add Attachmentvar fileBytes = await File.ReadAllBytesAsync(filePath);var fileContent = new ByteArrayContent(fileBytes);fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream");form.Add(fileContent, "attach", Path.GetFileName(filePath));
var response = await client.PostAsync(url, form);response.EnsureSuccessStatusCode();$Uri = "http://localhost:8000/notify"$Payload = @{ urls = @( "discord://TOKEN/CHANNEL", "mailto://user:pass@example.com" ) body = "Hello from Apprise (stateless)"}
Invoke-RestMethod -Uri $Uri ` -Method Post ` -Body ($Payload | ConvertTo-Json) ` -ContentType "application/json"Voici un exemple utilisant des pièces jointes :
$Uri = "http://localhost:8000/notify"$FilePath = "/path/to/file.txt"
# PowerShell 6.1+ gère automatiquement le multipart si vous utilisez une table de hachage$Form = @{ # Passer les URL sous forme de chaîne séparée par des virgules urls = "discord://TOKEN/CHANNEL,mailto://user:pass@example.com" body = "Artifact attached (Stateless)" attach = Get-Item -Path $FilePath}
Invoke-RestMethod -Uri $Uri -Method Post -Form $Formrequire 'net/http'require 'json'require 'uri'
uri = URI('http://localhost:8000/notify')http = Net::HTTP.new(uri.host, uri.port)request = Net::HTTP::Post.new(uri.path, {'Content-Type' => 'application/json'})
request.body = { urls: [ 'discord://TOKEN/CHANNEL', 'mailto://user:pass@example.com' ], body: 'Hello from Apprise (stateless)'}.to_json
response = http.request(request)puts response.bodyVoici un exemple utilisant des pièces jointes :
require 'net/http/post/multipart' # gem install multipart-postrequire 'uri'
url = URI.parse('http://localhost:8000/notify')file_path = '/path/to/file.txt'
File.open(file_path) do |file| req = Net::HTTP::Post::Multipart.new(url.path, # Passer les URL sous forme de chaîne séparée par des virgules "urls" => "discord://TOKEN/CHANNEL,mailto://user:pass@example.com", "body" => "Artifact attached (Stateless)", "attach" => UploadIO.new(file, "application/octet-stream", File.basename(file_path)) )
Net::HTTP.start(url.host, url.port) do |http| http.request(req) endenduse reqwest::blocking::Client;use serde_json::json;
fn main() -> Result<(), Box<dyn std::error::Error>> { let client = Client::new();
let payload = json!({ "urls": [ "discord://TOKEN/CHANNEL", "slack://TOKEN/CHANNEL" ], "body": "Hello from Apprise (stateless)" });
client .post("http://localhost:8000/notify") .json(&payload) .send()?;
Ok(())}# Payload JSON (recommandé)SCHEME=httpHOST=localhostPORT=8000KEY=my-alerts
BASE="$SCHEME://$HOST"if [ ! -z "$PORT" ]; then BASE="$SCHEME://$HOST:$PORT"fiURL="$BASE/notify/$KEY"
curl -X POST \ -F "title=Build" \ -F "type=success" \ -F "body=Build complete" \ "$URL"Voici un autre exemple qui route selon les tags définis :
curl -X POST \ -F "type=warning" \ -F "tag=devops critical" \ -F "body=Disk space low" \ "$URL"# Pièce jointe (multipart)curl -X POST \ -F "body=See attached log" \ -F "attach=@/var/log/syslog" \ "$URL"L’exemple suivant s’appuie sur la bibliothèque requests :
from __future__ import annotations
from dataclasses import dataclassfrom urllib.parse import quote
import requests
@dataclass(frozen=True)class AppriseApi: scheme: str host: str key: str port: int | None = None base_path: str = ""
@property def notify_url(self) -> str: origin = f"{self.scheme}://{self.host}:{self.port}" if self.port else f"{self.scheme}://{self.host}" prefix = self.base_path.strip("/") path = f"/{prefix}/notify/{quote(self.key)}" if prefix else f"/notify/{quote(self.key)}" return f"{origin}{path}"
# Utilisation :api = AppriseApi(scheme="http", host="localhost", port=8000, key="my-alerts")payload = { "title": "Deploy", "body": "Deployed to production", "type": "success", "tag": "devops,admin",}
resp = requests.post( api.url, json=payload, headers={"Accept": "application/json"}, timeout=30,)resp.raise_for_status()L’exemple suivant s’appuie sur la bibliothèque aiohttp :
from __future__ import annotations
from dataclasses import dataclassfrom pathlib import Pathfrom typing import Anyfrom urllib.parse import quote
import asyncioimport aiohttp
@dataclass(frozen=True)class AppriseApi: scheme: str host: str key: str port: int | None = None base_path: str = ""
@property def notify_url(self) -> str: origin = f"{self.scheme}://{self.host}:{self.port}" if self.port else f"{self.scheme}://{self.host}" prefix = self.base_path.strip("/") path = f"/{prefix}/notify/{quote(self.key)}" if prefix else f"/notify/{quote(self.key)}" return f"{origin}{path}"
async def apprise_notify_json(url: str, payload: dict[str, Any]) -> None: timeout = aiohttp.ClientTimeout(total=30) async with aiohttp.ClientSession(timeout=timeout, headers={"Accept": "application/json"}) as session: async with session.post(url, json=payload) as resp: resp.raise_for_status()
async def apprise_notify_with_attachment(url: str, body: str, file_path: str) -> None: path = Path(file_path) form = aiohttp.FormData() form.add_field("body", body) # Le nom du champ peut être attach, attachment ou attachments form.add_field("attach", path.read_bytes(), filename=path.name)
timeout = aiohttp.ClientTimeout(total=60) async with aiohttp.ClientSession(timeout=timeout, headers={"Accept": "application/json"}) as session: async with session.post(url, data=form) as resp: resp.raise_for_status()
# Utilisation :api = AppriseApi(scheme="http", host="localhost", port=8000, key="my-alerts")
# Envoyer notre notification :asyncio.run(apprise_notify_json(api.notify_url, {"body": "Hello"}))
# Envoyer notre notification avec une pièce jointe :asyncio.run(apprise_notify_with_attachment( api.notify_url, { "title": "Strange abuse detected", "body": "See logs for details", "type": "warning", "tag": "cyber-team", }, '/var/log/nginx/access.log'))type AppriseType = "info" | "success" | "warning" | "failure";type AppriseFormat = "text" | "markdown" | "html";
interface NotifyPayload { body: string; title?: string; type?: AppriseType; format?: AppriseFormat; tag?: string;}
function buildNotifyUrl( scheme: "http" | "https", host: string, key: string, port?: number, basePath: string = "",): string { const origin = port ? `${scheme}://${host}:${port}` : `${scheme}://${host}`; const prefix = basePath ? `/${basePath.replace(/^\/+|\/+$/g, "")}` : ""; return `${origin}${prefix}/notify/${encodeURIComponent(key)}`;}
export async function appriseNotifyJson( url: string, payload: NotifyPayload,): Promise<void> { const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify(payload), });
if (!res.ok) { const text = await res.text().catch(() => ""); throw new Error(`Apprise API failed: ${res.status} ${text}`); }}
export async function appriseNotifyWithAttachment( url: string, payload: NotifyPayload, file: File,): Promise<void> { const form = new FormData(); if (payload.title) form.append("title", payload.title); if (payload.type) form.append("type", payload.type); if (payload.format) form.append("format", payload.format); if (payload.tag) form.append("tag", payload.tag); form.append("body", payload.body);
// Le nom du champ peut être attach, attachment ou attachments form.append("attach", file);
const res = await fetch(url, { method: "POST", headers: { Accept: "application/json", }, body: form, });
if (!res.ok) { const text = await res.text().catch(() => ""); throw new Error(`Apprise API failed: ${res.status} ${text}`); }}
// Exemple :const notifyUrl = buildNotifyUrl( "https", "apprise.example.net", "my-alerts", 443,);await appriseNotifyJson(notifyUrl, { title: "Deploy", body: "Deployed to production", type: "success", tag: "devops,admin",});function buildNotifyUrl({ scheme, host, port, key, basePath = "" }) { const origin = port ? `${scheme}://${host}:${port}` : `${scheme}://${host}`; const prefix = basePath ? `/${basePath.replace(/^\/+|\/+$/g, "")}` : ""; return `${origin}${prefix}/notify/${encodeURIComponent(key)}`;}
async function appriseNotify(url, payload) { const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify(payload), });
if (!res.ok) { const text = await res.text().catch(() => ""); throw new Error(`Apprise API failed: ${res.status} ${text}`); }}
// Exemple :const url = buildNotifyUrl({ scheme: "http", host: "localhost", port: 8000, key: "my-alerts",});
await appriseNotify(url, { title: "Backup", body: "Backup completed successfully.", type: "success",});<?php
function build_notify_url(string $scheme, string $host, string $key, ?int $port = null, string $base_path = ''): string { $origin = $port ? sprintf('%s://%s:%d', $scheme, $host, $port) : sprintf('%s://%s', $scheme, $host); $prefix = trim($base_path, '/'); $path = $prefix ? sprintf('/%s/notify/%s', $prefix, rawurlencode($key)) : sprintf('/notify/%s', rawurlencode($key)); return $origin . $path;}
function apprise_notify_json(string $url, array $payload): void { $ch = curl_init($url); if ($ch === false) { throw new RuntimeException('Failed to init curl'); }
$json = json_encode($payload); if ($json === false) { throw new RuntimeException('Failed to encode JSON'); }
curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Accept: application/json', ], CURLOPT_POSTFIELDS => $json, ]);
$resp = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($resp === false) { $err = curl_error($ch); curl_close($ch); throw new RuntimeException('Apprise API request failed: ' . $err); }
curl_close($ch);
if ($code < 200 || $code >= 300) { throw new RuntimeException(sprintf('Apprise API failed: %d %s', $code, trim((string)$resp))); }}
function apprise_notify_with_attachment(string $url, string $body, string $file_path): void { $ch = curl_init($url); if ($ch === false) { throw new RuntimeException('Failed to init curl'); }
$post = [ 'body' => $body, // Le nom du champ peut être attach, attachment ou attachments 'attach' => new CURLFile($file_path), ];
curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Accept: application/json', ], CURLOPT_POSTFIELDS => $post, ]);
$resp = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($resp === false) { $err = curl_error($ch); curl_close($ch); throw new RuntimeException('Apprise API request failed: ' . $err); }
curl_close($ch);
if ($code < 200 || $code >= 300) { throw new RuntimeException(sprintf('Apprise API failed: %d %s', $code, trim((string)$resp))); }}
// Exemple :$url = build_notify_url('http', 'localhost', 'my-alerts', 8000);
apprise_notify_json($url, [ 'title' => 'Deploy', 'body' => 'Deployed to production', 'type' => 'success', 'tag' => 'devops,admin',]);
apprise_notify_with_attachment($url, 'See attached log', '/var/log/syslog');package main
import ( "bytes" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "net/url" "os" "path" "strings")
type NotifyPayload struct { Body string `json:"body"` Title string `json:"title,omitempty"` Type string `json:"type,omitempty"` Format string `json:"format,omitempty"` Tag string `json:"tag,omitempty"`}
func buildNotifyURL(scheme, host, key string, port int, basePath string) (string, error) { u := &url.URL{Scheme: scheme, Host: host} if port != 0 { u.Host = fmt.Sprintf("%s:%d", host, port) } cleanBase := strings.Trim(basePath, "/") if cleanBase != "" { u.Path = "/" + cleanBase } u.Path = path.Join(u.Path, "notify", key) return u.String(), nil}
func notifyJSON(endpoint string, payload NotifyPayload) error { b, err := json.Marshal(payload) if err != nil { return err }
req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(b)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req) if err != nil { return err } defer res.Body.Close()
if res.StatusCode < 200 || res.StatusCode >= 300 { body, _ := io.ReadAll(res.Body) return fmt.Errorf("apprise api failed: %d %s", res.StatusCode, strings.TrimSpace(string(body))) }
return nil}
func notifyWithAttachment(endpoint string, payload NotifyPayload, filePath string) error { var buf bytes.Buffer w := multipart.NewWriter(&buf)
// Champs _ = w.WriteField("body", payload.Body) if payload.Title != "" { _ = w.WriteField("title", payload.Title) } if payload.Type != "" { _ = w.WriteField("type", payload.Type) } if payload.Format != "" { _ = w.WriteField("format", payload.Format) } if payload.Tag != "" { _ = w.WriteField("tag", payload.Tag) }
// Fichier f, err := os.Open(filePath) if err != nil { return err } defer f.Close()
part, err := w.CreateFormFile("attach", path.Base(filePath)) if err != nil { return err } if _, err = io.Copy(part, f); err != nil { return err }
if err = w.Close(); err != nil { return err }
req, err := http.NewRequest(http.MethodPost, endpoint, &buf) if err != nil { return err } req.Header.Set("Content-Type", w.FormDataContentType()) req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req) if err != nil { return err } defer res.Body.Close()
if res.StatusCode < 200 || res.StatusCode >= 300 { body, _ := io.ReadAll(res.Body) return fmt.Errorf("apprise api failed: %d %s", res.StatusCode, strings.TrimSpace(string(body))) }
return nil}
func main() { endpoint, err := buildNotifyURL("http", "localhost", "my-alerts", 8000, "") if err != nil { panic(err) }
_ = notifyJSON(endpoint, NotifyPayload{ Title: "Deploy", Body: "Deployed to production", Type: "success", Tag: "devops,admin", })
_ = notifyWithAttachment(endpoint, NotifyPayload{Body: "See attached log"}, "/var/log/syslog")}import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.net.http.HttpRequest.BodyPublishers;
public class AppriseNotify { public static void main(String[] args) { String url = "http://localhost:8000/notify/my-alerts"; String jsonPayload = """ { "title": "Deploy", "body": "Deployed to production", "type": "success", "tag": "devops,admin" } """;
HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Content-Type", "application/json") .header("Accept", "application/json") .POST(BodyPublishers.ofString(jsonPayload)) .build();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body) .thenAccept(System.out::println) .join(); }}using System.Text;using System.Text.Json;
var url = "<http://localhost:8000/notify/my-alerts>";var payload = new{title = "Deploy",body = "Deployed to production",type = "success",tag = "devops,admin"};
using var client = new HttpClient();var json = JsonSerializer.Serialize(payload);var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);response.EnsureSuccessStatusCode();Ci-dessous, un exemple avec pièce jointe :
using var client = new HttpClient();using var form = new MultipartFormDataContent();
form.Add(new StringContent("See attached log"), "body");// Ouvrir le flux du fichierusing var fileStream = File.OpenRead("/var/log/syslog");using var fileContent = new StreamContent(fileStream);form.Add(fileContent, "attach", "syslog.txt");
var response = await client.PostAsync("http://localhost:8000/notify/my-alerts", form);response.EnsureSuccessStatusCode();$Uri = "http://localhost:8000/notify/my-alerts"$Payload = @{ title = "Deploy" body = "Deployed to production" type = "success" tag = "devops,admin"}
Invoke-RestMethod -Uri $Uri -Method Post -Body ($Payload | ConvertTo-Json) -ContentType "application/json"Ci-dessous, un exemple avec pièce jointe :
$Uri = "http://localhost:8000/notify/my-alerts"$Form = @{ body = "See attached log" attach = Get-Item -Path "/var/log/syslog"}
Invoke-RestMethod -Uri $Uri -Method Post -Form $Formrequire 'net/http'require 'json'require 'uri'
uri = URI('<http://localhost:8000/notify/my-alerts>')http = Net::HTTP.new(uri.host, uri.port)request = Net::HTTP::Post.new(uri.path, {'Content-Type' => 'application/json'})
request.body = {title: 'Deploy',body: 'Deployed to production',type: 'success',tag: 'devops,admin'}.to_json
response = http.request(request)puts response.bodyCi-dessous, un exemple avec pièce jointe :
require 'net/http/post/multipart' # gem install multipart-post
url = URI.parse('http://localhost:8000/notify/my-alerts')
File.open('/var/log/syslog') do |file| req = Net::HTTP::Post::Multipart.new(url.path, "body" => "See attached log", "attach" => UploadIO.new(file, "text/plain", "syslog.txt") )
res = Net::HTTP.start(url.host, url.port) do |http| http.request(req) endenduse reqwest::multipart;use reqwest::Client;use serde::Serialize;
#[derive(Serialize)]struct NotifyPayload<'a> { body: &'a str, #[serde(skip_serializing_if = "Option::is_none")] title: Option<&'a str>, #[serde(skip_serializing_if = "Option::is_none")] r#type: Option<&'a str>, #[serde(skip_serializing_if = "Option::is_none")] format: Option<&'a str>, #[serde(skip_serializing_if = "Option::is_none")] tag: Option<&'a str>,}
fn build_notify_url(scheme: &str, host: &str, key: &str, port: Option<u16>, base_path: Option<&str>) -> String { let origin = match port { Some(p) => format!("{}://{}:{}", scheme, host, p), None => format!("{}://{}", scheme, host), };
let prefix = base_path .unwrap_or("") .trim_matches('/') .to_string();
if prefix.is_empty() { format!("{}/notify/{}", origin, urlencoding::encode(key)) } else { format!("{}/{}/notify/{}", origin, prefix, urlencoding::encode(key)) }}
async fn notify_json(endpoint: &str, payload: NotifyPayload<'_>) -> Result<(), reqwest::Error> { let res = Client::new() .post(endpoint) .header("Accept", "application/json") .json(&payload) .send() .await?;
if !res.status().is_success() { let text = res.text().await.unwrap_or_default(); panic!("Apprise API failed: {} {}", res.status(), text); }
Ok(())}
async fn notify_with_attachment(endpoint: &str, body: &str, file_path: &str) -> Result<(), reqwest::Error> { let form = multipart::Form::new() .text("body", body.to_string()) .file("attach", file_path)?;
let res = Client::new() .post(endpoint) .header("Accept", "application/json") .multipart(form) .send() .await?;
if !res.status().is_success() { let text = res.text().await.unwrap_or_default(); panic!("Apprise API failed: {} {}", res.status(), text); }
Ok(())}
#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let endpoint = build_notify_url("http", "localhost", "my-alerts", Some(8000), None);
notify_json( &endpoint, NotifyPayload { title: Some("Deploy"), body: "Deployed to production", r#type: Some("success"), format: None, tag: Some("devops,admin"), }, ) .await?;
notify_with_attachment(&endpoint, "See attached log", "/var/log/syslog").await?;
Ok(())} 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 :