diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..d207778 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,24 @@ +{ + "name": "Wizarr notifications node devcontainer", + "image": "mcr.microsoft.com/devcontainers/base:1-bookworm", + "features": { + "ghcr.io/devcontainers/features/node:1": { "version": "lts" } + }, + "workspaceFolder": "/workspace", + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", + "remoteUser": "vscode", + "forwardPorts": [], + "customizations": { + "vscode": { + "extensions": [ + "eg2.vscode-npm-script" + ], + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + } + } + }, + "runArgs": [ + "--network=bridge2", "--name=wizarr-notify-dev" + ] +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..d207778 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,24 @@ +{ + "name": "Wizarr notifications node devcontainer", + "image": "mcr.microsoft.com/devcontainers/base:1-bookworm", + "features": { + "ghcr.io/devcontainers/features/node:1": { "version": "lts" } + }, + "workspaceFolder": "/workspace", + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", + "remoteUser": "vscode", + "forwardPorts": [], + "customizations": { + "vscode": { + "extensions": [ + "eg2.vscode-npm-script" + ], + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + } + } + }, + "runArgs": [ + "--network=bridge2", "--name=wizarr-notify-dev" + ] +} diff --git a/default_templates/user_deleted.json b/default_templates/user_deleted.json index bef021a..1bf097b 100644 --- a/default_templates/user_deleted.json +++ b/default_templates/user_deleted.json @@ -1,3 +1,4 @@ +// :ignore { "title": "User Deleted", "message": "User {{user}} deleted from server" diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..d207778 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,24 @@ +{ + "name": "Wizarr notifications node devcontainer", + "image": "mcr.microsoft.com/devcontainers/base:1-bookworm", + "features": { + "ghcr.io/devcontainers/features/node:1": { "version": "lts" } + }, + "workspaceFolder": "/workspace", + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", + "remoteUser": "vscode", + "forwardPorts": [], + "customizations": { + "vscode": { + "extensions": [ + "eg2.vscode-npm-script" + ], + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + } + } + }, + "runArgs": [ + "--network=bridge2", "--name=wizarr-notify-dev" + ] +} diff --git a/default_templates/user_deleted.json b/default_templates/user_deleted.json index bef021a..1bf097b 100644 --- a/default_templates/user_deleted.json +++ b/default_templates/user_deleted.json @@ -1,3 +1,4 @@ +// :ignore { "title": "User Deleted", "message": "User {{user}} deleted from server" diff --git a/default_templates/user_invited.json b/default_templates/user_invited.json deleted file mode 100644 index bca7ba6..0000000 --- a/default_templates/user_invited.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "User Invited", - "message": "User {{user}} accepted invit from code {{code}}" -} \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..d207778 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,24 @@ +{ + "name": "Wizarr notifications node devcontainer", + "image": "mcr.microsoft.com/devcontainers/base:1-bookworm", + "features": { + "ghcr.io/devcontainers/features/node:1": { "version": "lts" } + }, + "workspaceFolder": "/workspace", + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", + "remoteUser": "vscode", + "forwardPorts": [], + "customizations": { + "vscode": { + "extensions": [ + "eg2.vscode-npm-script" + ], + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + } + } + }, + "runArgs": [ + "--network=bridge2", "--name=wizarr-notify-dev" + ] +} diff --git a/default_templates/user_deleted.json b/default_templates/user_deleted.json index bef021a..1bf097b 100644 --- a/default_templates/user_deleted.json +++ b/default_templates/user_deleted.json @@ -1,3 +1,4 @@ +// :ignore { "title": "User Deleted", "message": "User {{user}} deleted from server" diff --git a/default_templates/user_invited.json b/default_templates/user_invited.json deleted file mode 100644 index bca7ba6..0000000 --- a/default_templates/user_invited.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "User Invited", - "message": "User {{user}} accepted invit from code {{code}}" -} \ No newline at end of file diff --git a/default_templates/user_joined.json b/default_templates/user_joined.json new file mode 100644 index 0000000..28222a8 --- /dev/null +++ b/default_templates/user_joined.json @@ -0,0 +1,4 @@ +{ + "title": "User Joined", + "message": "User {{user}} accepted invite from code {{code}}" +} \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..d207778 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,24 @@ +{ + "name": "Wizarr notifications node devcontainer", + "image": "mcr.microsoft.com/devcontainers/base:1-bookworm", + "features": { + "ghcr.io/devcontainers/features/node:1": { "version": "lts" } + }, + "workspaceFolder": "/workspace", + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", + "remoteUser": "vscode", + "forwardPorts": [], + "customizations": { + "vscode": { + "extensions": [ + "eg2.vscode-npm-script" + ], + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + } + } + }, + "runArgs": [ + "--network=bridge2", "--name=wizarr-notify-dev" + ] +} diff --git a/default_templates/user_deleted.json b/default_templates/user_deleted.json index bef021a..1bf097b 100644 --- a/default_templates/user_deleted.json +++ b/default_templates/user_deleted.json @@ -1,3 +1,4 @@ +// :ignore { "title": "User Deleted", "message": "User {{user}} deleted from server" diff --git a/default_templates/user_invited.json b/default_templates/user_invited.json deleted file mode 100644 index bca7ba6..0000000 --- a/default_templates/user_invited.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "User Invited", - "message": "User {{user}} accepted invit from code {{code}}" -} \ No newline at end of file diff --git a/default_templates/user_joined.json b/default_templates/user_joined.json new file mode 100644 index 0000000..28222a8 --- /dev/null +++ b/default_templates/user_joined.json @@ -0,0 +1,4 @@ +{ + "title": "User Joined", + "message": "User {{user}} accepted invite from code {{code}}" +} \ No newline at end of file diff --git a/docker_run_temp.sh b/docker_run_temp.sh deleted file mode 100755 index 2ca9841..0000000 --- a/docker_run_temp.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -docker run --name wizarr-notify -d --rm -it -v $(pwd):/opt/wizarr_notify -p:9991:80 "$@" node:latest sleep infinity diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..d207778 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,24 @@ +{ + "name": "Wizarr notifications node devcontainer", + "image": "mcr.microsoft.com/devcontainers/base:1-bookworm", + "features": { + "ghcr.io/devcontainers/features/node:1": { "version": "lts" } + }, + "workspaceFolder": "/workspace", + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", + "remoteUser": "vscode", + "forwardPorts": [], + "customizations": { + "vscode": { + "extensions": [ + "eg2.vscode-npm-script" + ], + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + } + } + }, + "runArgs": [ + "--network=bridge2", "--name=wizarr-notify-dev" + ] +} diff --git a/default_templates/user_deleted.json b/default_templates/user_deleted.json index bef021a..1bf097b 100644 --- a/default_templates/user_deleted.json +++ b/default_templates/user_deleted.json @@ -1,3 +1,4 @@ +// :ignore { "title": "User Deleted", "message": "User {{user}} deleted from server" diff --git a/default_templates/user_invited.json b/default_templates/user_invited.json deleted file mode 100644 index bca7ba6..0000000 --- a/default_templates/user_invited.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "User Invited", - "message": "User {{user}} accepted invit from code {{code}}" -} \ No newline at end of file diff --git a/default_templates/user_joined.json b/default_templates/user_joined.json new file mode 100644 index 0000000..28222a8 --- /dev/null +++ b/default_templates/user_joined.json @@ -0,0 +1,4 @@ +{ + "title": "User Joined", + "message": "User {{user}} accepted invite from code {{code}}" +} \ No newline at end of file diff --git a/docker_run_temp.sh b/docker_run_temp.sh deleted file mode 100755 index 2ca9841..0000000 --- a/docker_run_temp.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -docker run --name wizarr-notify -d --rm -it -v $(pwd):/opt/wizarr_notify -p:9991:80 "$@" node:latest sleep infinity diff --git a/index.js b/index.js index 1e44bbf..266af9e 100644 --- a/index.js +++ b/index.js @@ -1,67 +1,80 @@ const express = require('express'); const Pushover = require('pushover-notifications'); -const mustache = require('mustache'); const fs = require('node:fs'); const path = require('node:path'); +const nodemailer = require('nodemailer'); +const mustacheExpress = require('mustache-express'); +const mustache = require('mustache'); -/* -{"event":"user_invited","data":{"auth":"WHo3P_ZCFdnPyWFKJvKR","code":"RLMUQK","created":"2024-11-21 21:30:24.728194","email":"bavis.mark+plex2@gmail.com","expires":null,"id":3,"token":"449061671","username":"bavis.ma"}} -{"event":"user_deleted","data":{"auth":"WHo3P_ZCFdnPyWFKJvKR","code":"RLMUQK","created":"2024-11-21 21:30:24.728194","email":"bavis.mark+plex2@gmail.com","expires":null,"id":3,"token":"449061671","username":"bavis.ma"}} -{"event":"invitation_created","data":{"allow_download":true,"code":"RLMUQK","created":"2024-11-21 21:11:29.928042","duration":null,"expires":"2024-11-22 21:11:29.928034","hide_user":true,"id":1,"live_tv":true,"plex_allow_sync":false,"sessions":0,"specific_libraries":[""],"unlimited":false,"used":false,"used_at":null,"used_by":null}} -{"event":"invitation_deleted","data":{"allow_download":true,"code":"RLMUQK","created":"2024-11-21 21:11:29.928042","duration":null,"expires":"2024-11-22 21:11:29.928034","hide_user":true,"id":1,"live_tv":true,"plex_allow_sync":false,"sessions":"0","specific_libraries":"","unlimited":false,"used":true,"used_at":"2024-11-21 13:30:35.743697","used_by":null}} -*/ +const emailFrom = process.env.MAIL_FROM; +const emailSubject = process.env.MAIL_SUBJECT; +const notify_templates = getNotifyTemplates(); +console.log("Listening for: " + Array.from(notify_templates.keys()).join(",")); + const pushover = new Pushover( { - user: process.env['PUSHOVER_USER'], - token: process.env['PUSHOVER_TOKEN'], + user: process.env.PUSHOVER_USER, + token: process.env.PUSHOVER_TOKEN, } ); -const notify_templates = new Map(); - -const templatesStat = fs.statSync("templates", {throwIfNoEntry: false}); -if (!templatesStat) fs.mkdirSync("templates"); - -if (fs.readdirSync("templates").length == 0) +const testEmailData = {username:"Test User"}; +function renderTemplateManual(template, data) { - for (let filename of fs.readdirSync("default_templates", {encoding:'utf8'})) - { - fs.copyFileSync(path.join("default_templates", filename), path.join("templates", filename)); - } + return mustache.render(fs.readFileSync(`views/${template}.html`, {'encoding':'utf8'}), data) } -for (let filename of fs.readdirSync("templates", {encoding:'utf8'})) +async function sendEmail(from, to, subject, html) { - const is_comment = l => l.trim().startsWith("//"); - if (path.extname(filename) != '.json') continue; + let transporter = nodemailer.createTransport({ + host: process.env.MAIL_HOST, + port: 587, // Common port for secure SMTP (TLS) + secure: false, // Use 'true' if using port 465 (SSL), 'false' for other ports like 587 (TLS) + requireTLS: true, + auth: { + user: process.env.SMTP_USER, + pass: process.env.SMTP_PASSWORD + } + }); - let lines = fs.readFileSync(path.join("templates", filename), {'encoding':'utf8'}).split(/[\r\n]/); - if (lines.some(l => is_comment(l) && l.indexOf(":ignore") != -1)) continue; - lines = lines.filter(l => !is_comment(l)); + //await transporter.verify(); - notify_templates.set(path.parse(filename).name, lines.join("\n")); + await transporter.sendMail({from, to, subject, html}); + + console.log(`Email sent to ${to}`); } -console.log("Listening for: " + Array.from(notify_templates.keys()).join(",")); - const app = express(); +// Register '.mustache' extension with The Mustache Express +app.engine('html', mustacheExpress()); +app.set('view engine', 'html'); +app.set('views', __dirname + '/views'); app.use(express.json()); -app.post('/', (req, res) => + + +app.get('/email.html', (req, res) => res.send(renderTemplateManual('email', testEmailData))); // res.render('email', testEmailData)); +app.get('/email_test', async (req, res) => { + await sendEmail(emailFrom, req.query.to, emailSubject, renderTemplateManual('email', testEmailData)); + res.send('sent'); +}); + +app.post('/', async (req, res, next) => +{ + console.log(req.body); + if ('event' in req.body) { - console.log(JSON.stringify(req.body)); - if (notify_templates.has(req.body.event)) { const template_output = JSON.parse(mustache.render(notify_templates.get(req.body.event), { - ...req.body.data, - user: `${req.body.data.username} (${req.body.data.email})` + ...req.body, + user: `${req.body.username} (${req.body.email})` })); if ('title' in template_output && 'message' in template_output) @@ -71,11 +84,53 @@ message: template_output.message }); } + + if (req.body.event == 'user_joined') + { + try + { + await sendEmail(emailFrom, req.body.email, emailSubject, renderTemplateManual('email', req.body)); + } + catch (err) + { + next(err); + } + } } } res.end(); }) -app.use((req, res) => res.status(404).end()); +app.use((_, res) => res.status(404).end()); app.listen(80); + +function getNotifyTemplates() +{ + const templates = new Map(); + + const templatesStat = fs.statSync("templates", {throwIfNoEntry: false}); + if (!templatesStat) fs.mkdirSync("templates"); + + if (fs.readdirSync("templates").length == 0) + { + for (let filename of fs.readdirSync("default_templates", {encoding:'utf8'})) + { + fs.copyFileSync(path.join("default_templates", filename), path.join("templates", filename)); + } + } + + for (let filename of fs.readdirSync("templates", {encoding:'utf8'})) + { + const is_comment = l => l.trim().startsWith("//"); + if (path.extname(filename) != '.json') continue; + + let lines = fs.readFileSync(path.join("templates", filename), {'encoding':'utf8'}).split(/[\r\n]/); + if (lines.some(l => is_comment(l) && l.indexOf(":ignore") != -1)) continue; + lines = lines.filter(l => !is_comment(l)); + + templates.set(path.parse(filename).name, lines.join("\n")); + } + + return templates; +} \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..d207778 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,24 @@ +{ + "name": "Wizarr notifications node devcontainer", + "image": "mcr.microsoft.com/devcontainers/base:1-bookworm", + "features": { + "ghcr.io/devcontainers/features/node:1": { "version": "lts" } + }, + "workspaceFolder": "/workspace", + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", + "remoteUser": "vscode", + "forwardPorts": [], + "customizations": { + "vscode": { + "extensions": [ + "eg2.vscode-npm-script" + ], + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + } + } + }, + "runArgs": [ + "--network=bridge2", "--name=wizarr-notify-dev" + ] +} diff --git a/default_templates/user_deleted.json b/default_templates/user_deleted.json index bef021a..1bf097b 100644 --- a/default_templates/user_deleted.json +++ b/default_templates/user_deleted.json @@ -1,3 +1,4 @@ +// :ignore { "title": "User Deleted", "message": "User {{user}} deleted from server" diff --git a/default_templates/user_invited.json b/default_templates/user_invited.json deleted file mode 100644 index bca7ba6..0000000 --- a/default_templates/user_invited.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "User Invited", - "message": "User {{user}} accepted invit from code {{code}}" -} \ No newline at end of file diff --git a/default_templates/user_joined.json b/default_templates/user_joined.json new file mode 100644 index 0000000..28222a8 --- /dev/null +++ b/default_templates/user_joined.json @@ -0,0 +1,4 @@ +{ + "title": "User Joined", + "message": "User {{user}} accepted invite from code {{code}}" +} \ No newline at end of file diff --git a/docker_run_temp.sh b/docker_run_temp.sh deleted file mode 100755 index 2ca9841..0000000 --- a/docker_run_temp.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -docker run --name wizarr-notify -d --rm -it -v $(pwd):/opt/wizarr_notify -p:9991:80 "$@" node:latest sleep infinity diff --git a/index.js b/index.js index 1e44bbf..266af9e 100644 --- a/index.js +++ b/index.js @@ -1,67 +1,80 @@ const express = require('express'); const Pushover = require('pushover-notifications'); -const mustache = require('mustache'); const fs = require('node:fs'); const path = require('node:path'); +const nodemailer = require('nodemailer'); +const mustacheExpress = require('mustache-express'); +const mustache = require('mustache'); -/* -{"event":"user_invited","data":{"auth":"WHo3P_ZCFdnPyWFKJvKR","code":"RLMUQK","created":"2024-11-21 21:30:24.728194","email":"bavis.mark+plex2@gmail.com","expires":null,"id":3,"token":"449061671","username":"bavis.ma"}} -{"event":"user_deleted","data":{"auth":"WHo3P_ZCFdnPyWFKJvKR","code":"RLMUQK","created":"2024-11-21 21:30:24.728194","email":"bavis.mark+plex2@gmail.com","expires":null,"id":3,"token":"449061671","username":"bavis.ma"}} -{"event":"invitation_created","data":{"allow_download":true,"code":"RLMUQK","created":"2024-11-21 21:11:29.928042","duration":null,"expires":"2024-11-22 21:11:29.928034","hide_user":true,"id":1,"live_tv":true,"plex_allow_sync":false,"sessions":0,"specific_libraries":[""],"unlimited":false,"used":false,"used_at":null,"used_by":null}} -{"event":"invitation_deleted","data":{"allow_download":true,"code":"RLMUQK","created":"2024-11-21 21:11:29.928042","duration":null,"expires":"2024-11-22 21:11:29.928034","hide_user":true,"id":1,"live_tv":true,"plex_allow_sync":false,"sessions":"0","specific_libraries":"","unlimited":false,"used":true,"used_at":"2024-11-21 13:30:35.743697","used_by":null}} -*/ +const emailFrom = process.env.MAIL_FROM; +const emailSubject = process.env.MAIL_SUBJECT; +const notify_templates = getNotifyTemplates(); +console.log("Listening for: " + Array.from(notify_templates.keys()).join(",")); + const pushover = new Pushover( { - user: process.env['PUSHOVER_USER'], - token: process.env['PUSHOVER_TOKEN'], + user: process.env.PUSHOVER_USER, + token: process.env.PUSHOVER_TOKEN, } ); -const notify_templates = new Map(); - -const templatesStat = fs.statSync("templates", {throwIfNoEntry: false}); -if (!templatesStat) fs.mkdirSync("templates"); - -if (fs.readdirSync("templates").length == 0) +const testEmailData = {username:"Test User"}; +function renderTemplateManual(template, data) { - for (let filename of fs.readdirSync("default_templates", {encoding:'utf8'})) - { - fs.copyFileSync(path.join("default_templates", filename), path.join("templates", filename)); - } + return mustache.render(fs.readFileSync(`views/${template}.html`, {'encoding':'utf8'}), data) } -for (let filename of fs.readdirSync("templates", {encoding:'utf8'})) +async function sendEmail(from, to, subject, html) { - const is_comment = l => l.trim().startsWith("//"); - if (path.extname(filename) != '.json') continue; + let transporter = nodemailer.createTransport({ + host: process.env.MAIL_HOST, + port: 587, // Common port for secure SMTP (TLS) + secure: false, // Use 'true' if using port 465 (SSL), 'false' for other ports like 587 (TLS) + requireTLS: true, + auth: { + user: process.env.SMTP_USER, + pass: process.env.SMTP_PASSWORD + } + }); - let lines = fs.readFileSync(path.join("templates", filename), {'encoding':'utf8'}).split(/[\r\n]/); - if (lines.some(l => is_comment(l) && l.indexOf(":ignore") != -1)) continue; - lines = lines.filter(l => !is_comment(l)); + //await transporter.verify(); - notify_templates.set(path.parse(filename).name, lines.join("\n")); + await transporter.sendMail({from, to, subject, html}); + + console.log(`Email sent to ${to}`); } -console.log("Listening for: " + Array.from(notify_templates.keys()).join(",")); - const app = express(); +// Register '.mustache' extension with The Mustache Express +app.engine('html', mustacheExpress()); +app.set('view engine', 'html'); +app.set('views', __dirname + '/views'); app.use(express.json()); -app.post('/', (req, res) => + + +app.get('/email.html', (req, res) => res.send(renderTemplateManual('email', testEmailData))); // res.render('email', testEmailData)); +app.get('/email_test', async (req, res) => { + await sendEmail(emailFrom, req.query.to, emailSubject, renderTemplateManual('email', testEmailData)); + res.send('sent'); +}); + +app.post('/', async (req, res, next) => +{ + console.log(req.body); + if ('event' in req.body) { - console.log(JSON.stringify(req.body)); - if (notify_templates.has(req.body.event)) { const template_output = JSON.parse(mustache.render(notify_templates.get(req.body.event), { - ...req.body.data, - user: `${req.body.data.username} (${req.body.data.email})` + ...req.body, + user: `${req.body.username} (${req.body.email})` })); if ('title' in template_output && 'message' in template_output) @@ -71,11 +84,53 @@ message: template_output.message }); } + + if (req.body.event == 'user_joined') + { + try + { + await sendEmail(emailFrom, req.body.email, emailSubject, renderTemplateManual('email', req.body)); + } + catch (err) + { + next(err); + } + } } } res.end(); }) -app.use((req, res) => res.status(404).end()); +app.use((_, res) => res.status(404).end()); app.listen(80); + +function getNotifyTemplates() +{ + const templates = new Map(); + + const templatesStat = fs.statSync("templates", {throwIfNoEntry: false}); + if (!templatesStat) fs.mkdirSync("templates"); + + if (fs.readdirSync("templates").length == 0) + { + for (let filename of fs.readdirSync("default_templates", {encoding:'utf8'})) + { + fs.copyFileSync(path.join("default_templates", filename), path.join("templates", filename)); + } + } + + for (let filename of fs.readdirSync("templates", {encoding:'utf8'})) + { + const is_comment = l => l.trim().startsWith("//"); + if (path.extname(filename) != '.json') continue; + + let lines = fs.readFileSync(path.join("templates", filename), {'encoding':'utf8'}).split(/[\r\n]/); + if (lines.some(l => is_comment(l) && l.indexOf(":ignore") != -1)) continue; + lines = lines.filter(l => !is_comment(l)); + + templates.set(path.parse(filename).name, lines.join("\n")); + } + + return templates; +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e8d4840..847e1b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,14 @@ "license": "ISC", "dependencies": { "express": "^4.21.1", + "moustache": "^0.0.4", "mustache": "^4.2.0", + "mustache-express": "^1.3.2", + "nodemailer": "^7.0.10", "pushover-notifications": "^1.2.3" + }, + "devDependencies": { + "nodemon": "^3.1.10" } }, "node_modules/accepts": { @@ -26,11 +32,47 @@ "node": ">= 0.6" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -54,6 +96,28 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -80,6 +144,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -199,9 +293,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -222,7 +316,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -237,6 +331,22 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, "node_modules/finalhandler": { @@ -272,6 +382,20 @@ "node": ">= 0.6" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -298,6 +422,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -309,6 +445,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -379,6 +524,12 @@ "node": ">=0.10.0" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -392,6 +543,56 @@ "node": ">= 0.10" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -446,6 +647,24 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/moustache": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/moustache/-/moustache-0.0.4.tgz", + "integrity": "sha512-9dSiwLbGR98lcHP/2HimKuRBgKC3/CXKtwy1bKFQ5kGOy3KIdynwbQyEZKN/Q57elo6yihV1FkVHsorA8E7nzg==", + "deprecated": "INVALID PACKAGE" + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -455,10 +674,24 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", "bin": { "mustache": "bin/mustache" } }, + "node_modules/mustache-express": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mustache-express/-/mustache-express-1.3.2.tgz", + "integrity": "sha512-yAdGHctEq9ubUH7h9O6Z6kW35fwfE+7LpW/TBrcfVibZuiVRHDcK8DEydgiU5nlJmJUY5VC3jv2lzaPUL+Arkw==", + "dependencies": { + "async": "~3.2.0", + "lru-cache": "~5.1.1", + "mustache": "^4.2.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -467,6 +700,74 @@ "node": ">= 0.6" } }, + "node_modules/nodemailer": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.10.tgz", + "integrity": "sha512-Us/Se1WtT0ylXgNFfyFSx4LElllVLJXQjWi2Xz17xWw7amDKO2MLtFnVp1WACy7GkVGs+oBlRopVNUzlrGSw1w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", @@ -498,9 +799,21 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -514,6 +827,12 @@ "node": ">= 0.10" } }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, "node_modules/pushover-notifications": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/pushover-notifications/-/pushover-notifications-1.2.3.tgz", @@ -558,6 +877,18 @@ "node": ">= 0.8" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -582,6 +913,18 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", @@ -670,6 +1013,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -678,6 +1033,30 @@ "node": ">= 0.8" } }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -686,6 +1065,15 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -698,6 +1086,12 @@ "node": ">= 0.6" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -721,6 +1115,11 @@ "engines": { "node": ">= 0.8" } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" } } } diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..d207778 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,24 @@ +{ + "name": "Wizarr notifications node devcontainer", + "image": "mcr.microsoft.com/devcontainers/base:1-bookworm", + "features": { + "ghcr.io/devcontainers/features/node:1": { "version": "lts" } + }, + "workspaceFolder": "/workspace", + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", + "remoteUser": "vscode", + "forwardPorts": [], + "customizations": { + "vscode": { + "extensions": [ + "eg2.vscode-npm-script" + ], + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + } + } + }, + "runArgs": [ + "--network=bridge2", "--name=wizarr-notify-dev" + ] +} diff --git a/default_templates/user_deleted.json b/default_templates/user_deleted.json index bef021a..1bf097b 100644 --- a/default_templates/user_deleted.json +++ b/default_templates/user_deleted.json @@ -1,3 +1,4 @@ +// :ignore { "title": "User Deleted", "message": "User {{user}} deleted from server" diff --git a/default_templates/user_invited.json b/default_templates/user_invited.json deleted file mode 100644 index bca7ba6..0000000 --- a/default_templates/user_invited.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "User Invited", - "message": "User {{user}} accepted invit from code {{code}}" -} \ No newline at end of file diff --git a/default_templates/user_joined.json b/default_templates/user_joined.json new file mode 100644 index 0000000..28222a8 --- /dev/null +++ b/default_templates/user_joined.json @@ -0,0 +1,4 @@ +{ + "title": "User Joined", + "message": "User {{user}} accepted invite from code {{code}}" +} \ No newline at end of file diff --git a/docker_run_temp.sh b/docker_run_temp.sh deleted file mode 100755 index 2ca9841..0000000 --- a/docker_run_temp.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -docker run --name wizarr-notify -d --rm -it -v $(pwd):/opt/wizarr_notify -p:9991:80 "$@" node:latest sleep infinity diff --git a/index.js b/index.js index 1e44bbf..266af9e 100644 --- a/index.js +++ b/index.js @@ -1,67 +1,80 @@ const express = require('express'); const Pushover = require('pushover-notifications'); -const mustache = require('mustache'); const fs = require('node:fs'); const path = require('node:path'); +const nodemailer = require('nodemailer'); +const mustacheExpress = require('mustache-express'); +const mustache = require('mustache'); -/* -{"event":"user_invited","data":{"auth":"WHo3P_ZCFdnPyWFKJvKR","code":"RLMUQK","created":"2024-11-21 21:30:24.728194","email":"bavis.mark+plex2@gmail.com","expires":null,"id":3,"token":"449061671","username":"bavis.ma"}} -{"event":"user_deleted","data":{"auth":"WHo3P_ZCFdnPyWFKJvKR","code":"RLMUQK","created":"2024-11-21 21:30:24.728194","email":"bavis.mark+plex2@gmail.com","expires":null,"id":3,"token":"449061671","username":"bavis.ma"}} -{"event":"invitation_created","data":{"allow_download":true,"code":"RLMUQK","created":"2024-11-21 21:11:29.928042","duration":null,"expires":"2024-11-22 21:11:29.928034","hide_user":true,"id":1,"live_tv":true,"plex_allow_sync":false,"sessions":0,"specific_libraries":[""],"unlimited":false,"used":false,"used_at":null,"used_by":null}} -{"event":"invitation_deleted","data":{"allow_download":true,"code":"RLMUQK","created":"2024-11-21 21:11:29.928042","duration":null,"expires":"2024-11-22 21:11:29.928034","hide_user":true,"id":1,"live_tv":true,"plex_allow_sync":false,"sessions":"0","specific_libraries":"","unlimited":false,"used":true,"used_at":"2024-11-21 13:30:35.743697","used_by":null}} -*/ +const emailFrom = process.env.MAIL_FROM; +const emailSubject = process.env.MAIL_SUBJECT; +const notify_templates = getNotifyTemplates(); +console.log("Listening for: " + Array.from(notify_templates.keys()).join(",")); + const pushover = new Pushover( { - user: process.env['PUSHOVER_USER'], - token: process.env['PUSHOVER_TOKEN'], + user: process.env.PUSHOVER_USER, + token: process.env.PUSHOVER_TOKEN, } ); -const notify_templates = new Map(); - -const templatesStat = fs.statSync("templates", {throwIfNoEntry: false}); -if (!templatesStat) fs.mkdirSync("templates"); - -if (fs.readdirSync("templates").length == 0) +const testEmailData = {username:"Test User"}; +function renderTemplateManual(template, data) { - for (let filename of fs.readdirSync("default_templates", {encoding:'utf8'})) - { - fs.copyFileSync(path.join("default_templates", filename), path.join("templates", filename)); - } + return mustache.render(fs.readFileSync(`views/${template}.html`, {'encoding':'utf8'}), data) } -for (let filename of fs.readdirSync("templates", {encoding:'utf8'})) +async function sendEmail(from, to, subject, html) { - const is_comment = l => l.trim().startsWith("//"); - if (path.extname(filename) != '.json') continue; + let transporter = nodemailer.createTransport({ + host: process.env.MAIL_HOST, + port: 587, // Common port for secure SMTP (TLS) + secure: false, // Use 'true' if using port 465 (SSL), 'false' for other ports like 587 (TLS) + requireTLS: true, + auth: { + user: process.env.SMTP_USER, + pass: process.env.SMTP_PASSWORD + } + }); - let lines = fs.readFileSync(path.join("templates", filename), {'encoding':'utf8'}).split(/[\r\n]/); - if (lines.some(l => is_comment(l) && l.indexOf(":ignore") != -1)) continue; - lines = lines.filter(l => !is_comment(l)); + //await transporter.verify(); - notify_templates.set(path.parse(filename).name, lines.join("\n")); + await transporter.sendMail({from, to, subject, html}); + + console.log(`Email sent to ${to}`); } -console.log("Listening for: " + Array.from(notify_templates.keys()).join(",")); - const app = express(); +// Register '.mustache' extension with The Mustache Express +app.engine('html', mustacheExpress()); +app.set('view engine', 'html'); +app.set('views', __dirname + '/views'); app.use(express.json()); -app.post('/', (req, res) => + + +app.get('/email.html', (req, res) => res.send(renderTemplateManual('email', testEmailData))); // res.render('email', testEmailData)); +app.get('/email_test', async (req, res) => { + await sendEmail(emailFrom, req.query.to, emailSubject, renderTemplateManual('email', testEmailData)); + res.send('sent'); +}); + +app.post('/', async (req, res, next) => +{ + console.log(req.body); + if ('event' in req.body) { - console.log(JSON.stringify(req.body)); - if (notify_templates.has(req.body.event)) { const template_output = JSON.parse(mustache.render(notify_templates.get(req.body.event), { - ...req.body.data, - user: `${req.body.data.username} (${req.body.data.email})` + ...req.body, + user: `${req.body.username} (${req.body.email})` })); if ('title' in template_output && 'message' in template_output) @@ -71,11 +84,53 @@ message: template_output.message }); } + + if (req.body.event == 'user_joined') + { + try + { + await sendEmail(emailFrom, req.body.email, emailSubject, renderTemplateManual('email', req.body)); + } + catch (err) + { + next(err); + } + } } } res.end(); }) -app.use((req, res) => res.status(404).end()); +app.use((_, res) => res.status(404).end()); app.listen(80); + +function getNotifyTemplates() +{ + const templates = new Map(); + + const templatesStat = fs.statSync("templates", {throwIfNoEntry: false}); + if (!templatesStat) fs.mkdirSync("templates"); + + if (fs.readdirSync("templates").length == 0) + { + for (let filename of fs.readdirSync("default_templates", {encoding:'utf8'})) + { + fs.copyFileSync(path.join("default_templates", filename), path.join("templates", filename)); + } + } + + for (let filename of fs.readdirSync("templates", {encoding:'utf8'})) + { + const is_comment = l => l.trim().startsWith("//"); + if (path.extname(filename) != '.json') continue; + + let lines = fs.readFileSync(path.join("templates", filename), {'encoding':'utf8'}).split(/[\r\n]/); + if (lines.some(l => is_comment(l) && l.indexOf(":ignore") != -1)) continue; + lines = lines.filter(l => !is_comment(l)); + + templates.set(path.parse(filename).name, lines.join("\n")); + } + + return templates; +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e8d4840..847e1b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,14 @@ "license": "ISC", "dependencies": { "express": "^4.21.1", + "moustache": "^0.0.4", "mustache": "^4.2.0", + "mustache-express": "^1.3.2", + "nodemailer": "^7.0.10", "pushover-notifications": "^1.2.3" + }, + "devDependencies": { + "nodemon": "^3.1.10" } }, "node_modules/accepts": { @@ -26,11 +32,47 @@ "node": ">= 0.6" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -54,6 +96,28 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -80,6 +144,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -199,9 +293,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -222,7 +316,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -237,6 +331,22 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, "node_modules/finalhandler": { @@ -272,6 +382,20 @@ "node": ">= 0.6" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -298,6 +422,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -309,6 +445,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -379,6 +524,12 @@ "node": ">=0.10.0" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -392,6 +543,56 @@ "node": ">= 0.10" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -446,6 +647,24 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/moustache": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/moustache/-/moustache-0.0.4.tgz", + "integrity": "sha512-9dSiwLbGR98lcHP/2HimKuRBgKC3/CXKtwy1bKFQ5kGOy3KIdynwbQyEZKN/Q57elo6yihV1FkVHsorA8E7nzg==", + "deprecated": "INVALID PACKAGE" + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -455,10 +674,24 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", "bin": { "mustache": "bin/mustache" } }, + "node_modules/mustache-express": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mustache-express/-/mustache-express-1.3.2.tgz", + "integrity": "sha512-yAdGHctEq9ubUH7h9O6Z6kW35fwfE+7LpW/TBrcfVibZuiVRHDcK8DEydgiU5nlJmJUY5VC3jv2lzaPUL+Arkw==", + "dependencies": { + "async": "~3.2.0", + "lru-cache": "~5.1.1", + "mustache": "^4.2.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -467,6 +700,74 @@ "node": ">= 0.6" } }, + "node_modules/nodemailer": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.10.tgz", + "integrity": "sha512-Us/Se1WtT0ylXgNFfyFSx4LElllVLJXQjWi2Xz17xWw7amDKO2MLtFnVp1WACy7GkVGs+oBlRopVNUzlrGSw1w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", @@ -498,9 +799,21 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -514,6 +827,12 @@ "node": ">= 0.10" } }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, "node_modules/pushover-notifications": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/pushover-notifications/-/pushover-notifications-1.2.3.tgz", @@ -558,6 +877,18 @@ "node": ">= 0.8" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -582,6 +913,18 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", @@ -670,6 +1013,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -678,6 +1033,30 @@ "node": ">= 0.8" } }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -686,6 +1065,15 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -698,6 +1086,12 @@ "node": ">= 0.6" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -721,6 +1115,11 @@ "engines": { "node": ">= 0.8" } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" } } } diff --git a/package.json b/package.json index 3b990ba..9d37a6a 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,16 @@ "license": "ISC", "dependencies": { "express": "^4.21.1", + "moustache": "^0.0.4", "mustache": "^4.2.0", + "mustache-express": "^1.3.2", + "nodemailer": "^7.0.10", "pushover-notifications": "^1.2.3" + }, + "devDependencies": { + "nodemon": "^3.1.10" + }, + "nodemonConfig": { + "ext": "js,html" } } diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..d207778 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,24 @@ +{ + "name": "Wizarr notifications node devcontainer", + "image": "mcr.microsoft.com/devcontainers/base:1-bookworm", + "features": { + "ghcr.io/devcontainers/features/node:1": { "version": "lts" } + }, + "workspaceFolder": "/workspace", + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", + "remoteUser": "vscode", + "forwardPorts": [], + "customizations": { + "vscode": { + "extensions": [ + "eg2.vscode-npm-script" + ], + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + } + } + }, + "runArgs": [ + "--network=bridge2", "--name=wizarr-notify-dev" + ] +} diff --git a/default_templates/user_deleted.json b/default_templates/user_deleted.json index bef021a..1bf097b 100644 --- a/default_templates/user_deleted.json +++ b/default_templates/user_deleted.json @@ -1,3 +1,4 @@ +// :ignore { "title": "User Deleted", "message": "User {{user}} deleted from server" diff --git a/default_templates/user_invited.json b/default_templates/user_invited.json deleted file mode 100644 index bca7ba6..0000000 --- a/default_templates/user_invited.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "User Invited", - "message": "User {{user}} accepted invit from code {{code}}" -} \ No newline at end of file diff --git a/default_templates/user_joined.json b/default_templates/user_joined.json new file mode 100644 index 0000000..28222a8 --- /dev/null +++ b/default_templates/user_joined.json @@ -0,0 +1,4 @@ +{ + "title": "User Joined", + "message": "User {{user}} accepted invite from code {{code}}" +} \ No newline at end of file diff --git a/docker_run_temp.sh b/docker_run_temp.sh deleted file mode 100755 index 2ca9841..0000000 --- a/docker_run_temp.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -docker run --name wizarr-notify -d --rm -it -v $(pwd):/opt/wizarr_notify -p:9991:80 "$@" node:latest sleep infinity diff --git a/index.js b/index.js index 1e44bbf..266af9e 100644 --- a/index.js +++ b/index.js @@ -1,67 +1,80 @@ const express = require('express'); const Pushover = require('pushover-notifications'); -const mustache = require('mustache'); const fs = require('node:fs'); const path = require('node:path'); +const nodemailer = require('nodemailer'); +const mustacheExpress = require('mustache-express'); +const mustache = require('mustache'); -/* -{"event":"user_invited","data":{"auth":"WHo3P_ZCFdnPyWFKJvKR","code":"RLMUQK","created":"2024-11-21 21:30:24.728194","email":"bavis.mark+plex2@gmail.com","expires":null,"id":3,"token":"449061671","username":"bavis.ma"}} -{"event":"user_deleted","data":{"auth":"WHo3P_ZCFdnPyWFKJvKR","code":"RLMUQK","created":"2024-11-21 21:30:24.728194","email":"bavis.mark+plex2@gmail.com","expires":null,"id":3,"token":"449061671","username":"bavis.ma"}} -{"event":"invitation_created","data":{"allow_download":true,"code":"RLMUQK","created":"2024-11-21 21:11:29.928042","duration":null,"expires":"2024-11-22 21:11:29.928034","hide_user":true,"id":1,"live_tv":true,"plex_allow_sync":false,"sessions":0,"specific_libraries":[""],"unlimited":false,"used":false,"used_at":null,"used_by":null}} -{"event":"invitation_deleted","data":{"allow_download":true,"code":"RLMUQK","created":"2024-11-21 21:11:29.928042","duration":null,"expires":"2024-11-22 21:11:29.928034","hide_user":true,"id":1,"live_tv":true,"plex_allow_sync":false,"sessions":"0","specific_libraries":"","unlimited":false,"used":true,"used_at":"2024-11-21 13:30:35.743697","used_by":null}} -*/ +const emailFrom = process.env.MAIL_FROM; +const emailSubject = process.env.MAIL_SUBJECT; +const notify_templates = getNotifyTemplates(); +console.log("Listening for: " + Array.from(notify_templates.keys()).join(",")); + const pushover = new Pushover( { - user: process.env['PUSHOVER_USER'], - token: process.env['PUSHOVER_TOKEN'], + user: process.env.PUSHOVER_USER, + token: process.env.PUSHOVER_TOKEN, } ); -const notify_templates = new Map(); - -const templatesStat = fs.statSync("templates", {throwIfNoEntry: false}); -if (!templatesStat) fs.mkdirSync("templates"); - -if (fs.readdirSync("templates").length == 0) +const testEmailData = {username:"Test User"}; +function renderTemplateManual(template, data) { - for (let filename of fs.readdirSync("default_templates", {encoding:'utf8'})) - { - fs.copyFileSync(path.join("default_templates", filename), path.join("templates", filename)); - } + return mustache.render(fs.readFileSync(`views/${template}.html`, {'encoding':'utf8'}), data) } -for (let filename of fs.readdirSync("templates", {encoding:'utf8'})) +async function sendEmail(from, to, subject, html) { - const is_comment = l => l.trim().startsWith("//"); - if (path.extname(filename) != '.json') continue; + let transporter = nodemailer.createTransport({ + host: process.env.MAIL_HOST, + port: 587, // Common port for secure SMTP (TLS) + secure: false, // Use 'true' if using port 465 (SSL), 'false' for other ports like 587 (TLS) + requireTLS: true, + auth: { + user: process.env.SMTP_USER, + pass: process.env.SMTP_PASSWORD + } + }); - let lines = fs.readFileSync(path.join("templates", filename), {'encoding':'utf8'}).split(/[\r\n]/); - if (lines.some(l => is_comment(l) && l.indexOf(":ignore") != -1)) continue; - lines = lines.filter(l => !is_comment(l)); + //await transporter.verify(); - notify_templates.set(path.parse(filename).name, lines.join("\n")); + await transporter.sendMail({from, to, subject, html}); + + console.log(`Email sent to ${to}`); } -console.log("Listening for: " + Array.from(notify_templates.keys()).join(",")); - const app = express(); +// Register '.mustache' extension with The Mustache Express +app.engine('html', mustacheExpress()); +app.set('view engine', 'html'); +app.set('views', __dirname + '/views'); app.use(express.json()); -app.post('/', (req, res) => + + +app.get('/email.html', (req, res) => res.send(renderTemplateManual('email', testEmailData))); // res.render('email', testEmailData)); +app.get('/email_test', async (req, res) => { + await sendEmail(emailFrom, req.query.to, emailSubject, renderTemplateManual('email', testEmailData)); + res.send('sent'); +}); + +app.post('/', async (req, res, next) => +{ + console.log(req.body); + if ('event' in req.body) { - console.log(JSON.stringify(req.body)); - if (notify_templates.has(req.body.event)) { const template_output = JSON.parse(mustache.render(notify_templates.get(req.body.event), { - ...req.body.data, - user: `${req.body.data.username} (${req.body.data.email})` + ...req.body, + user: `${req.body.username} (${req.body.email})` })); if ('title' in template_output && 'message' in template_output) @@ -71,11 +84,53 @@ message: template_output.message }); } + + if (req.body.event == 'user_joined') + { + try + { + await sendEmail(emailFrom, req.body.email, emailSubject, renderTemplateManual('email', req.body)); + } + catch (err) + { + next(err); + } + } } } res.end(); }) -app.use((req, res) => res.status(404).end()); +app.use((_, res) => res.status(404).end()); app.listen(80); + +function getNotifyTemplates() +{ + const templates = new Map(); + + const templatesStat = fs.statSync("templates", {throwIfNoEntry: false}); + if (!templatesStat) fs.mkdirSync("templates"); + + if (fs.readdirSync("templates").length == 0) + { + for (let filename of fs.readdirSync("default_templates", {encoding:'utf8'})) + { + fs.copyFileSync(path.join("default_templates", filename), path.join("templates", filename)); + } + } + + for (let filename of fs.readdirSync("templates", {encoding:'utf8'})) + { + const is_comment = l => l.trim().startsWith("//"); + if (path.extname(filename) != '.json') continue; + + let lines = fs.readFileSync(path.join("templates", filename), {'encoding':'utf8'}).split(/[\r\n]/); + if (lines.some(l => is_comment(l) && l.indexOf(":ignore") != -1)) continue; + lines = lines.filter(l => !is_comment(l)); + + templates.set(path.parse(filename).name, lines.join("\n")); + } + + return templates; +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e8d4840..847e1b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,14 @@ "license": "ISC", "dependencies": { "express": "^4.21.1", + "moustache": "^0.0.4", "mustache": "^4.2.0", + "mustache-express": "^1.3.2", + "nodemailer": "^7.0.10", "pushover-notifications": "^1.2.3" + }, + "devDependencies": { + "nodemon": "^3.1.10" } }, "node_modules/accepts": { @@ -26,11 +32,47 @@ "node": ">= 0.6" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -54,6 +96,28 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -80,6 +144,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -199,9 +293,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -222,7 +316,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -237,6 +331,22 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, "node_modules/finalhandler": { @@ -272,6 +382,20 @@ "node": ">= 0.6" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -298,6 +422,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -309,6 +445,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -379,6 +524,12 @@ "node": ">=0.10.0" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -392,6 +543,56 @@ "node": ">= 0.10" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -446,6 +647,24 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/moustache": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/moustache/-/moustache-0.0.4.tgz", + "integrity": "sha512-9dSiwLbGR98lcHP/2HimKuRBgKC3/CXKtwy1bKFQ5kGOy3KIdynwbQyEZKN/Q57elo6yihV1FkVHsorA8E7nzg==", + "deprecated": "INVALID PACKAGE" + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -455,10 +674,24 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", "bin": { "mustache": "bin/mustache" } }, + "node_modules/mustache-express": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mustache-express/-/mustache-express-1.3.2.tgz", + "integrity": "sha512-yAdGHctEq9ubUH7h9O6Z6kW35fwfE+7LpW/TBrcfVibZuiVRHDcK8DEydgiU5nlJmJUY5VC3jv2lzaPUL+Arkw==", + "dependencies": { + "async": "~3.2.0", + "lru-cache": "~5.1.1", + "mustache": "^4.2.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -467,6 +700,74 @@ "node": ">= 0.6" } }, + "node_modules/nodemailer": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.10.tgz", + "integrity": "sha512-Us/Se1WtT0ylXgNFfyFSx4LElllVLJXQjWi2Xz17xWw7amDKO2MLtFnVp1WACy7GkVGs+oBlRopVNUzlrGSw1w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", @@ -498,9 +799,21 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -514,6 +827,12 @@ "node": ">= 0.10" } }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, "node_modules/pushover-notifications": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/pushover-notifications/-/pushover-notifications-1.2.3.tgz", @@ -558,6 +877,18 @@ "node": ">= 0.8" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -582,6 +913,18 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", @@ -670,6 +1013,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -678,6 +1033,30 @@ "node": ">= 0.8" } }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -686,6 +1065,15 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -698,6 +1086,12 @@ "node": ">= 0.6" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -721,6 +1115,11 @@ "engines": { "node": ">= 0.8" } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" } } } diff --git a/package.json b/package.json index 3b990ba..9d37a6a 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,16 @@ "license": "ISC", "dependencies": { "express": "^4.21.1", + "moustache": "^0.0.4", "mustache": "^4.2.0", + "mustache-express": "^1.3.2", + "nodemailer": "^7.0.10", "pushover-notifications": "^1.2.3" + }, + "devDependencies": { + "nodemon": "^3.1.10" + }, + "nodemonConfig": { + "ext": "js,html" } } diff --git a/set_test_env b/set_test_env new file mode 100644 index 0000000..679fa44 --- /dev/null +++ b/set_test_env @@ -0,0 +1,10 @@ +read -p"Mail host: " MAIL_HOST && export MAIL_HOST + +read -p"SMTP user: " SMTP_USER && export SMTP_USER +read -s -p"SMTP pwd: " SMTP_PASSWORD && export SMTP_PASSWORD + +read -p"FROM: " MAIL_FROM && export MAIL_FROM +read -p"Subject: " MAIL_SUBJECT && export MAIL_SUBJECT + +read -p"Pushover user: " PUSHOVER_USER && export PUSHOVER_USER +read -s -p"Pushover token: " PUSHOVER_TOKEN && export PUSHOVER_TOKEN diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..d207778 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,24 @@ +{ + "name": "Wizarr notifications node devcontainer", + "image": "mcr.microsoft.com/devcontainers/base:1-bookworm", + "features": { + "ghcr.io/devcontainers/features/node:1": { "version": "lts" } + }, + "workspaceFolder": "/workspace", + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", + "remoteUser": "vscode", + "forwardPorts": [], + "customizations": { + "vscode": { + "extensions": [ + "eg2.vscode-npm-script" + ], + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + } + } + }, + "runArgs": [ + "--network=bridge2", "--name=wizarr-notify-dev" + ] +} diff --git a/default_templates/user_deleted.json b/default_templates/user_deleted.json index bef021a..1bf097b 100644 --- a/default_templates/user_deleted.json +++ b/default_templates/user_deleted.json @@ -1,3 +1,4 @@ +// :ignore { "title": "User Deleted", "message": "User {{user}} deleted from server" diff --git a/default_templates/user_invited.json b/default_templates/user_invited.json deleted file mode 100644 index bca7ba6..0000000 --- a/default_templates/user_invited.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "User Invited", - "message": "User {{user}} accepted invit from code {{code}}" -} \ No newline at end of file diff --git a/default_templates/user_joined.json b/default_templates/user_joined.json new file mode 100644 index 0000000..28222a8 --- /dev/null +++ b/default_templates/user_joined.json @@ -0,0 +1,4 @@ +{ + "title": "User Joined", + "message": "User {{user}} accepted invite from code {{code}}" +} \ No newline at end of file diff --git a/docker_run_temp.sh b/docker_run_temp.sh deleted file mode 100755 index 2ca9841..0000000 --- a/docker_run_temp.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -docker run --name wizarr-notify -d --rm -it -v $(pwd):/opt/wizarr_notify -p:9991:80 "$@" node:latest sleep infinity diff --git a/index.js b/index.js index 1e44bbf..266af9e 100644 --- a/index.js +++ b/index.js @@ -1,67 +1,80 @@ const express = require('express'); const Pushover = require('pushover-notifications'); -const mustache = require('mustache'); const fs = require('node:fs'); const path = require('node:path'); +const nodemailer = require('nodemailer'); +const mustacheExpress = require('mustache-express'); +const mustache = require('mustache'); -/* -{"event":"user_invited","data":{"auth":"WHo3P_ZCFdnPyWFKJvKR","code":"RLMUQK","created":"2024-11-21 21:30:24.728194","email":"bavis.mark+plex2@gmail.com","expires":null,"id":3,"token":"449061671","username":"bavis.ma"}} -{"event":"user_deleted","data":{"auth":"WHo3P_ZCFdnPyWFKJvKR","code":"RLMUQK","created":"2024-11-21 21:30:24.728194","email":"bavis.mark+plex2@gmail.com","expires":null,"id":3,"token":"449061671","username":"bavis.ma"}} -{"event":"invitation_created","data":{"allow_download":true,"code":"RLMUQK","created":"2024-11-21 21:11:29.928042","duration":null,"expires":"2024-11-22 21:11:29.928034","hide_user":true,"id":1,"live_tv":true,"plex_allow_sync":false,"sessions":0,"specific_libraries":[""],"unlimited":false,"used":false,"used_at":null,"used_by":null}} -{"event":"invitation_deleted","data":{"allow_download":true,"code":"RLMUQK","created":"2024-11-21 21:11:29.928042","duration":null,"expires":"2024-11-22 21:11:29.928034","hide_user":true,"id":1,"live_tv":true,"plex_allow_sync":false,"sessions":"0","specific_libraries":"","unlimited":false,"used":true,"used_at":"2024-11-21 13:30:35.743697","used_by":null}} -*/ +const emailFrom = process.env.MAIL_FROM; +const emailSubject = process.env.MAIL_SUBJECT; +const notify_templates = getNotifyTemplates(); +console.log("Listening for: " + Array.from(notify_templates.keys()).join(",")); + const pushover = new Pushover( { - user: process.env['PUSHOVER_USER'], - token: process.env['PUSHOVER_TOKEN'], + user: process.env.PUSHOVER_USER, + token: process.env.PUSHOVER_TOKEN, } ); -const notify_templates = new Map(); - -const templatesStat = fs.statSync("templates", {throwIfNoEntry: false}); -if (!templatesStat) fs.mkdirSync("templates"); - -if (fs.readdirSync("templates").length == 0) +const testEmailData = {username:"Test User"}; +function renderTemplateManual(template, data) { - for (let filename of fs.readdirSync("default_templates", {encoding:'utf8'})) - { - fs.copyFileSync(path.join("default_templates", filename), path.join("templates", filename)); - } + return mustache.render(fs.readFileSync(`views/${template}.html`, {'encoding':'utf8'}), data) } -for (let filename of fs.readdirSync("templates", {encoding:'utf8'})) +async function sendEmail(from, to, subject, html) { - const is_comment = l => l.trim().startsWith("//"); - if (path.extname(filename) != '.json') continue; + let transporter = nodemailer.createTransport({ + host: process.env.MAIL_HOST, + port: 587, // Common port for secure SMTP (TLS) + secure: false, // Use 'true' if using port 465 (SSL), 'false' for other ports like 587 (TLS) + requireTLS: true, + auth: { + user: process.env.SMTP_USER, + pass: process.env.SMTP_PASSWORD + } + }); - let lines = fs.readFileSync(path.join("templates", filename), {'encoding':'utf8'}).split(/[\r\n]/); - if (lines.some(l => is_comment(l) && l.indexOf(":ignore") != -1)) continue; - lines = lines.filter(l => !is_comment(l)); + //await transporter.verify(); - notify_templates.set(path.parse(filename).name, lines.join("\n")); + await transporter.sendMail({from, to, subject, html}); + + console.log(`Email sent to ${to}`); } -console.log("Listening for: " + Array.from(notify_templates.keys()).join(",")); - const app = express(); +// Register '.mustache' extension with The Mustache Express +app.engine('html', mustacheExpress()); +app.set('view engine', 'html'); +app.set('views', __dirname + '/views'); app.use(express.json()); -app.post('/', (req, res) => + + +app.get('/email.html', (req, res) => res.send(renderTemplateManual('email', testEmailData))); // res.render('email', testEmailData)); +app.get('/email_test', async (req, res) => { + await sendEmail(emailFrom, req.query.to, emailSubject, renderTemplateManual('email', testEmailData)); + res.send('sent'); +}); + +app.post('/', async (req, res, next) => +{ + console.log(req.body); + if ('event' in req.body) { - console.log(JSON.stringify(req.body)); - if (notify_templates.has(req.body.event)) { const template_output = JSON.parse(mustache.render(notify_templates.get(req.body.event), { - ...req.body.data, - user: `${req.body.data.username} (${req.body.data.email})` + ...req.body, + user: `${req.body.username} (${req.body.email})` })); if ('title' in template_output && 'message' in template_output) @@ -71,11 +84,53 @@ message: template_output.message }); } + + if (req.body.event == 'user_joined') + { + try + { + await sendEmail(emailFrom, req.body.email, emailSubject, renderTemplateManual('email', req.body)); + } + catch (err) + { + next(err); + } + } } } res.end(); }) -app.use((req, res) => res.status(404).end()); +app.use((_, res) => res.status(404).end()); app.listen(80); + +function getNotifyTemplates() +{ + const templates = new Map(); + + const templatesStat = fs.statSync("templates", {throwIfNoEntry: false}); + if (!templatesStat) fs.mkdirSync("templates"); + + if (fs.readdirSync("templates").length == 0) + { + for (let filename of fs.readdirSync("default_templates", {encoding:'utf8'})) + { + fs.copyFileSync(path.join("default_templates", filename), path.join("templates", filename)); + } + } + + for (let filename of fs.readdirSync("templates", {encoding:'utf8'})) + { + const is_comment = l => l.trim().startsWith("//"); + if (path.extname(filename) != '.json') continue; + + let lines = fs.readFileSync(path.join("templates", filename), {'encoding':'utf8'}).split(/[\r\n]/); + if (lines.some(l => is_comment(l) && l.indexOf(":ignore") != -1)) continue; + lines = lines.filter(l => !is_comment(l)); + + templates.set(path.parse(filename).name, lines.join("\n")); + } + + return templates; +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e8d4840..847e1b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,14 @@ "license": "ISC", "dependencies": { "express": "^4.21.1", + "moustache": "^0.0.4", "mustache": "^4.2.0", + "mustache-express": "^1.3.2", + "nodemailer": "^7.0.10", "pushover-notifications": "^1.2.3" + }, + "devDependencies": { + "nodemon": "^3.1.10" } }, "node_modules/accepts": { @@ -26,11 +32,47 @@ "node": ">= 0.6" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -54,6 +96,28 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -80,6 +144,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -199,9 +293,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -222,7 +316,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -237,6 +331,22 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, "node_modules/finalhandler": { @@ -272,6 +382,20 @@ "node": ">= 0.6" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -298,6 +422,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -309,6 +445,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -379,6 +524,12 @@ "node": ">=0.10.0" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -392,6 +543,56 @@ "node": ">= 0.10" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -446,6 +647,24 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/moustache": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/moustache/-/moustache-0.0.4.tgz", + "integrity": "sha512-9dSiwLbGR98lcHP/2HimKuRBgKC3/CXKtwy1bKFQ5kGOy3KIdynwbQyEZKN/Q57elo6yihV1FkVHsorA8E7nzg==", + "deprecated": "INVALID PACKAGE" + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -455,10 +674,24 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", "bin": { "mustache": "bin/mustache" } }, + "node_modules/mustache-express": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mustache-express/-/mustache-express-1.3.2.tgz", + "integrity": "sha512-yAdGHctEq9ubUH7h9O6Z6kW35fwfE+7LpW/TBrcfVibZuiVRHDcK8DEydgiU5nlJmJUY5VC3jv2lzaPUL+Arkw==", + "dependencies": { + "async": "~3.2.0", + "lru-cache": "~5.1.1", + "mustache": "^4.2.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -467,6 +700,74 @@ "node": ">= 0.6" } }, + "node_modules/nodemailer": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.10.tgz", + "integrity": "sha512-Us/Se1WtT0ylXgNFfyFSx4LElllVLJXQjWi2Xz17xWw7amDKO2MLtFnVp1WACy7GkVGs+oBlRopVNUzlrGSw1w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", @@ -498,9 +799,21 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -514,6 +827,12 @@ "node": ">= 0.10" } }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, "node_modules/pushover-notifications": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/pushover-notifications/-/pushover-notifications-1.2.3.tgz", @@ -558,6 +877,18 @@ "node": ">= 0.8" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -582,6 +913,18 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", @@ -670,6 +1013,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -678,6 +1033,30 @@ "node": ">= 0.8" } }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -686,6 +1065,15 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -698,6 +1086,12 @@ "node": ">= 0.6" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -721,6 +1115,11 @@ "engines": { "node": ">= 0.8" } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" } } } diff --git a/package.json b/package.json index 3b990ba..9d37a6a 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,16 @@ "license": "ISC", "dependencies": { "express": "^4.21.1", + "moustache": "^0.0.4", "mustache": "^4.2.0", + "mustache-express": "^1.3.2", + "nodemailer": "^7.0.10", "pushover-notifications": "^1.2.3" + }, + "devDependencies": { + "nodemon": "^3.1.10" + }, + "nodemonConfig": { + "ext": "js,html" } } diff --git a/set_test_env b/set_test_env new file mode 100644 index 0000000..679fa44 --- /dev/null +++ b/set_test_env @@ -0,0 +1,10 @@ +read -p"Mail host: " MAIL_HOST && export MAIL_HOST + +read -p"SMTP user: " SMTP_USER && export SMTP_USER +read -s -p"SMTP pwd: " SMTP_PASSWORD && export SMTP_PASSWORD + +read -p"FROM: " MAIL_FROM && export MAIL_FROM +read -p"Subject: " MAIL_SUBJECT && export MAIL_SUBJECT + +read -p"Pushover user: " PUSHOVER_USER && export PUSHOVER_USER +read -s -p"Pushover token: " PUSHOVER_TOKEN && export PUSHOVER_TOKEN diff --git a/views/email.html b/views/email.html new file mode 100644 index 0000000..cadf24c --- /dev/null +++ b/views/email.html @@ -0,0 +1,56 @@ + + + + + Welcome {{username}} + + +
+

Welcome {{username}}!

+
+
+ +

🎬 Getting Started with Plex

+

Use your Plex account to sign into Mark’s private Plex server, accessible only to invited users. All streaming is privately managed by the server owner. You can install Plex on any device.

+ +
+ + Download Plex Apps + +
+ +

🎧 Requesting Content

+

+ Request new movies or TV at + https://request.plex.mhack.io, + or add items to your Plex watchlist. Both methods trigger automatic downloads and send you an email when your content is ready to watch! +

+ +
+ + Request Movies or TV + +
+ +

🎞 Tips for the Best Experience

+
    +
  1. Disable trailers & extras: Settings β†’ Experience β†’ uncheck Show cinematic trailers
  2. +
  3. Force original quality: Settings β†’ Playback β†’ set Automatic to Original
  4. +
  5. Prefer a wired connection on TVs for smooth 4K playback
  6. +
+ +

▢️ Open Plex Web

+
+ + Open Plex in Browser + +
+ +
+
+
+ +