diff --git a/server/notification-providers/notification-provider.js b/server/notification-providers/notification-provider.js index b9fb3d863..42e8e616d 100644 --- a/server/notification-providers/notification-provider.js +++ b/server/notification-providers/notification-provider.js @@ -1,3 +1,6 @@ +const { Liquid } = require("liquidjs"); +const { DOWN } = require("../../src/util"); + class NotificationProvider { /** @@ -49,6 +52,50 @@ class NotificationProvider { } } + /** + * Renders a message template with notification context + * @param {string} template the template + * @param {string} msg the message that will be included in the context + * @param {?object} monitorJSON Monitor details (For Up/Down/Cert-Expiry only) + * @param {?object} heartbeatJSON Heartbeat details (For Up/Down only) + * @returns {Promise} rendered template + */ + async renderTemplate(template, msg, monitorJSON, heartbeatJSON) { + const engine = new Liquid(); + const parsedTpl = engine.parse(template); + + // Let's start with dummy values to simplify code + let monitorName = "Monitor Name not available"; + let monitorHostnameOrURL = "testing.hostname"; + + if (monitorJSON !== null) { + monitorName = monitorJSON["name"]; + monitorHostnameOrURL = this.extractAddress(monitorJSON); + } + + let serviceStatus = "⚠️ Test"; + if (heartbeatJSON !== null) { + serviceStatus = (heartbeatJSON["status"] === DOWN) ? "🔴 Down" : "✅ Up"; + } + + const context = { + // for v1 compatibility, to be removed in v3 + "STATUS": serviceStatus, + "NAME": monitorName, + "HOSTNAME_OR_URL": monitorHostnameOrURL, + + // variables which are officially supported + "status": serviceStatus, + "name": monitorName, + "hostnameOrURL": monitorHostnameOrURL, + monitorJSON, + heartbeatJSON, + msg, + }; + + return engine.render(parsedTpl, context); + } + /** * Throws an error * @param {any} error The error to throw diff --git a/server/notification-providers/smtp.js b/server/notification-providers/smtp.js index 9f3defa5e..980c7dfd3 100644 --- a/server/notification-providers/smtp.js +++ b/server/notification-providers/smtp.js @@ -1,7 +1,5 @@ const nodemailer = require("nodemailer"); const NotificationProvider = require("./notification-provider"); -const { DOWN } = require("../../src/util"); -const { Liquid } = require("liquidjs"); class SMTP extends NotificationProvider { name = "smtp"; @@ -53,15 +51,11 @@ class SMTP extends NotificationProvider { const customSubject = notification.customSubject?.trim() || ""; const customBody = notification.customBody?.trim() || ""; - const context = this.generateContext(msg, monitorJSON, heartbeatJSON); - const engine = new Liquid(); if (customSubject !== "") { - const tpl = engine.parse(customSubject); - subject = await engine.render(tpl, context); + subject = await this.renderTemplate(customSubject, msg, monitorJSON, heartbeatJSON); } if (customBody !== "") { - const tpl = engine.parse(customBody); - body = await engine.render(tpl, context); + body = await this.renderTemplate(customBody, msg, monitorJSON, heartbeatJSON); } } @@ -78,43 +72,6 @@ class SMTP extends NotificationProvider { return okMsg; } - - /** - * Generate context for LiquidJS - * @param {string} msg the message that will be included in the context - * @param {?object} monitorJSON Monitor details (For Up/Down/Cert-Expiry only) - * @param {?object} heartbeatJSON Heartbeat details (For Up/Down only) - * @returns {{STATUS: string, status: string, HOSTNAME_OR_URL: string, hostnameOrUrl: string, NAME: string, name: string, monitorJSON: ?object, heartbeatJSON: ?object, msg: string}} the context - */ - generateContext(msg, monitorJSON, heartbeatJSON) { - // Let's start with dummy values to simplify code - let monitorName = "Monitor Name not available"; - let monitorHostnameOrURL = "testing.hostname"; - - if (monitorJSON !== null) { - monitorName = monitorJSON["name"]; - monitorHostnameOrURL = this.extractAddress(monitorJSON); - } - - let serviceStatus = "⚠️ Test"; - if (heartbeatJSON !== null) { - serviceStatus = (heartbeatJSON["status"] === DOWN) ? "🔴 Down" : "✅ Up"; - } - return { - // for v1 compatibility, to be removed in v3 - "STATUS": serviceStatus, - "NAME": monitorName, - "HOSTNAME_OR_URL": monitorHostnameOrURL, - - // variables which are officially supported - "status": serviceStatus, - "name": monitorName, - "hostnameOrURL": monitorHostnameOrURL, - monitorJSON, - heartbeatJSON, - msg, - }; - } } module.exports = SMTP; diff --git a/server/notification-providers/telegram.js b/server/notification-providers/telegram.js index c5bbb1909..62263db07 100644 --- a/server/notification-providers/telegram.js +++ b/server/notification-providers/telegram.js @@ -22,6 +22,14 @@ class Telegram extends NotificationProvider { params.message_thread_id = notification.telegramMessageThreadID; } + if (notification.telegramUseTemplate) { + params.text = await this.renderTemplate(notification.telegramTemplate, msg, monitorJSON, heartbeatJSON); + + if (notification.telegramTemplateParseMode !== "plain") { + params.parse_mode = notification.telegramTemplateParseMode; + } + } + await axios.get(`${url}/bot${notification.telegramBotToken}/sendMessage`, { params: params, }); diff --git a/server/notification-providers/webhook.js b/server/notification-providers/webhook.js index 986986d44..537f94bd5 100644 --- a/server/notification-providers/webhook.js +++ b/server/notification-providers/webhook.js @@ -1,7 +1,6 @@ const NotificationProvider = require("./notification-provider"); const axios = require("axios"); const FormData = require("form-data"); -const { Liquid } = require("liquidjs"); class Webhook extends NotificationProvider { name = "webhook"; @@ -28,17 +27,7 @@ class Webhook extends NotificationProvider { config.headers = formData.getHeaders(); data = formData; } else if (notification.webhookContentType === "custom") { - // Initialize LiquidJS and parse the custom Body Template - const engine = new Liquid(); - const tpl = engine.parse(notification.webhookCustomBody); - - // Insert templated values into Body - data = await engine.render(tpl, - { - msg, - heartbeatJSON, - monitorJSON - }); + data = await this.renderTemplate(notification.webhookCustomBody, msg, monitorJSON, heartbeatJSON); } if (notification.webhookAdditionalHeaders) { diff --git a/server/notification-providers/yzj.js b/server/notification-providers/yzj.js new file mode 100644 index 000000000..6bd3cba51 --- /dev/null +++ b/server/notification-providers/yzj.js @@ -0,0 +1,57 @@ +const NotificationProvider = require("./notification-provider"); +const { DOWN, UP } = require("../../src/util"); +const { default: axios } = require("axios"); + +class YZJ extends NotificationProvider { + name = "YZJ"; + + /** + * @inheritdoc + */ + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully."; + + try { + if (heartbeatJSON !== null) { + msg = `${this.statusToString(heartbeatJSON["status"])} ${monitorJSON["name"]} \n> ${heartbeatJSON["msg"]}\n> Time (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`; + } + + const config = { + headers: { + "Content-Type": "application/json", + }, + }; + const params = { + content: msg + }; + // yzjtype=0 => general robot + const url = `${notification.yzjWebHookUrl}?yzjtype=0&yzjtoken=${notification.yzjToken}`; + + const result = await axios.post(url, params, config); + if (!result.data?.success) { + throw new Error(result.data?.errmsg ?? "yzj's server did not respond with the expected result"); + } + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error); + } + } + + /** + * Convert status constant to string + * @param {string} status The status constant + * @returns {string} status + */ + statusToString(status) { + switch (status) { + case DOWN: + return "❌"; + case UP: + return "✅"; + default: + return status; + } + } +} + +module.exports = YZJ; diff --git a/server/notification.js b/server/notification.js index ad78d6ce4..a77094ac9 100644 --- a/server/notification.js +++ b/server/notification.js @@ -70,6 +70,7 @@ const Cellsynt = require("./notification-providers/cellsynt"); const Onesender = require("./notification-providers/onesender"); const Wpush = require("./notification-providers/wpush"); const SendGrid = require("./notification-providers/send-grid"); +const YZJ = require("./notification-providers/yzj"); class Notification { @@ -156,7 +157,8 @@ class Notification { new GtxMessaging(), new Cellsynt(), new Wpush(), - new SendGrid() + new SendGrid(), + new YZJ() ]; for (let item of list) { if (! item.name) { diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index b82d1cd39..561637ec0 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -184,6 +184,7 @@ export default { "ServerChan": "ServerChan (Server酱)", "smsc": "SMSC", "WPush": "WPush(wpush.cn)", + "YZJ": "YZJ (云之家自定义机器人)" }; // Sort by notification name diff --git a/src/components/TemplatedInput.vue b/src/components/TemplatedInput.vue new file mode 100644 index 000000000..43c5382e0 --- /dev/null +++ b/src/components/TemplatedInput.vue @@ -0,0 +1,75 @@ + + + diff --git a/src/components/TemplatedTextarea.vue b/src/components/TemplatedTextarea.vue new file mode 100644 index 000000000..ff0c0f9fb --- /dev/null +++ b/src/components/TemplatedTextarea.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/src/components/notifications/SMTP.vue b/src/components/notifications/SMTP.vue index 003f90556..4e0fb4b57 100644 --- a/src/components/notifications/SMTP.vue +++ b/src/components/notifications/SMTP.vue @@ -67,25 +67,15 @@ -

- - {{ $t("documentation") }} - - {{name}}: {{ $t("emailTemplateServiceName") }}
- {{msg}}: {{ $t("emailTemplateMsg") }}
- {{status}}: {{ $t("emailTemplateStatus") }}
- {{heartbeatJSON}}: {{ $t("emailTemplateHeartbeatJSON") }}{{ $t("emailTemplateLimitedToUpDownNotification") }}
- {{monitorJSON}}: {{ $t("emailTemplateMonitorJSON") }} {{ $t("emailTemplateLimitedToUpDownNotification") }}
- {{hostnameOrURL}}: {{ $t("emailTemplateHostnameOrURL") }}
-

- +
{{ $t("leave blank for default subject") }}
+
- +
{{ $t("leave blank for default body") }}
@@ -124,11 +114,15 @@ + + diff --git a/src/components/notifications/Webhook.vue b/src/components/notifications/Webhook.vue index 8c67a2745..7775a3fdd 100644 --- a/src/components/notifications/Webhook.vue +++ b/src/components/notifications/Webhook.vue @@ -32,20 +32,7 @@ @@ -67,7 +54,12 @@ diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js index d353bca6c..c1162da92 100644 --- a/src/components/notifications/index.js +++ b/src/components/notifications/index.js @@ -68,6 +68,7 @@ import Cellsynt from "./Cellsynt.vue"; import WPush from "./WPush.vue"; import SIGNL4 from "./SIGNL4.vue"; import SendGrid from "./SendGrid.vue"; +import YZJ from "./YZJ.vue"; /** * Manage all notification form. @@ -144,6 +145,7 @@ const NotificationFormList = { "Cellsynt": Cellsynt, "WPush": WPush, "SendGrid": SendGrid, + "YZJ": YZJ, }; export default NotificationFormList; diff --git a/src/lang/en.json b/src/lang/en.json index b672b1a47..c0bf639c8 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -231,6 +231,9 @@ "templateMonitorJSON": "object describing the monitor", "templateLimitedToUpDownCertNotifications": "only available for UP/DOWN/Certificate expiry notifications", "templateLimitedToUpDownNotifications": "only available for UP/DOWN notifications", + "templateServiceName": "service name", + "templateHostnameOrURL": "hostname or URL", + "templateStatus": "status", "webhookAdditionalHeadersTitle": "Additional Headers", "webhookAdditionalHeadersDesc": "Sets additional headers sent with the webhook. Each header should be defined as a JSON key/value.", "webhookBodyPresetOption": "Preset - {0}", @@ -421,6 +424,9 @@ "telegramSendSilentlyDescription": "Sends the message silently. Users will receive a notification with no sound.", "telegramProtectContent": "Protect Forwarding/Saving", "telegramProtectContentDescription": "If enabled, the bot messages in Telegram will be protected from forwarding and saving.", + "telegramUseTemplate": "Use custom message template", + "telegramUseTemplateDescription": "If enabled, the message will be sent using a custom template.", + "telegramTemplateFormatDescription": "Telegram allows using different markup languages for messages, see Telegram {0} for specifc details.", "supportTelegramChatID": "Support Direct Chat / Group / Channel's Chat ID", "wayToGetTelegramChatID": "You can get your chat ID by sending a message to the bot and going to this URL to view the chat_id:", "YOUR BOT TOKEN HERE": "YOUR BOT TOKEN HERE", @@ -519,9 +525,6 @@ "leave blank for default subject": "leave blank for default subject", "emailCustomBody": "Custom Body", "leave blank for default body": "leave blank for default body", - "emailTemplateServiceName": "Service Name", - "emailTemplateHostnameOrURL": "Hostname or URL", - "emailTemplateStatus": "Status", "emailTemplateMonitorJSON": "object describing the monitor", "emailTemplateHeartbeatJSON": "object describing the heartbeat", "emailTemplateMsg": "message of the notification", @@ -1057,5 +1060,10 @@ "wayToGetWahaApiUrl": "Your WAHA Instance URL.", "wayToGetWahaApiKey": "API Key is WHATSAPP_API_KEY environment variable value you used to run WAHA.", "wayToGetWahaSession": "From this session WAHA sends notifications to Chat ID. You can find it in WAHA Dashboard.", - "wayToWriteWahaChatId": "The phone number with the international prefix, but without the plus sign at the start ({0}), the Contact ID ({1}) or the Group ID ({2}). Notifications are sent to this Chat ID from WAHA Session." + "wayToWriteWahaChatId": "The phone number with the international prefix, but without the plus sign at the start ({0}), the Contact ID ({1}) or the Group ID ({2}). Notifications are sent to this Chat ID from WAHA Session.", + "YZJ Webhook URL": "YZJ Webhook URL", + "YZJ Robot Token": "YZJ Robot token", + "Plain Text": "Plain Text", + "Message Template": "Message Template", + "Template Format": "Template Format" }