uptime-kuma/server/notification.js

715 lines
28 KiB
JavaScript
Raw Normal View History

2021-07-08 23:14:03 -07:00
const axios = require("axios");
2021-07-27 10:47:13 -07:00
const { R } = require("redbean-node");
const FormData = require("form-data");
2021-07-09 10:08:08 -07:00
const nodemailer = require("nodemailer");
2021-07-18 03:51:58 -07:00
const child_process = require("child_process");
2021-07-08 23:14:03 -07:00
const { UP, DOWN } = require("../src/util");
2021-07-08 23:14:03 -07:00
class Notification {
2021-07-18 03:51:58 -07:00
2021-07-18 05:49:46 -07:00
/**
*
* @param notification
* @param msg
* @param monitorJSON
* @param heartbeatJSON
* @returns {Promise<string>} Successful msg
* Throw Error with fail msg
*/
2021-09-04 05:08:18 -07:00
static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
2021-07-18 05:49:46 -07:00
let okMsg = "Sent Successfully. ";
2021-07-18 03:51:58 -07:00
2021-07-08 23:14:03 -07:00
if (notification.type === "telegram") {
2021-07-09 04:33:22 -07:00
try {
await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, {
params: {
chat_id: notification.telegramChatID,
text: msg,
2021-07-27 10:47:13 -07:00
},
2021-07-09 04:33:22 -07:00
})
2021-07-18 05:49:46 -07:00
return okMsg;
2021-07-14 02:25:10 -07:00
} catch (error) {
2021-07-18 05:49:46 -07:00
let msg = (error.response.data.description) ? error.response.data.description : "Error without description"
throw new Error(msg)
2021-07-14 02:25:10 -07:00
}
} else if (notification.type === "gotify") {
try {
2021-07-18 05:49:46 -07:00
if (notification.gotifyserverurl && notification.gotifyserverurl.endsWith("/")) {
2021-07-14 02:25:10 -07:00
notification.gotifyserverurl = notification.gotifyserverurl.slice(0, -1);
}
await axios.post(`${notification.gotifyserverurl}/message?token=${notification.gotifyapplicationToken}`, {
"message": msg,
"priority": notification.gotifyPriority || 8,
2021-07-27 10:47:13 -07:00
"title": "Uptime-Kuma",
2021-07-14 02:25:10 -07:00
})
2021-07-18 05:49:46 -07:00
return okMsg;
2021-07-09 04:33:22 -07:00
} catch (error) {
2021-07-18 05:49:46 -07:00
throwGeneralAxiosError(error)
2021-07-09 04:33:22 -07:00
}
} else if (notification.type === "webhook") {
try {
let data = {
heartbeat: heartbeatJSON,
monitor: monitorJSON,
msg,
};
let finalData;
let config = {};
if (notification.webhookContentType === "form-data") {
finalData = new FormData();
2021-07-27 10:47:13 -07:00
finalData.append("data", JSON.stringify(data));
2021-07-09 04:33:22 -07:00
config = {
2021-07-27 10:47:13 -07:00
headers: finalData.getHeaders(),
2021-07-09 04:33:22 -07:00
}
} else {
finalData = data;
2021-07-08 23:14:03 -07:00
}
2021-07-09 04:33:22 -07:00
2021-07-21 20:15:53 -07:00
await axios.post(notification.webhookURL, finalData, config)
2021-07-18 05:49:46 -07:00
return okMsg;
2021-07-09 04:33:22 -07:00
} catch (error) {
2021-07-18 05:49:46 -07:00
throwGeneralAxiosError(error)
2021-07-09 04:33:22 -07:00
}
2021-07-09 10:08:08 -07:00
} else if (notification.type === "smtp") {
2021-09-03 20:27:18 -07:00
return await Notification.smtp(notification, msg, heartbeatJSON)
2021-07-09 20:38:00 -07:00
} else if (notification.type === "discord") {
try {
const discordDisplayName = notification.discordUsername || "Uptime Kuma";
2021-07-27 10:47:13 -07:00
// If heartbeatJSON is null, assume we're testing.
if (heartbeatJSON == null) {
2021-08-05 09:11:11 -07:00
let discordtestdata = {
username: discordDisplayName,
2021-07-27 10:47:13 -07:00
content: msg,
}
2021-08-05 09:11:11 -07:00
await axios.post(notification.discordWebhookUrl, discordtestdata)
2021-07-27 10:47:13 -07:00
return okMsg;
}
let url;
if (monitorJSON["type"] === "port") {
url = monitorJSON["hostname"];
if (monitorJSON["port"]) {
url += ":" + monitorJSON["port"];
}
} else {
url = monitorJSON["url"];
}
2021-07-27 10:47:13 -07:00
// If heartbeatJSON is not null, we go into the normal alerting loop.
if (heartbeatJSON["status"] == DOWN) {
2021-08-05 09:11:11 -07:00
let discorddowndata = {
username: discordDisplayName,
2021-08-07 11:18:33 -07:00
embeds: [{
title: "❌ Your service " + monitorJSON["name"] + " went down. ❌",
2021-08-07 11:18:33 -07:00
color: 16711680,
timestamp: heartbeatJSON["time"],
fields: [
{
name: "Service Name",
value: monitorJSON["name"],
},
{
name: "Service URL",
value: url,
2021-08-07 11:18:33 -07:00
},
{
name: "Time (UTC)",
value: heartbeatJSON["time"],
},
{
name: "Error",
value: heartbeatJSON["msg"],
},
],
}],
}
await axios.post(notification.discordWebhookUrl, discorddowndata)
return okMsg;
} else if (heartbeatJSON["status"] == UP) {
2021-08-05 09:11:11 -07:00
let discordupdata = {
username: discordDisplayName,
2021-08-07 11:18:33 -07:00
embeds: [{
title: "✅ Your service " + monitorJSON["name"] + " is up! ✅",
color: 65280,
timestamp: heartbeatJSON["time"],
fields: [
{
name: "Service Name",
value: monitorJSON["name"],
},
{
name: "Service URL",
value: url.startsWith("http") ? "[Visit Service](" + url + ")" : url,
2021-08-07 11:18:33 -07:00
},
{
name: "Time (UTC)",
value: heartbeatJSON["time"],
},
{
name: "Ping",
value: heartbeatJSON["ping"] + "ms",
},
],
}],
}
await axios.post(notification.discordWebhookUrl, discordupdata)
return okMsg;
2021-08-05 09:11:11 -07:00
}
2021-07-27 10:47:13 -07:00
} catch (error) {
throwGeneralAxiosError(error)
}
2021-07-09 20:38:00 -07:00
2021-07-12 13:06:03 -07:00
} else if (notification.type === "signal") {
2021-07-27 10:47:13 -07:00
try {
let data = {
"message": msg,
"number": notification.signalNumber,
"recipients": notification.signalRecipients.replace(/\s/g, "").split(","),
};
let config = {};
await axios.post(notification.signalURL, data, config)
return okMsg;
} catch (error) {
throwGeneralAxiosError(error)
}
} else if (notification.type === "pushy") {
try {
await axios.post(`https://api.pushy.me/push?api_key=${notification.pushyAPIKey}`, {
"to": notification.pushyToken,
"data": {
"message": "Uptime-Kuma"
},
"notification": {
"body": msg,
"badge": 1,
"sound": "ping.aiff"
}
})
return true;
} catch (error) {
console.log(error)
return false;
}
2021-08-11 15:15:53 -07:00
} else if (notification.type === "octopush") {
try {
let config = {
headers: {
"api-key": notification.octopushAPIKey,
"api-login": notification.octopushLogin,
"cache-control": "no-cache"
2021-08-11 15:15:53 -07:00
}
};
let data = {
"recipients": [
{
"phone_number": notification.octopushPhoneNumber
}
],
//octopush not supporting non ascii char
"text": msg.replace(/[^\x00-\x7F]/g, ""),
2021-08-11 15:15:53 -07:00
"type": notification.octopushSMSType,
"purpose": "alert",
"sender": notification.octopushSenderName
};
await axios.post("https://api.octopush.com/v1/public/sms-campaign/send", data, config)
2021-08-11 15:15:53 -07:00
return true;
} catch (error) {
console.log(error)
return false;
}
2021-07-14 08:37:14 -07:00
} else if (notification.type === "slack") {
try {
if (heartbeatJSON == null) {
2021-07-27 10:47:13 -07:00
let data = {
"text": "Uptime Kuma Slack testing successful.",
"channel": notification.slackchannel,
"username": notification.slackusername,
"icon_emoji": notification.slackiconemo,
}
2021-07-21 20:15:53 -07:00
await axios.post(notification.slackwebhookURL, data)
2021-07-18 05:49:46 -07:00
return okMsg;
2021-07-14 08:37:14 -07:00
}
const time = heartbeatJSON["time"];
let data = {
2021-07-17 00:17:52 -07:00
"text": "Uptime Kuma Alert",
2021-07-27 10:47:13 -07:00
"channel": notification.slackchannel,
2021-07-17 00:17:52 -07:00
"username": notification.slackusername,
"icon_emoji": notification.slackiconemo,
2021-07-14 08:37:14 -07:00
"blocks": [{
2021-07-27 10:47:13 -07:00
"type": "header",
"text": {
"type": "plain_text",
"text": "Uptime Kuma Alert",
2021-07-14 08:37:14 -07:00
},
2021-07-27 10:47:13 -07:00
},
{
"type": "section",
"fields": [{
"type": "mrkdwn",
"text": "*Message*\n" + msg,
2021-07-14 08:37:14 -07:00
},
{
2021-07-27 10:47:13 -07:00
"type": "mrkdwn",
"text": "*Time (UTC)*\n" + time,
}],
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Visit Uptime Kuma",
},
"value": "Uptime-Kuma",
"url": notification.slackbutton || "https://github.com/louislam/uptime-kuma",
},
],
}],
}
2021-07-21 20:15:53 -07:00
await axios.post(notification.slackwebhookURL, data)
2021-07-18 05:49:46 -07:00
return okMsg;
2021-07-14 08:37:14 -07:00
} catch (error) {
2021-07-18 05:49:46 -07:00
throwGeneralAxiosError(error)
2021-07-17 05:25:02 -07:00
}
2021-08-25 12:01:29 -07:00
} else if (notification.type === "rocket.chat") {
try {
if (heartbeatJSON == null) {
let data = {
"text": "Uptime Kuma Rocket.chat testing successful.",
"channel": notification.rocketchannel,
"username": notification.rocketusername,
"icon_emoji": notification.rocketiconemo,
}
await axios.post(notification.rocketwebhookURL, data)
return okMsg;
}
const time = heartbeatJSON["time"];
let data = {
"text": "Uptime Kuma Alert",
"channel": notification.rocketchannel,
"username": notification.rocketusername,
"icon_emoji": notification.rocketiconemo,
"attachments": [
{
"title": "Uptime Kuma Alert *Time (UTC)*\n" + time,
"title_link": notification.rocketbutton,
"text": "*Message*\n" + msg,
"color": "#32cd32"
}
]
}
await axios.post(notification.rocketwebhookURL, data)
return okMsg;
} catch (error) {
throwGeneralAxiosError(error)
}
2021-08-24 11:19:21 -07:00
} else if (notification.type === "mattermost") {
try {
const mattermostUserName = notification.mattermostusername || "Uptime Kuma";
// If heartbeatJSON is null, assume we're testing.
if (heartbeatJSON == null) {
let mattermostTestData = {
username: mattermostUserName,
text: msg,
}
await axios.post(notification.mattermostWebhookUrl, mattermostTestData)
return okMsg;
}
2021-08-24 11:19:21 -07:00
const mattermostChannel = notification.mattermostchannel;
const mattermostIconEmoji = notification.mattermosticonemo;
const mattermostIconUrl = notification.mattermosticonurl;
if (heartbeatJSON["status"] == DOWN) {
2021-08-24 11:19:21 -07:00
let mattermostdowndata = {
username: mattermostUserName,
text: "Uptime Kuma Alert",
channel: mattermostChannel,
icon_emoji: mattermostIconEmoji,
icon_url: mattermostIconUrl,
attachments: [
{
fallback:
"Your " +
monitorJSON["name"] +
" service went down.",
color: "#FF0000",
title:
"❌ " +
monitorJSON["name"] +
" service went down. ❌",
title_link: monitorJSON["url"],
fields: [
{
short: true,
title: "Service Name",
value: monitorJSON["name"],
},
{
short: true,
title: "Time (UTC)",
value: heartbeatJSON["time"],
},
{
short: false,
title: "Error",
value: heartbeatJSON["msg"],
},
],
},
],
};
await axios.post(
notification.mattermostWebhookUrl,
mattermostdowndata
);
return okMsg;
} else if (heartbeatJSON["status"] == UP) {
2021-08-24 11:19:21 -07:00
let mattermostupdata = {
username: mattermostUserName,
text: "Uptime Kuma Alert",
channel: mattermostChannel,
icon_emoji: mattermostIconEmoji,
icon_url: mattermostIconUrl,
attachments: [
{
fallback:
"Your " +
monitorJSON["name"] +
" service went up!",
color: "#32CD32",
title:
"✅ " +
monitorJSON["name"] +
" service went up! ✅",
title_link: monitorJSON["url"],
fields: [
{
short: true,
title: "Service Name",
value: monitorJSON["name"],
},
{
short: true,
title: "Time (UTC)",
value: heartbeatJSON["time"],
},
{
short: false,
title: "Ping",
value: heartbeatJSON["ping"] + "ms",
},
],
},
],
};
await axios.post(
notification.mattermostWebhookUrl,
mattermostupdata
);
return okMsg;
}
} catch (error) {
throwGeneralAxiosError(error);
}
2021-07-17 05:25:02 -07:00
} else if (notification.type === "pushover") {
2021-07-27 10:47:13 -07:00
let pushoverlink = "https://api.pushover.net/1/messages.json"
2021-07-17 05:25:02 -07:00
try {
if (heartbeatJSON == null) {
2021-07-27 10:47:13 -07:00
let data = {
"message": "<b>Uptime Kuma Pushover testing successful.</b>",
"user": notification.pushoveruserkey,
"token": notification.pushoverapptoken,
"sound": notification.pushoversounds,
"priority": notification.pushoverpriority,
"title": notification.pushovertitle,
"retry": "30",
"expire": "3600",
"html": 1,
}
2021-07-21 20:15:53 -07:00
await axios.post(pushoverlink, data)
2021-07-18 05:49:46 -07:00
return okMsg;
2021-07-17 05:25:02 -07:00
}
let data = {
2021-07-27 10:47:13 -07:00
"message": "<b>Uptime Kuma Alert</b>\n\n<b>Message</b>:" + msg + "\n<b>Time (UTC)</b>:" + heartbeatJSON["time"],
"user": notification.pushoveruserkey,
2021-07-17 05:25:02 -07:00
"token": notification.pushoverapptoken,
"sound": notification.pushoversounds,
"priority": notification.pushoverpriority,
"title": notification.pushovertitle,
"retry": "30",
"expire": "3600",
2021-07-27 10:47:13 -07:00
"html": 1,
}
2021-07-21 20:15:53 -07:00
await axios.post(pushoverlink, data)
2021-07-18 05:49:46 -07:00
return okMsg;
2021-07-14 08:37:14 -07:00
} catch (error) {
2021-07-18 05:49:46 -07:00
throwGeneralAxiosError(error)
2021-07-14 08:37:14 -07:00
}
2021-07-12 13:06:03 -07:00
2021-07-18 03:51:58 -07:00
} else if (notification.type === "apprise") {
return Notification.apprise(notification, msg)
2021-07-30 22:03:20 -07:00
} else if (notification.type === "lunasea") {
2021-07-30 22:35:18 -07:00
let lunaseadevice = "https://notify.lunasea.app/v1/custom/device/" + notification.lunaseaDevice
2021-07-30 22:03:20 -07:00
try {
if (heartbeatJSON == null) {
let testdata = {
"title": "Uptime Kuma Alert",
2021-07-30 22:35:18 -07:00
"body": "Testing Successful.",
2021-07-30 22:03:20 -07:00
}
await axios.post(lunaseadevice, testdata)
return okMsg;
}
if (heartbeatJSON["status"] == DOWN) {
2021-07-30 22:03:20 -07:00
let downdata = {
"title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
2021-07-30 22:03:20 -07:00
}
await axios.post(lunaseadevice, downdata)
return okMsg;
}
if (heartbeatJSON["status"] == UP) {
2021-07-30 22:03:20 -07:00
let updata = {
"title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
2021-07-30 22:03:20 -07:00
}
await axios.post(lunaseadevice, updata)
return okMsg;
}
2021-07-30 22:35:18 -07:00
} catch (error) {
2021-07-30 22:03:20 -07:00
throwGeneralAxiosError(error)
}
2021-08-13 13:18:43 -07:00
} else if (notification.type === "pushbullet") {
try {
let pushbulletUrl = "https://api.pushbullet.com/v2/pushes";
2021-08-13 13:18:43 -07:00
let config = {
headers: {
"Access-Token": notification.pushbulletAccessToken,
"Content-Type": "application/json"
2021-08-13 13:18:43 -07:00
}
};
if (heartbeatJSON == null) {
let testdata = {
"type": "note",
2021-08-13 13:18:43 -07:00
"title": "Uptime Kuma Alert",
"body": "Testing Successful.",
}
await axios.post(pushbulletUrl, testdata, config)
} else if (heartbeatJSON["status"] == DOWN) {
2021-08-13 13:18:43 -07:00
let downdata = {
"type": "note",
"title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
2021-08-13 13:18:43 -07:00
}
await axios.post(pushbulletUrl, downdata, config)
} else if (heartbeatJSON["status"] == UP) {
2021-08-13 13:18:43 -07:00
let updata = {
"type": "note",
"title": "UptimeKuma Alert: " + monitorJSON["name"],
"body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"],
2021-08-13 13:18:43 -07:00
}
await axios.post(pushbulletUrl, updata, config)
}
return okMsg;
} catch (error) {
throwGeneralAxiosError(error)
}
} else if (notification.type === "line") {
try {
let lineAPIUrl = "https://api.line.me/v2/bot/message/push";
let config = {
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + notification.lineChannelAccessToken
}
};
if (heartbeatJSON == null) {
let testMessage = {
"to": notification.lineUserID,
"messages": [
{
"type": "text",
2021-08-24 10:21:06 -07:00
"text": "Test Successful!"
}
]
}
await axios.post(lineAPIUrl, testMessage, config)
} else if (heartbeatJSON["status"] == DOWN) {
let downMessage = {
"to": notification.lineUserID,
"messages": [
{
"type": "text",
2021-08-24 10:21:06 -07:00
"text": "UptimeKuma Alert: [🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
}
]
}
await axios.post(lineAPIUrl, downMessage, config)
} else if (heartbeatJSON["status"] == UP) {
let upMessage = {
"to": notification.lineUserID,
"messages": [
{
"type": "text",
2021-08-24 10:21:06 -07:00
"text": "UptimeKuma Alert: [✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"]
}
]
}
await axios.post(lineAPIUrl, upMessage, config)
}
return okMsg;
} catch (error) {
throwGeneralAxiosError(error)
}
2021-07-08 23:14:03 -07:00
} else {
throw new Error("Notification type is not supported")
}
}
static async save(notification, notificationID, userID) {
let bean
if (notificationID) {
bean = await R.findOne("notification", " id = ? AND user_id = ? ", [
notificationID,
userID,
])
if (! bean) {
throw new Error("notification not found")
}
} else {
bean = R.dispense("notification")
}
bean.name = notification.name;
bean.user_id = userID;
bean.config = JSON.stringify(notification)
await R.store(bean)
}
static async delete(notificationID, userID) {
let bean = await R.findOne("notification", " id = ? AND user_id = ? ", [
notificationID,
userID,
])
if (! bean) {
throw new Error("notification not found")
}
await R.trash(bean)
}
2021-07-09 10:08:08 -07:00
2021-09-03 20:27:18 -07:00
static async smtp(notification, msg, heartbeatJSON = null) {
2021-07-09 10:08:08 -07:00
const config = {
2021-07-09 10:08:08 -07:00
host: notification.smtpHost,
port: notification.smtpPort,
secure: notification.smtpSecure,
};
// Should fix the issue in https://github.com/louislam/uptime-kuma/issues/26#issuecomment-896373904
if (notification.smtpUsername || notification.smtpPassword) {
config.auth = {
2021-07-09 10:08:08 -07:00
user: notification.smtpUsername,
pass: notification.smtpPassword,
};
}
let transporter = nodemailer.createTransport(config);
2021-07-09 10:08:08 -07:00
2021-09-03 20:27:18 -07:00
let bodyTextContent = msg;
if(heartbeatJSON) {
bodyTextContent = `${msg}\nTime (UTC): ${heartbeatJSON["time"]}`;
}
2021-07-09 10:08:08 -07:00
// send mail with defined transport object
await transporter.sendMail({
2021-07-09 10:08:08 -07:00
from: `"Uptime Kuma" <${notification.smtpFrom}>`,
to: notification.smtpTo,
subject: msg,
2021-09-03 20:27:18 -07:00
text: bodyTextContent,
2021-07-09 10:08:08 -07:00
});
2021-07-18 05:49:46 -07:00
return "Sent Successfully.";
2021-07-09 10:08:08 -07:00
}
2021-07-09 20:38:00 -07:00
2021-07-18 03:51:58 -07:00
static async apprise(notification, msg) {
let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL])
2021-07-09 20:38:00 -07:00
2021-07-27 10:47:13 -07:00
let output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found";
2021-07-09 20:38:00 -07:00
2021-07-18 03:51:58 -07:00
if (output) {
2021-07-18 05:49:46 -07:00
if (! output.includes("ERROR")) {
return "Sent Successfully";
2021-07-18 03:51:58 -07:00
}
2021-07-27 10:47:13 -07:00
2021-07-27 11:02:20 -07:00
throw new Error(output)
2021-07-18 03:51:58 -07:00
} else {
2021-07-18 05:49:46 -07:00
return ""
2021-07-18 03:51:58 -07:00
}
2021-07-09 10:08:08 -07:00
}
2021-07-09 20:38:00 -07:00
2021-07-18 03:51:58 -07:00
static checkApprise() {
2021-07-27 10:47:13 -07:00
let commandExistsSync = require("command-exists").sync;
let exists = commandExistsSync("apprise");
2021-07-18 03:51:58 -07:00
return exists;
2021-07-09 20:38:00 -07:00
}
2021-07-18 05:49:46 -07:00
}
2021-07-09 20:38:00 -07:00
2021-07-18 05:49:46 -07:00
function throwGeneralAxiosError(error) {
let msg = "Error: " + error + " ";
2021-07-09 20:38:00 -07:00
2021-07-18 05:49:46 -07:00
if (error.response && error.response.data) {
if (typeof error.response.data === "string") {
msg += error.response.data;
} else {
msg += JSON.stringify(error.response.data)
}
2021-07-09 20:38:00 -07:00
}
2021-07-18 05:49:46 -07:00
throw new Error(msg)
2021-07-08 23:14:03 -07:00
}
module.exports = {
Notification,
}