195 lines
8.1 KiB
JavaScript
195 lines
8.1 KiB
JavaScript
//
|
||
// bark.js —— xpcool 定制版(基于 Uptime Kuma 2.5.3 官方实现)
|
||
//
|
||
// 为什么要定制:官方 Bark provider 只支持 endpoint / group / sound 三个参数,
|
||
// 且图标写死成 GitHub 上的 Uptime Kuma 图标。本项目需要:
|
||
// 1. 自定义推送图标(自托管 PNG,手机端直接拉取)
|
||
// 2. 推送消息分组(可选 config.barkGroup 覆盖)
|
||
// 3. 重要警告 level=critical,穿透静音 / 专注模式(仅 DOWN)
|
||
// 4. 图片推送通知 image,按状态区分「异常图 / 恢复图」
|
||
// 5. 设置角标 badge(DOWN 自增、UP 清零)
|
||
// 6. 附带:call=1 持续响铃、isArchive=1 自动保存(仅 DOWN)
|
||
//
|
||
// 部署方式:宿主文件 /data/uptime-kuma-patch/bark.js 只读挂载覆盖本路径。
|
||
// ⚠️ 升级 uptime-kuma 镜像后需重新核对此文件与新版官方实现的差异。
|
||
//
|
||
const NotificationProvider = require("./notification-provider");
|
||
const { DOWN, UP } = require("../../src/util");
|
||
const { default: axios } = require("axios");
|
||
|
||
// ===== xpcool 定制:素材地址与默认值 =====
|
||
const barkNotificationAvatar = "https://status.xpcool.com/bark-assets/icon.png"; // 自定义推送图标
|
||
const IMAGE_DOWN = "https://status.xpcool.com/bark-assets/down.png"; // 服务异常配图
|
||
const IMAGE_UP = "https://status.xpcool.com/bark-assets/up.png"; // 服务恢复配图
|
||
const DEFAULT_GROUP = "Uptime-Kuma"; // 默认推送分组
|
||
|
||
const successMessage = "Successes!";
|
||
|
||
class Bark extends NotificationProvider {
|
||
name = "Bark";
|
||
|
||
/**
|
||
* @inheritdoc
|
||
*/
|
||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||
let barkEndpoint = notification.barkEndpoint;
|
||
|
||
// check if the endpoint has a "/" suffix, if so, delete it first
|
||
if (barkEndpoint.endsWith("/")) {
|
||
barkEndpoint = barkEndpoint.substring(0, barkEndpoint.length - 1);
|
||
}
|
||
|
||
// xpcool 定制:按 UP / DOWN 状态生成附加参数(重要警告、角标、配图等)
|
||
const status = heartbeatJSON != null ? heartbeatJSON["status"] : null;
|
||
const extra = this.statusParams(notification, status);
|
||
|
||
if (msg != null && heartbeatJSON != null && status === UP) {
|
||
let title = "UptimeKuma Monitor Up";
|
||
return await this.postNotification(notification, title, msg, barkEndpoint, extra);
|
||
}
|
||
|
||
if (msg != null && heartbeatJSON != null && status === DOWN) {
|
||
let title = "UptimeKuma Monitor Down";
|
||
return await this.postNotification(notification, title, msg, barkEndpoint, extra);
|
||
}
|
||
|
||
if (msg != null) {
|
||
let title = "UptimeKuma Message";
|
||
return await this.postNotification(notification, title, msg, barkEndpoint, extra);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* xpcool 定制:按告警状态生成 Bark 附加参数
|
||
* 所有项都可用通知 config 里的同名字段覆盖(barkLevel / barkVolume / barkCall /
|
||
* barkBadge / barkImageDown / barkImageUp / barkBadgeReset),无需改代码。
|
||
* @param {BeanModel} notification 通知配置
|
||
* @param {number|null} status 心跳状态(0=DOWN 1=UP)
|
||
* @returns {object} 追加到 Bark 请求上的参数
|
||
*/
|
||
statusParams(notification, status) {
|
||
const p = {};
|
||
|
||
if (status === DOWN) {
|
||
// 重要警告:无视静音与专注模式(手机上需在 Bark App 里允许「重要警告」)
|
||
p.level = notification.barkLevel || "critical";
|
||
p.volume = notification.barkVolume != null ? notification.barkVolume : 5;
|
||
|
||
// 持续响铃约 30 秒:默认关闭(避免半夜长时间吵闹),
|
||
// 想开启就在通知 config 里加 "barkCall": true
|
||
if (notification.barkCall === true) {
|
||
p.call = 1;
|
||
}
|
||
|
||
// 角标:auto = 自动累加未处理告警数,也可填具体数字
|
||
p.badge = notification.barkBadge != null ? notification.barkBadge : "auto";
|
||
|
||
// 自动保存到通知中心,便于事后回看
|
||
p.isArchive = 1;
|
||
|
||
// 异常配图
|
||
p.image = notification.barkImageDown || IMAGE_DOWN;
|
||
} else if (status === UP) {
|
||
// 恢复配图
|
||
p.image = notification.barkImageUp || IMAGE_UP;
|
||
|
||
// 恢复时清掉角标(设 barkBadgeReset=false 可保留)
|
||
if (notification.barkBadgeReset !== false) {
|
||
p.badge = 0;
|
||
}
|
||
}
|
||
|
||
return p;
|
||
}
|
||
|
||
/**
|
||
* Add additional parameter for Bark v1 endpoints.
|
||
* Leads to better on device styles (iOS 15 optimized)
|
||
* @param {BeanModel} notification Notification to be sent
|
||
* @param {object} extra xpcool 定制:状态相关附加参数
|
||
* @returns {string} Additional URL parameters
|
||
*/
|
||
additionalParameters(notification, extra = {}) {
|
||
// xpcool 定制:图标改为自托管 PNG(可被 config.barkIcon 覆盖)
|
||
let params = "?icon=" + encodeURIComponent(notification.barkIcon || barkNotificationAvatar);
|
||
|
||
// 推送消息分组
|
||
params += "&group=" + encodeURIComponent(notification.barkGroup || DEFAULT_GROUP);
|
||
|
||
// picked a sound, this should follow system's mute status when arrival
|
||
params += "&sound=" + encodeURIComponent(notification.barkSound || "telegraph");
|
||
|
||
// xpcool 定制:追加状态相关参数(level / volume / call / badge / image / isArchive)
|
||
for (const key of Object.keys(extra)) {
|
||
const value = extra[key];
|
||
if (value === undefined || value === null || value === "") {
|
||
continue;
|
||
}
|
||
params += "&" + key + "=" + encodeURIComponent(value);
|
||
}
|
||
|
||
return params;
|
||
}
|
||
|
||
/**
|
||
* Check if result is successful
|
||
* @param {object} result Axios response object
|
||
* @returns {void}
|
||
* @throws {Error} The status code is not in range 2xx
|
||
*/
|
||
checkResult(result) {
|
||
if (result.status == null) {
|
||
throw new Error("Bark notification failed with invalid response!");
|
||
}
|
||
if (result.status < 200 || result.status >= 300) {
|
||
throw new Error("Bark notification failed with status code " + result.status);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Send the message
|
||
* @param {BeanModel} notification Notification to be sent
|
||
* @param {string} title Message title
|
||
* @param {string} subtitle Message
|
||
* @param {string} endpoint Endpoint to send request to
|
||
* @param {object} extra xpcool 定制:状态相关附加参数
|
||
* @returns {Promise<string>} Success message
|
||
*/
|
||
async postNotification(notification, title, subtitle, endpoint, extra = {}) {
|
||
let result;
|
||
let config = this.getAxiosConfigWithProxy({});
|
||
|
||
if (notification.apiVersion === "v1" || notification.apiVersion == null) {
|
||
// url encode title and subtitle
|
||
title = encodeURIComponent(title);
|
||
subtitle = encodeURIComponent(subtitle);
|
||
const params = this.additionalParameters(notification, extra);
|
||
result = await axios.get(`${endpoint}/${title}/${subtitle}${params}`, config);
|
||
} else {
|
||
// xpcool 定制:v2(POST) 分支同样带上附加参数
|
||
const body = {
|
||
title,
|
||
body: subtitle,
|
||
icon: notification.barkIcon || barkNotificationAvatar,
|
||
sound: notification.barkSound || "telegraph",
|
||
group: notification.barkGroup || DEFAULT_GROUP,
|
||
};
|
||
for (const key of Object.keys(extra)) {
|
||
if (extra[key] !== undefined && extra[key] !== null) {
|
||
body[key] = extra[key];
|
||
}
|
||
}
|
||
result = await axios.post(endpoint, body, config);
|
||
}
|
||
|
||
this.checkResult(result);
|
||
if (result.statusText != null) {
|
||
return "Bark notification succeed: " + result.statusText;
|
||
}
|
||
// because returned in range 200 ..< 300
|
||
return successMessage;
|
||
}
|
||
}
|
||
|
||
module.exports = Bark;
|