add apprise support

This commit is contained in:
LouisLam 2021-07-18 18:51:58 +08:00
parent 13c9244e3f
commit 66037e236c
6 changed files with 133 additions and 74 deletions

View file

@ -20,11 +20,10 @@ RUN apk add --no-cache --virtual .build-deps make g++ python3 python3-dev && \
# Runtime Error => ModuleNotFoundError: No module named 'six' => pip3 install six # Runtime Error => ModuleNotFoundError: No module named 'six' => pip3 install six
# Runtime Error 2 => ModuleNotFoundError: No module named 'six' => apk add py3-six # Runtime Error 2 => ModuleNotFoundError: No module named 'six' => apk add py3-six
ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1 ENV CRYPTOGRAPHY_DONT_BUILD_RUST=1
RUN apk add --no-cache python3 RUN apk add --no-cache python3 py3-pip py3-six cargo
RUN apk add --no-cache --virtual .build-deps libffi-dev musl-dev openssl-dev cargo py3-pip python3-dev && \ RUN apk add --no-cache --virtual .build-deps libffi-dev musl-dev openssl-dev python3-dev && \
pip3 install apprise && \ pip3 install apprise && \
apk del .build-deps apk del .build-deps
RUN apk add --no-cache py3-six
RUN apprise --version RUN apprise --version
# New things add here # New things add here

7
package-lock.json generated
View file

