diff --git a/data/websites.json b/data/websites.json new file mode 100644 index 0000000..d4a4503 --- /dev/null +++ b/data/websites.json @@ -0,0 +1,14 @@ +[ + { + "name": "Minecraft.net", + "url": "https://minecraft.net" + }, + { + "name": "Minecraft Textures", + "url": "https://textures.minecraft.net" + }, + { + "name": "Minecraft Session Server", + "url": "https://session.minecraft.net" + } +] diff --git a/package.json b/package.json index b6b674a..70e8dc4 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ "dependencies": { "@influxdata/influxdb-client": "^1.33.2", "@types/mcping-js": "^1.5.4", + "@types/node": "^20.10.6", "@types/node-cron": "^3.0.11", + "axios": "^1.6.4", "dns": "^0.2.2", "mcpe-ping-fixed": "^0.0.3", "mcping-js": "^1.5.0", @@ -22,7 +24,6 @@ "ts-node": "^10.9.2", "tsup": "^8.0.1", "typescript": "^5.3.3", - "winston": "^3.11.0", - "@types/node": "^20.10.6" + "winston": "^3.11.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff83ffc..0a84061 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ dependencies: '@types/node-cron': specifier: ^3.0.11 version: 3.0.11 + axios: + specifier: ^1.6.4 + version: 1.6.4 dns: specifier: ^0.2.2 version: 0.2.2 @@ -570,10 +573,24 @@ packages: resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} dev: false + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + /aws-sign@0.2.0: resolution: {integrity: sha512-6P7/Ls5F6++DsKu7iacris7qq/AZSWaX+gT4dtSyUxM82ePxWxaP7Slo82ZO3ZTx6GSKxQHAQlmFvM8e+Dd8ZA==} dev: false + /axios@1.6.4: + resolution: {integrity: sha512-heJnIs6N4aa1eSthhN9M5ioILu8Wi8vmQW9iHQ9NUvfkJb0lEEDUiIdQNAuBtfUt3FxReaKdpQA5DbmMOqzF/A==} + dependencies: + follow-redirects: 1.15.4 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: false @@ -762,6 +779,13 @@ packages: delayed-stream: 0.0.5 dev: false + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + /commander@0.6.1: resolution: {integrity: sha512-0fLycpl1UMTGX257hRsu/arL/cUbcvQM4zMKwvLvzXtfdezIV4yotPS2dYtknF+NmEfWSoCEF6+hj9XLm/6hEw==} engines: {node: '>= 0.4.x'} @@ -900,6 +924,11 @@ packages: engines: {node: '>=0.4.0'} dev: false + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + /depd@0.3.0: resolution: {integrity: sha512-Uyx3FgdvEYlpA3W4lf37Ide++2qOsjLlJ7dap0tbM63j/BxTCcxmyIOO6PXbKbOuNSko+fsDHzzx1DUeo1+3fA==} engines: {node: '>= 0.8.0'} @@ -1158,6 +1187,16 @@ packages: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} dev: false + /follow-redirects@1.15.4: + resolution: {integrity: sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + /foreground-child@3.1.1: resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} engines: {node: '>=14'} @@ -1179,6 +1218,15 @@ packages: mime: 1.2.11 dev: false + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + /fresh@0.2.2: resolution: {integrity: sha512-ZGGi8GROK//ijm2gB33sUuN9TjN1tC/dvG4Bt4j6IWrVGpMmudUBCxx+Ir7qePsdREfkpQC4FL8W0jeSOsgv1w==} dev: false @@ -1455,11 +1503,23 @@ packages: picomatch: 2.3.1 dev: false + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + /mime-types@1.0.2: resolution: {integrity: sha512-echfutj/t5SoTL4WZpqjA1DCud1XO0WQF3/GJ48YBmc4ZMhCK77QA6Z/w6VTQERLKuJ4drze3kw2TUT8xZXVNw==} engines: {node: '>= 0.8.0'} dev: false + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + /mime@1.2.11: resolution: {integrity: sha512-Ysa2F/nqTNGHhhm9MV8ure4+Hc+Y8AWiqUdHxsO7xu8zc92ND9f3kpALHjaP026Ft17UfxrMt95c50PLUeynBw==} dev: false @@ -1713,6 +1773,10 @@ packages: ipaddr.js: 0.1.2 dev: false + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false + /punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} diff --git a/src/index.ts b/src/index.ts index 78e0026..fa7554b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import Influx from "./influx/influx"; import ServerManager from "./server/serverManager"; +import WebsiteManager from "./website/websiteManager"; /** * The influx database instance. @@ -7,3 +8,4 @@ import ServerManager from "./server/serverManager"; export const influx = new Influx(); new ServerManager(); +new WebsiteManager(); diff --git a/src/server/serverManager.ts b/src/server/serverManager.ts index ea01ef9..54412b1 100644 --- a/src/server/serverManager.ts +++ b/src/server/serverManager.ts @@ -41,7 +41,7 @@ export default class ServerManager { // ping all servers in parallel await Promise.all(this.servers.map((server) => server.pingServer())); - logger.info("Finished pinging servers"); + logger.info("Finished pinging servers!"); } /** diff --git a/src/website/website.ts b/src/website/website.ts new file mode 100644 index 0000000..b932515 --- /dev/null +++ b/src/website/website.ts @@ -0,0 +1,64 @@ +import { Point } from "@influxdata/influxdb-client"; +import axios from "axios"; +import { influx } from ".."; +import { logger } from "../utils/logger"; + +import Config from "../../data/config.json"; + +type WebsiteOptions = { + name: string; + url: string; +}; + +export default class Website { + /** + * The name of the website. + */ + private name: string; + + /** + * The url of the website. + */ + private url: string; + + constructor({ name, url }: WebsiteOptions) { + this.name = name; + this.url = url; + } + + /** + * Pings a website and gets the response. + * + * @returns the response + */ + public async pingWebsite(): Promise { + try { + const before = Date.now(); + const response = await axios.get(this.url, { + validateStatus: () => true, // Don't throw a error on non-200 status codes + timeout: Config.pinger.timeout, + }); + if (response.status === 500) { + throw new Error("Server returned 500 status code"); + } + + const responseTime = Date.now() - before; + + influx.writePoint( + new Point("websiteStatus") + .tag("name", this.name) + .booleanField("online", true) + .intField("responseTime", responseTime) + .timestamp(Date.now()) + ); + } catch (err) { + logger.error(`Failed to ping ${this.name}:`, err); + influx.writePoint( + new Point("websiteStatus") + .tag("name", this.name) + .booleanField("online", false) + .timestamp(Date.now()) + ); + } + } +} diff --git a/src/website/websiteManager.ts b/src/website/websiteManager.ts new file mode 100644 index 0000000..f311f87 --- /dev/null +++ b/src/website/websiteManager.ts @@ -0,0 +1,47 @@ +import cron from "node-cron"; +import { logger } from "../utils/logger"; + +import Config from "../../data/config.json"; +import Websites from "../../data/websites.json"; +import Website from "./website"; + +export default class WebsiteManager { + private websites: Website[] = []; + + constructor() { + logger.info("Loading websites..."); + for (const configWebsite of Websites) { + const website = new Website({ + name: configWebsite.name, + url: configWebsite.url, + }); + this.websites.push(website); + } + logger.info(`Loaded ${this.websites.length} websites!`); + + cron.schedule(Config.pinger.pingCron, () => { + this.pingWebsites(); + }); + } + + /** + * Ping all websites to update their status. + */ + private async pingWebsites(): Promise { + logger.info(`Pinging websites ${this.websites.length}`); + + // ping all websites in parallel + await Promise.all(this.websites.map((website) => website.pingWebsite())); + + logger.info("Finished pinging websites!"); + } + + /** + * Returns the websites. + * + * @returns the websites + */ + public getWebsites(): Website[] { + return this.websites; + } +}