feat(notifications): add Bark push support

Add Bark as a supported Apprise notification protocol.

Supported URL formats:
- bark://bark_key uses the official Bark server at https://api.day.app/
- bark://host/bark_key uses a custom Bark server over HTTP
- barks://host/bark_key uses a custom Bark server over HTTPS

Bark notifications are sent with POST JSON payloads containing the device key, title, and body. The notification settings modal now
lists Bark examples in the Apprise URL placeholder and support text.
This commit is contained in:
undirectlookable
2026-05-25 16:42:25 +08:00
committed by Jarek Krochmalski
parent 3cbcfa3cdb
commit b7a8cca387
2 changed files with 67 additions and 1 deletions
+63
View File
@@ -129,6 +129,9 @@ async function sendToAppriseUrl(url: string, payload: NotificationPayload): Prom
case 'ntfy': case 'ntfy':
case 'ntfys': case 'ntfys':
return await sendNtfy(url, payload); return await sendNtfy(url, payload);
case 'bark':
case 'barks':
return await sendBark(url, payload);
case 'pushover': case 'pushover':
return await sendPushover(url, payload); return await sendPushover(url, payload);
case 'json': case 'json':
@@ -435,6 +438,66 @@ async function sendNtfy(appriseUrl: string, payload: NotificationPayload): Promi
} }
} }
// Bark
async function sendBark(appriseUrl: string, payload: NotificationPayload): Promise<NotificationResult> {
// Supported formats:
// bark://device_key (official api.day.app server)
// bark://host/device_key (custom server over HTTP)
// barks://host/device_key (custom server over HTTPS)
const isSecure = appriseUrl.startsWith('barks');
const path = appriseUrl.replace(/^barks?:\/\//, '');
let url: string;
let deviceKey: string;
if (!path.includes('/')) {
if (!path) {
return { success: false, error: 'Invalid Bark URL format. Expected: bark://device_key, bark://host/device_key, or barks://host/device_key' };
}
url = 'https://api.day.app/push';
deviceKey = path;
} else {
const parts = path.split('/');
if (parts.length !== 2) {
return { success: false, error: 'Invalid Bark URL format. Expected: bark://device_key, bark://host/device_key, or barks://host/device_key' };
}
const [host, key] = parts;
deviceKey = key;
if (!host || !deviceKey) {
return { success: false, error: 'Invalid Bark URL format. Expected: bark://device_key, bark://host/device_key, or barks://host/device_key' };
}
url = `${isSecure ? 'https' : 'http'}://${host}/push`;
}
const titleWithEnv = payload.environmentName ? `${payload.title} [${payload.environmentName}]` : payload.title;
const body: Record<string, string> = {
device_key: deviceKey,
title: titleWithEnv,
body: payload.message
};
if (payload.type === 'error') {
body.level = 'timeSensitive';
}
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (!response.ok) {
const text = await response.text().catch(() => '');
return { success: false, error: `Bark error ${response.status}: ${text || response.statusText}` };
}
await drainResponse(response);
return { success: true };
} catch (error) {
return { success: false, error: `Bark connection failed: ${error instanceof Error ? error.message : String(error)}` };
}
}
// Pushover // Pushover
async function sendPushover(appriseUrl: string, payload: NotificationPayload): Promise<NotificationResult> { async function sendPushover(appriseUrl: string, payload: NotificationPayload): Promise<NotificationResult> {
// pushover://user_key/api_token // pushover://user_key/api_token
@@ -427,11 +427,14 @@ ntfy://host/topic?auth=base64token&priority=3
ntfys://host/topic?auth=base64token ntfys://host/topic?auth=base64token
pushover://user_key/api_token pushover://user_key/api_token
workflows://hostname/workflow/signature workflows://hostname/workflow/signature
bark://bark_key
bark://host/bark_key
barks://host/bark_key
jsons://hostname/webhook/path" jsons://hostname/webhook/path"
class="flex min-h-[220px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" class="flex min-h-[220px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
></textarea> ></textarea>
<p class="text-xs text-muted-foreground"> <p class="text-xs text-muted-foreground">
Supports Gotify (gotify:// or gotifys:// for HTTPS), Discord, Slack, Mattermost (mmost:// or mmosts://), Telegram, ntfy, Pushover, Workflows (for e.g. Microsoft Teams), and generic JSON webhooks. Supports Gotify (gotify:// or gotifys:// for HTTPS), Discord, Slack, Mattermost (mmost:// or mmosts://), Telegram, ntfy, Bark, Pushover, Workflows (for e.g. Microsoft Teams), and generic JSON webhooks.
</p> </p>
</div> </div>
</div> </div>