@ -1,6 +1,6 @@
{ {
"name": "uptime-kuma", "name": "uptime-kuma",
"version": "1.0.4", "version": "1.0.5",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -655,6 +655,11 @@
"delayed-stream": "~1.0.0" "delayed-stream": "~1.0.0"
} }
}, },
"command-exists": {
"version": "1.2.9",
"resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz",
"integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w=="
},
"commander": { "commander": {
"version": "6.2.1", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",

View file

@ -19,6 +19,7 @@
"axios": "^0.21.1", "axios": "^0.21.1",
"bcrypt": "^5.0.1", "bcrypt": "^5.0.1",
"bootstrap": "^5.0.0", "bootstrap": "^5.0.0",
"command-exists": "^1.2.9",
"dayjs": "^1.10.4", "dayjs": "^1.10.4",
"express": "^4.17.1", "express": "^4.17.1",
"form-data": "^4.0.0", "form-data": "^4.0.0",

View file

@ -2,9 +2,16 @@ const axios = require("axios");
const {R} = require("redbean-node"); const {R} = require("redbean-node");
const FormData = require('form-data'); const FormData = require('form-data');
const nodemailer = require("nodemailer"); const nodemailer = require("nodemailer");
const child_process = require("child_process");
class Notification { class Notification {
static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let res = {
ok: true,
msg: "Sent Successfully"
}
if (notification.type === "telegram") { if (notification.type === "telegram") {
try { try {
await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, { await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, {
@ -185,7 +192,7 @@ class Notification {
var pushoverlink = 'https://api.pushover.net/1/messages.json' var pushoverlink = 'https://api.pushover.net/1/messages.json'
try { try {
if (heartbeatJSON == null) { if (heartbeatJSON == null) {
let data = {'message': "<b>Uptime Kuma Pushover testing successful.</b>", let data = {'message': "<b>Uptime Kuma Pushover testing successful.</b>",
'user': notification.pushoveruserkey, 'token': notification.pushoverapptoken, 'sound':notification.pushoversounds, 'user': notification.pushoveruserkey, 'token': notification.pushoverapptoken, 'sound':notification.pushoversounds,
'priority': notification.pushoverpriority, 'title':notification.pushovertitle, 'retry': "30", 'expire':"3600", 'html': 1} 'priority': notification.pushoverpriority, 'title':notification.pushovertitle, 'retry': "30", 'expire':"3600", 'html': 1}
let res = await axios.post(pushoverlink, data) let res = await axios.post(pushoverlink, data)
@ -210,6 +217,10 @@ class Notification {
return false; return false;
} }
} else if (notification.type === "apprise") {
return Notification.apprise(notification, msg)
} else { } else {
throw new Error("Notification type is not supported") throw new Error("Notification type is not supported")
} }
@ -274,16 +285,26 @@ class Notification {
return true; return true;
} }
static async discord(notification, msg) { static async apprise(notification, msg) {
const client = new Discord.Client(); let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL])
await client.login(notification.discordToken) let output = s.stdout.toString();
const channel = await client.channels.fetch(notification.discordChannelID); console.log(output)
await channel.send(msg);
client.destroy() if (output) {
return {
ok: ! output.includes("ERROR"),
msg: output
}
} else {
return { }
}
}
return true; static checkApprise() {
let commandExistsSync = require('command-exists').sync;
let exists = commandExistsSync('apprise');
return exists;
} }
} }

View file

@ -35,12 +35,15 @@ let needSetup = false;
(async () => { (async () => {
await initDatabase(); await initDatabase();
console.log("Adding route")
app.use('/', express.static("dist")); app.use('/', express.static("dist"));
app.get('*', function(request, response, next) { app.get('*', function(request, response, next) {
response.sendFile(process.cwd() + '/dist/index.html'); response.sendFile(process.cwd() + '/dist/index.html');
}); });
console.log("Adding socket handler")
io.on('connection', async (socket) => { io.on('connection', async (socket) => {
socket.emit("info", { socket.emit("info", {
@ -437,12 +440,9 @@ let needSetup = false;
try { try {
checkLogin(socket) checkLogin(socket)
await Notification.send(notification, notification.name + " Testing") let res = await Notification.send(notification, notification.name + " Testing")
callback({ callback(res);
ok: true,
msg: "Sent Successfully"
});
} catch (e) { } catch (e) {
callback({ callback({
@ -451,11 +451,20 @@ let needSetup = false;
}); });
} }
}); });
socket.on("checkApprise", async (callback) => {
try {
checkLogin(socket)
callback(Notification.checkApprise());
} catch (e) {
callback(false);
}
});
}); });
console.log("Init")
server.listen(port, hostname, () => { server.listen(port, hostname, () => {
console.log(`Listening on ${hostname}:${port}`); console.log(`Listening on ${hostname}:${port}`);
startMonitors(); startMonitors();
}); });
@ -551,10 +560,11 @@ async function initDatabase() {
} }
console.log("Connecting to Database") console.log("Connecting to Database")
R.setup('sqlite', { R.setup('sqlite', {
filename: path filename: path
}); });
console.log("Connected")
R.freeze(true) R.freeze(true)
await R.autoloadModels("./server/model"); await R.autoloadModels("./server/model");
@ -569,6 +579,7 @@ async function initDatabase() {
jwtSecretBean.value = passwordHash.generate(dayjs() + "") jwtSecretBean.value = passwordHash.generate(dayjs() + "")
await R.store(jwtSecretBean) await R.store(jwtSecretBean)
console.log("Stored JWT secret into database")
} else { } else {
console.log("Load JWT secret from database.") console.log("Load JWT secret from database.")
} }

View file

@ -10,60 +10,61 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="mb-3">
<label for="type" class="form-label">Notification Type</label>
<select class="form-select" id="type" v-model="notification.type">
<option value="telegram">Telegram</option>
<option value="webhook">Webhook</option>
<option value="smtp">Email (SMTP)</option>
<option value="discord">Discord</option>
<option value="signal">Signal</option>
<option value="gotify">Gotify</option>
<option value="slack">Slack</option>
<option value="pushover">Pushover</option>
<option value="apprise">Apprise (Support 50+ Notification services)</option>
</select>
</div>
<div class="mb-3">
<label for="name" class="form-label">Friendly Name</label>
<input type="text" class="form-control" id="name" required v-model="notification.name">
</div>
<template v-if="notification.type === 'telegram'">
<div class="mb-3"> <div class="mb-3">
<label for="type" class="form-label">Notification Type</label> <label for="telegram-bot-token" class="form-label">Bot Token</label>
<select class="form-select" id="type" v-model="notification.type"> <input type="text" class="form-control" id="telegram-bot-token" required v-model="notification.telegramBotToken">
<option value="telegram">Telegram</option> <div class="form-text">You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>.</div>
<option value="webhook">Webhook</option>
<option value="smtp">Email (SMTP)</option>
<option value="discord">Discord</option>
<option value="signal">Signal</option>
<option value="gotify">Gotify</option>
<option value="slack">Slack</option>
<option value="pushover">Pushover</option>
</select>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="name" class="form-label">Friendly Name</label> <label for="telegram-chat-id" class="form-label">Chat ID</label>
<input type="text" class="form-control" id="name" required v-model="notification.name">
<div class="input-group mb-3">
<input type="text" class="form-control" id="telegram-chat-id" required v-model="notification.telegramChatID">
<button class="btn btn-outline-secondary" type="button" @click="autoGetTelegramChatID" v-if="notification.telegramBotToken">Auto Get</button>
</div>
<div class="form-text">
Support Direct Chat / Group / Channel's Chat ID
<p style="margin-top: 8px;">
You can get your chat id by sending message to the bot and go to this url to view the chat_id:
</p>
<p style="margin-top: 8px;">
<template v-if="notification.telegramBotToken">
<a :href="telegramGetUpdatesURL" target="_blank">{{ telegramGetUpdatesURL }}</a>
</template>
<template v-else>
{{ telegramGetUpdatesURL }}
</template>
</p>
</div>
</div> </div>
</template>
<template v-if="notification.type === 'telegram'">
<div class="mb-3">
<label for="telegram-bot-token" class="form-label">Bot Token</label>
<input type="text" class="form-control" id="telegram-bot-token" required v-model="notification.telegramBotToken">
<div class="form-text">You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>.</div>
</div>
<div class="mb-3">
<label for="telegram-chat-id" class="form-label">Chat ID</label>
<div class="input-group mb-3">
<input type="text" class="form-control" id="telegram-chat-id" required v-model="notification.telegramChatID">
<button class="btn btn-outline-secondary" type="button" @click="autoGetTelegramChatID" v-if="notification.telegramBotToken">Auto Get</button>
</div>
<div class="form-text">
Support Direct Chat / Group / Channel's Chat ID
<p style="margin-top: 8px;">
You can get your chat id by sending message to the bot and go to this url to view the chat_id:
</p>
<p style="margin-top: 8px;">
<template v-if="notification.telegramBotToken">
<a :href="telegramGetUpdatesURL" target="_blank">{{ telegramGetUpdatesURL }}</a>
</template>
<template v-else>
{{ telegramGetUpdatesURL }}
</template>
</p>
</div>
</div>
</template>
<template v-if="notification.type === 'webhook'"> <template v-if="notification.type === 'webhook'">
<div class="mb-3"> <div class="mb-3">
@ -219,7 +220,7 @@
</div> </div>
</div> </div>
</template> </template>
<template v-if="notification.type === 'pushover'"> <template v-if="notification.type === 'pushover'">
<div class="mb-3"> <div class="mb-3">
<label for="pushover-app-token" class="form-label">Application Token<span style="color:red;"><sup>*</sup></span></label> <label for="pushover-app-token" class="form-label">Application Token<span style="color:red;"><sup>*</sup></span></label>
@ -269,6 +270,29 @@
</div> </div>
</template> </template>
<template v-if="notification.type === 'apprise'">
<div class="mb-3">
<label for="gotify-application-token" class="form-label">Apprise URL</label>
<input type="text" class="form-control" id="gotify-application-token" required v-model="notification.appriseURL">
<div class="form-text">
<p>Example: twilio://AccountSid:AuthToken@FromPhoneNo</p>
<p>
Read more: <a href="https://github.com/caronc/apprise/wiki#notification-services" target="_blank">https://github.com/caronc/apprise/wiki#notification-services</a>
</p>
</div>
</div>
<div class="mb-3">
<p>
Status:
<span class="text-primary" v-if="appriseInstalled">Apprise is installed</span>
<span class="text-danger" v-else>Apprise is not installed. <a href="https://github.com/caronc/apprise">Read more</a></span>
</p>
</div>
</template>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-danger" @click="deleteConfirm" :disabled="processing" v-if="id">Delete</button> <button type="button" class="btn btn-danger" @click="deleteConfirm" :disabled="processing" v-if="id">Delete</button>
@ -307,17 +331,15 @@ export default {
type: null, type: null,
gotifyPriority: 8 gotifyPriority: 8
}, },
appriseInstalled: false,
} }
}, },
mounted() { mounted() {
this.modal = new Modal(this.$refs.modal) this.modal = new Modal(this.$refs.modal)
// TODO: for edit this.$root.getSocket().emit("checkApprise", (installed) => {
this.$root.getSocket().emit("getSettings", "notification", (data) => { this.appriseInstalled = installed;
// this.notification = data
}) })
}, },
methods: { methods: {