initial commit
This commit is contained in:
commit
ba64de5dab
14 changed files with 648 additions and 0 deletions
5
.dockerignore
Normal file
5
.dockerignore
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
node_modules/
|
||||||
|
.vscode/
|
||||||
|
*.md
|
||||||
|
.gitignore
|
||||||
|
config.json.example
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules/
|
||||||
|
yarn.lock
|
||||||
|
package-lock.json
|
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"runtimeVersion": "20",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "api",
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**"
|
||||||
|
],
|
||||||
|
"program": "${workspaceFolder}/fritzfix.js"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
21
Dockerfile
Normal file
21
Dockerfile
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
FROM node:current-alpine
|
||||||
|
|
||||||
|
LABEL version="1.0.0" \
|
||||||
|
author="Daniel Sommer <daniel.sommer@velvettear.de>" \
|
||||||
|
license="MIT"
|
||||||
|
|
||||||
|
MAINTAINER Daniel Sommer <daniel.sommer@velvettear.de>
|
||||||
|
|
||||||
|
ENV LANG=C.UTF-8
|
||||||
|
|
||||||
|
RUN mkdir -p /opt/fritzfix && chown -R node:node /opt/fritzfix
|
||||||
|
|
||||||
|
WORKDIR /opt/fritzfix
|
||||||
|
|
||||||
|
COPY --chown=node:node . .
|
||||||
|
|
||||||
|
USER node
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
ENTRYPOINT ["node", "fritzfix.js"]
|
20
LICENSE.md
Normal file
20
LICENSE.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# MIT License
|
||||||
|
**Copyright (c) 2024 Daniel Sommer \<daniel.sommer@velvettear.de\>**
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice (including the next
|
||||||
|
paragraph) shall be included in all copies or substantial portions of the
|
||||||
|
Software.
|
||||||
|
|
||||||
|
**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||||
|
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||||
|
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.**
|
21
README.md
Normal file
21
README.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# fritzfix
|
||||||
|
|
||||||
|
a custom tool for a very special, personal use case.
|
||||||
|
|
||||||
|
## description
|
||||||
|
|
||||||
|
every time the router goes down or reboots all the phones connected to a fritzbox stop working until the fritzbox itself is rebooted.
|
||||||
|
|
||||||
|
this tool regularly checks if the router is responding to http requests.
|
||||||
|
if the router does not respond the tool will wait for it to come back online and then execute a reboot via selenium (headless browser in a docker container).
|
||||||
|
|
||||||
|
also some notifications are sent to my local ntfy instance.
|
||||||
|
|
||||||
|
## build / usage
|
||||||
|
|
||||||
|
- clone the repository: `git clone https://git.velvettear.de/velvettear/fritzfix.git`
|
||||||
|
- enter the repository: `cd fritzfix`
|
||||||
|
- create and modify a config file: `cp config.json.example config.json && vim config.json`
|
||||||
|
- build an image: `docker build --no-cache docker.registry.velvettear.de/fritzfix:latest .`
|
||||||
|
- push the image: `docker push docker.registry.velvettear.de/fritzfix:latest`
|
||||||
|
- run it: `docker run --rm -it docker.registry.velvettear.de/fritzfix:latest`
|
24
config.json.example
Normal file
24
config.json.example
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"fritzbox": {
|
||||||
|
"url": "http://192.168.1.1",
|
||||||
|
"user": "admin",
|
||||||
|
"password": "secretPassword"
|
||||||
|
},
|
||||||
|
"draytek": "http://192.168.178.1",
|
||||||
|
"selenium": "http://192.168.1.2:4444",
|
||||||
|
"notify": {
|
||||||
|
"url": "http://192.168.178.2:7000/fritzfix",
|
||||||
|
"user": "ntfyUser",
|
||||||
|
"password": "ntfyPassword"
|
||||||
|
},
|
||||||
|
"timeouts": {
|
||||||
|
"request": 10000,
|
||||||
|
"webui": 30000,
|
||||||
|
"reboot": 3000000
|
||||||
|
},
|
||||||
|
"interval": 10000,
|
||||||
|
"log": {
|
||||||
|
"level": "debug",
|
||||||
|
"timestamp": "DD.MM.YYYY HH:mm:ss:SS"
|
||||||
|
}
|
||||||
|
}
|
116
fritzfix.js
Normal file
116
fritzfix.js
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
const packageJSON = require("./package.json");
|
||||||
|
const configJSON = require("./config.json");
|
||||||
|
const logger = require("./libs/logger.js");
|
||||||
|
const fritzbox = require("./libs/fritzbox.js");
|
||||||
|
const draytek = require("./libs/draytek.js");
|
||||||
|
const notification = require("./libs/notify.js");
|
||||||
|
const timediff = require("./libs/timediff.js");
|
||||||
|
|
||||||
|
const INTERRUPTS = ["beforeExit", "SIGINT", "SIGTERM"];
|
||||||
|
|
||||||
|
global.appName = packageJSON.name;
|
||||||
|
global.appVersion = packageJSON.version;
|
||||||
|
global.config = configJSON;
|
||||||
|
|
||||||
|
let offlineTimestamp;
|
||||||
|
let rebootError;
|
||||||
|
|
||||||
|
main();
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
handleExit();
|
||||||
|
try {
|
||||||
|
logger.initialize(configJSON);
|
||||||
|
logger.info(
|
||||||
|
"launching " + global.appName + " " + global.appVersion + "..."
|
||||||
|
);
|
||||||
|
mainLoop();
|
||||||
|
} catch (err) {
|
||||||
|
await exit(1, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function mainLoop() {
|
||||||
|
const isOnline = await draytek.isOnline();
|
||||||
|
if (!isOnline) {
|
||||||
|
if (offlineTimestamp) {
|
||||||
|
logger.warn(
|
||||||
|
"draytek seems to be still not responding after " +
|
||||||
|
timediff.inSeconds(offlineTimestamp) +
|
||||||
|
" seconds!"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
offlineTimestamp = Date.now();
|
||||||
|
const msg = "draytek seems to be not responding!";
|
||||||
|
logger.warn(msg);
|
||||||
|
await notification.send(
|
||||||
|
"draytek offline!",
|
||||||
|
msg + "\nwaiting for it to come back online...",
|
||||||
|
"max",
|
||||||
|
"scream"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (offlineTimestamp) {
|
||||||
|
const msg =
|
||||||
|
"draytek is responding again after " +
|
||||||
|
timediff.inSeconds(offlineTimestamp) +
|
||||||
|
" seconds!";
|
||||||
|
logger.info(msg);
|
||||||
|
if (!rebootError) {
|
||||||
|
notification.send(
|
||||||
|
"rebooting fritzbox...",
|
||||||
|
msg + "\nrebooting fritzbox now...",
|
||||||
|
undefined,
|
||||||
|
"roll_eyes"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const err = await fritzbox.reboot();
|
||||||
|
if (err) {
|
||||||
|
rebootError = err;
|
||||||
|
notification.send("error rebooting fritzbox!", err, "max", "sob");
|
||||||
|
} else {
|
||||||
|
notification.send(
|
||||||
|
"fritzbox rebooted!",
|
||||||
|
"successfully rebooted device, the phones should now work again!",
|
||||||
|
"max",
|
||||||
|
"partying_face"
|
||||||
|
);
|
||||||
|
offlineTimestamp = undefined;
|
||||||
|
rebootError = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
mainLoop();
|
||||||
|
}, configJSON.interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleExit() {
|
||||||
|
for (var index = 0; index < INTERRUPTS.length; index++) {
|
||||||
|
process.on(INTERRUPTS[index], async (code) => {
|
||||||
|
await exit(code);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function exit(code, err) {
|
||||||
|
await fritzbox.quit();
|
||||||
|
if (code === undefined) {
|
||||||
|
code = 0;
|
||||||
|
if (err) {
|
||||||
|
code = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
logger.error(err);
|
||||||
|
logger.error(
|
||||||
|
global.appName + " " + global.appVersion + " ended due to an error"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logger.info(
|
||||||
|
global.appName + " " + global.appVersion + " shutting down gracefully"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
process.exit(code);
|
||||||
|
}
|
45
libs/draytek.js
Normal file
45
libs/draytek.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
const logger = require("./logger.js");
|
||||||
|
const timediff = require("./timediff.js");
|
||||||
|
|
||||||
|
async function isOnline() {
|
||||||
|
const url = global.config.draytek;
|
||||||
|
let online = false;
|
||||||
|
logger.info("checking if draytek is responding on url '" + url + "'...");
|
||||||
|
const timestamp = Date.now();
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(async function () {
|
||||||
|
logger.debug(
|
||||||
|
"request to draytek timed out after " +
|
||||||
|
timediff.inSeconds(timestamp) +
|
||||||
|
" seconds!"
|
||||||
|
);
|
||||||
|
controller.abort();
|
||||||
|
}, global.config.timeouts.request);
|
||||||
|
|
||||||
|
const result = await fetch(url, {
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
logger.info(
|
||||||
|
"draytek responded with status code '" +
|
||||||
|
result.status +
|
||||||
|
"' after " +
|
||||||
|
timediff.inSeconds(timestamp) +
|
||||||
|
" seconds!"
|
||||||
|
);
|
||||||
|
online = result.status === 200;
|
||||||
|
} catch (err) {
|
||||||
|
if (err.name !== "AbortError") {
|
||||||
|
logger.error(
|
||||||
|
"encountered an error checking if draytek is responding (" + err + ")"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return online;
|
||||||
|
}
|
||||||
|
|
||||||
|
// exports
|
||||||
|
module.exports = {
|
||||||
|
isOnline,
|
||||||
|
};
|
153
libs/fritzbox.js
Normal file
153
libs/fritzbox.js
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
const logger = require("./logger.js");
|
||||||
|
const timediff = require("./timediff.js");
|
||||||
|
const selenium = require("selenium-webdriver");
|
||||||
|
const firefox = require("selenium-webdriver/firefox");
|
||||||
|
|
||||||
|
let webdriver;
|
||||||
|
let defaultTimeout;
|
||||||
|
let rebootTimeout;
|
||||||
|
|
||||||
|
async function initializeDriver() {
|
||||||
|
logger.debug("initializing the webdriver...");
|
||||||
|
defaultTimeout = global.config.timeouts.webui;
|
||||||
|
rebootTimeout = global.config.timeouts.reboot;
|
||||||
|
let seleniumUrl = global.config.selenium;
|
||||||
|
if (!seleniumUrl.endsWith("/")) {
|
||||||
|
seleniumUrl += "/";
|
||||||
|
}
|
||||||
|
webdriver = await new selenium.Builder()
|
||||||
|
.forBrowser(selenium.Browser.FIREFOX)
|
||||||
|
.usingServer(seleniumUrl + "wd/hub")
|
||||||
|
.setFirefoxOptions(
|
||||||
|
new firefox.Options().addArguments(
|
||||||
|
"--headless",
|
||||||
|
"--private",
|
||||||
|
"--purgecaches"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.withCapabilities(
|
||||||
|
selenium.Capabilities.firefox().set("acceptInsecureCerts", true)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
await webdriver
|
||||||
|
.manage()
|
||||||
|
.setTimeouts({
|
||||||
|
// implicit: defaultTimeout,
|
||||||
|
pageLoad: defaultTimeout,
|
||||||
|
// script: defaultTimeout,
|
||||||
|
});
|
||||||
|
logger.debug("webdriver was successfully initialized!");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function login() {
|
||||||
|
logger.debug("opening url '" + global.config.fritzbox.url + "'...");
|
||||||
|
await webdriver.get(global.config.fritzbox.url);
|
||||||
|
logger.debug("entering credentials...");
|
||||||
|
try {
|
||||||
|
const selection = new selenium.Select(
|
||||||
|
await webdriver.findElement(selenium.By.id("uiViewUser"))
|
||||||
|
);
|
||||||
|
await selection.selectByValue(global.config.fritzbox.user);
|
||||||
|
logger.debug("set specified username");
|
||||||
|
} catch (ignored) {}
|
||||||
|
await webdriver
|
||||||
|
.findElement(selenium.By.id("uiPass"))
|
||||||
|
.sendKeys(global.config.fritzbox.password, selenium.Key.RETURN);
|
||||||
|
logger.debug("set specified password");
|
||||||
|
logger.info("logging in...");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reboot() {
|
||||||
|
logger.info("initializing reboot...");
|
||||||
|
try {
|
||||||
|
await initializeDriver();
|
||||||
|
await login();
|
||||||
|
await clickElementById("sys");
|
||||||
|
await clickElementById("mSave");
|
||||||
|
await clickElementById("reboot");
|
||||||
|
await clickElementByName("reboot");
|
||||||
|
|
||||||
|
await webdriver
|
||||||
|
.wait(
|
||||||
|
selenium.until.elementLocated(selenium.By.className("waitimg")),
|
||||||
|
defaultTimeout
|
||||||
|
)
|
||||||
|
.then(function () {
|
||||||
|
logger.debug("reboot has been initiated via web ui");
|
||||||
|
});
|
||||||
|
|
||||||
|
await webdriver
|
||||||
|
.wait(
|
||||||
|
selenium.until.elementLocated(selenium.By.className("waitimg")),
|
||||||
|
defaultTimeout
|
||||||
|
)
|
||||||
|
.then(function () {
|
||||||
|
logger.info("fritzbox is rebooting now...");
|
||||||
|
});
|
||||||
|
|
||||||
|
const timestamp = Date.now();
|
||||||
|
await webdriver
|
||||||
|
.wait(
|
||||||
|
selenium.until.elementLocated(selenium.By.id("loginForm")),
|
||||||
|
rebootTimeout
|
||||||
|
)
|
||||||
|
.then(function () {
|
||||||
|
logger.info(
|
||||||
|
"fritzbox has been successfully rebooted after " +
|
||||||
|
timediff.inSeconds(timestamp) +
|
||||||
|
" seconds!"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
logger.error("encountered an error executing the reboot (" + err + ")");
|
||||||
|
return err;
|
||||||
|
} finally {
|
||||||
|
await quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clickElementById(element) {
|
||||||
|
if (element === undefined || element.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug("waiting for element with id '" + element + "'...");
|
||||||
|
await webdriver.wait(
|
||||||
|
selenium.until.elementLocated(selenium.By.id(element)),
|
||||||
|
defaultTimeout
|
||||||
|
);
|
||||||
|
logger.debug("clicking on element with id '" + element + "'...");
|
||||||
|
await webdriver.findElement(selenium.By.id(element)).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clickElementByName(element) {
|
||||||
|
if (element === undefined || element.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug("waiting for element with name '" + element + "'...");
|
||||||
|
await webdriver.wait(
|
||||||
|
selenium.until.elementLocated(selenium.By.name(element)),
|
||||||
|
defaultTimeout
|
||||||
|
);
|
||||||
|
logger.debug("clicking on element with name '" + element + "'...");
|
||||||
|
await webdriver.findElement(selenium.By.name(element)).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function quit() {
|
||||||
|
if (webdriver === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
logger.debug("closing the webdriver...");
|
||||||
|
webdriver.quit();
|
||||||
|
logger.debug("webdriver was successfully closed!");
|
||||||
|
webdriver = undefined;
|
||||||
|
} catch (err) {
|
||||||
|
logger.error("encountered an error closing the webdriver (" + err + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// exports
|
||||||
|
module.exports = {
|
||||||
|
reboot,
|
||||||
|
quit,
|
||||||
|
};
|
137
libs/logger.js
Normal file
137
libs/logger.js
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
const moment = require('moment');
|
||||||
|
|
||||||
|
// constants
|
||||||
|
const LOG_PREFIX_DEBUG = 'debug';
|
||||||
|
const LOG_PREFIX_INFO = 'info';
|
||||||
|
const LOG_PREFIX_WARNING = 'warning';
|
||||||
|
const LOG_PREFIX_ERROR = 'error';
|
||||||
|
const LOGLEVEL_DEBUG = 0;
|
||||||
|
const LOGLEVEL_INFO = 1;
|
||||||
|
const LOGLEVEL_WARNING = 2;
|
||||||
|
const LOGLEVEL_ERROR = 3;
|
||||||
|
|
||||||
|
var loglevel = getLogLevel();
|
||||||
|
var timestamp = getTimestamp();
|
||||||
|
|
||||||
|
function initialize() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (global.config == undefined) {
|
||||||
|
reject('could not initialize logger, config is undefined');
|
||||||
|
}
|
||||||
|
loglevel = getLogLevel();
|
||||||
|
timestamp = getTimestamp();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the loglevel
|
||||||
|
function getLogLevel() {
|
||||||
|
if (global.config?.log?.level == undefined) {
|
||||||
|
return LOGLEVEL_INFO;
|
||||||
|
}
|
||||||
|
switch (global.config.log.level) {
|
||||||
|
case LOG_PREFIX_DEBUG:
|
||||||
|
case LOGLEVEL_DEBUG:
|
||||||
|
return LOGLEVEL_DEBUG;
|
||||||
|
case LOG_PREFIX_INFO:
|
||||||
|
case LOGLEVEL_INFO:
|
||||||
|
return LOGLEVEL_INFO;
|
||||||
|
case LOG_PREFIX_WARNING:
|
||||||
|
case LOGLEVEL_WARNING:
|
||||||
|
return LOGLEVEL_WARNING;
|
||||||
|
case LOG_PREFIX_ERROR:
|
||||||
|
case LOGLEVEL_ERROR:
|
||||||
|
return LOGLEVEL_ERROR;
|
||||||
|
default:
|
||||||
|
return LOGLEVEL_INFO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the timestamp format
|
||||||
|
function getTimestamp() {
|
||||||
|
if (global.config?.log?.format != undefined) {
|
||||||
|
return global.config.log.format;
|
||||||
|
}
|
||||||
|
return "DD.MM.YYYY HH:mm:ss:SS";
|
||||||
|
}
|
||||||
|
|
||||||
|
// prefix log with 'info'
|
||||||
|
function info(message) {
|
||||||
|
if (loglevel > LOGLEVEL_INFO) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
trace(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// prefix log with 'info'
|
||||||
|
function warn(message) {
|
||||||
|
if (loglevel > LOGLEVEL_WARNING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
trace(message, 'warning');
|
||||||
|
}
|
||||||
|
|
||||||
|
// prefix log with 'debug'
|
||||||
|
function debug(message) {
|
||||||
|
if (loglevel > LOGLEVEL_DEBUG) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
trace(message, 'debug');
|
||||||
|
}
|
||||||
|
|
||||||
|
// prefix log with 'error'
|
||||||
|
function error(message) {
|
||||||
|
if (loglevel > LOGLEVEL_ERROR) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (message.stack) {
|
||||||
|
trace(message.stack, 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (message.errors !== undefined) {
|
||||||
|
for (let index = 0; index < message.errors.length; index++) {
|
||||||
|
trace(message.errors[index], 'error');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (message.message) {
|
||||||
|
trace(message.message, 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
trace(message, 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
// default logging function
|
||||||
|
function trace(message, prefix) {
|
||||||
|
if (message === undefined || message === null || message.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (prefix === undefined || prefix === null || prefix.length === 0) {
|
||||||
|
prefix = 'info';
|
||||||
|
}
|
||||||
|
let print;
|
||||||
|
switch (prefix) {
|
||||||
|
case 'error':
|
||||||
|
print = console.error;
|
||||||
|
break;
|
||||||
|
case 'debug':
|
||||||
|
print = console.debug;
|
||||||
|
break;
|
||||||
|
case 'warning':
|
||||||
|
print = console.warn;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
print = console.log;
|
||||||
|
}
|
||||||
|
message = moment().format(timestamp) + ' | ' + prefix + ' > ' + message;
|
||||||
|
print(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// exports
|
||||||
|
module.exports = {
|
||||||
|
initialize,
|
||||||
|
info,
|
||||||
|
warn,
|
||||||
|
debug,
|
||||||
|
error
|
||||||
|
};
|
50
libs/notify.js
Normal file
50
libs/notify.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
const logger = require("./logger.js");
|
||||||
|
|
||||||
|
async function send(title, message, priority, ...tags) {
|
||||||
|
if (title?.length === 0 || message?.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = global.config.notify.url;
|
||||||
|
if (url?.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const timestamp = Date.now();
|
||||||
|
logger.debug("sending notification to '" + url + "'...");
|
||||||
|
const headers = new Headers();
|
||||||
|
headers.set("Authorization", "Basic " +
|
||||||
|
Buffer.from(
|
||||||
|
global.config.notify.user + ":" + global.config.notify.password
|
||||||
|
).toString("base64"));
|
||||||
|
headers.set("Title", title);
|
||||||
|
if (priority) {
|
||||||
|
headers.set("Priority", priority);
|
||||||
|
}
|
||||||
|
if (tags) {
|
||||||
|
headers.set("Tags", tags);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const result = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: headers,
|
||||||
|
body: message,
|
||||||
|
});
|
||||||
|
if (result.status !== 200) {
|
||||||
|
logger.warn(
|
||||||
|
"notification could not be sent (status code: " + result.status + ")"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug(
|
||||||
|
"successfully sent notification after " +
|
||||||
|
(Date.now() - timestamp) / 1000 +
|
||||||
|
" seconds!"
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error("encountered an error sending notification (" + err + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// exports
|
||||||
|
module.exports = {
|
||||||
|
send,
|
||||||
|
};
|
16
libs/timediff.js
Normal file
16
libs/timediff.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
function inMilliseconds(timestamp) {
|
||||||
|
if (!timestamp) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Date.now() - timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function inSeconds(timestamp) {
|
||||||
|
return inMilliseconds(timestamp) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// exports
|
||||||
|
module.exports = {
|
||||||
|
inMilliseconds,
|
||||||
|
inSeconds
|
||||||
|
}
|
22
package.json
Normal file
22
package.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "fritzfix",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"main": "fritzfix.js",
|
||||||
|
"scripts": {},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.velvettear.de/velvettear/fritzfix.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"fritzbox",
|
||||||
|
"remote control"
|
||||||
|
],
|
||||||
|
"author": "Daniel Sommer <daniel.sommer@velvettear.de>",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"moment": "^2.30.1",
|
||||||
|
"selenium-webdriver": "^4.21.0",
|
||||||
|
"ws": "^8.5.0"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue