diff --git a/package-lock.json b/package-lock.json index 2bfd348f..98222abe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "AGPL-3.0", "dependencies": { "@sentry/node": "^6.19.2", + "@snyk/protect": "^1.984.0", "body-parser": "^1.19.2", "browser-detect": "^0.2.28", "chunk": "0.0.3", @@ -418,6 +419,17 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, + "node_modules/@snyk/protect": { + "version": "1.984.0", + "resolved": "https://registry.npmjs.org/@snyk/protect/-/protect-1.984.0.tgz", + "integrity": "sha512-R+XQotaoMVBBkAfzDQqO/hcUYlMHKjxXNjeMhlvSjnD4cR8IOrC+XTPyAO2l5v7L81wBMkKjCJ10YQwB6xYISg==", + "bin": { + "snyk-protect": "bin/snyk-protect" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -1828,13 +1840,13 @@ "dev": true }, "node_modules/ejs-lint": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ejs-lint/-/ejs-lint-1.2.1.tgz", - "integrity": "sha512-pHN8kjh8a4HxeG8FKl4+8wgiX/h7fiZK9r56hK2KA9xJ2a+736+4iEfEYw6uHkTy0rXdVGDSQFqpiFqG80TtKg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ejs-lint/-/ejs-lint-1.2.2.tgz", + "integrity": "sha512-ESR/MePvJJJfkK3EUAYlPKe2JM2nRDc4uFkGgbB5Prr06nluN7JozNVFL3Ze7LV7xNY7JPWEi5H3i4hOl6mxXw==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "ejs": "3.1.6", + "ejs": "3.1.7", "ejs-include-regex": "^1.0.0", "globby": "^11.0.0", "read-input": "^0.3.1", @@ -1846,21 +1858,6 @@ "ejslint": "cli.js" } }, - "node_modules/ejs-lint/node_modules/ejs": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz", - "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==", - "dev": true, - "dependencies": { - "jake": "^10.6.1" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -3453,9 +3450,9 @@ } }, "node_modules/moment": { - "version": "2.29.2", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz", - "integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==", + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", "engines": { "node": "*" } @@ -3595,9 +3592,9 @@ } }, "node_modules/node-fetch": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.3.tgz", - "integrity": "sha512-AXP18u4pidSZ1xYXRDPY/8jdv3RAozIt/WLNR/MBGZAz+xjtlr90RvCnsvHQRiXyWliZF/CpytExp32UU67/SA==", + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", + "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", @@ -3896,9 +3893,9 @@ } }, "node_modules/passport": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/passport/-/passport-0.5.2.tgz", - "integrity": "sha512-w9n/Ot5I7orGD4y+7V3EFJCQEznE5RxHamUxcqLT2QoJY0f2JdN8GyHonYFvN0Vz+L6lUJfVhrk2aZz2LbuREw==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.5.3.tgz", + "integrity": "sha512-gGc+70h4gGdBWNsR3FuV3byLDY6KBTJAIExGFXTpQaYfbbcHCBlRRKx7RBQSpqEqc5Hh2qVzRs7ssvSfOpkUEA==", "dependencies": { "passport-strategy": "1.x.x", "pause": "0.0.1" @@ -5764,6 +5761,11 @@ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==" }, + "@snyk/protect": { + "version": "1.984.0", + "resolved": "https://registry.npmjs.org/@snyk/protect/-/protect-1.984.0.tgz", + "integrity": "sha512-R+XQotaoMVBBkAfzDQqO/hcUYlMHKjxXNjeMhlvSjnD4cR8IOrC+XTPyAO2l5v7L81wBMkKjCJ10YQwB6xYISg==" + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -6913,30 +6915,19 @@ "dev": true }, "ejs-lint": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ejs-lint/-/ejs-lint-1.2.1.tgz", - "integrity": "sha512-pHN8kjh8a4HxeG8FKl4+8wgiX/h7fiZK9r56hK2KA9xJ2a+736+4iEfEYw6uHkTy0rXdVGDSQFqpiFqG80TtKg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ejs-lint/-/ejs-lint-1.2.2.tgz", + "integrity": "sha512-ESR/MePvJJJfkK3EUAYlPKe2JM2nRDc4uFkGgbB5Prr06nluN7JozNVFL3Ze7LV7xNY7JPWEi5H3i4hOl6mxXw==", "dev": true, "requires": { "chalk": "^4.0.0", - "ejs": "3.1.6", + "ejs": "3.1.7", "ejs-include-regex": "^1.0.0", "globby": "^11.0.0", "read-input": "^0.3.1", "slash": "^3.0.0", "syntax-error": "^1.1.6", "yargs": "^16.0.0" - }, - "dependencies": { - "ejs": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz", - "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==", - "dev": true, - "requires": { - "jake": "^10.6.1" - } - } } }, "emoji-regex": { @@ -8173,9 +8164,9 @@ "dev": true }, "moment": { - "version": "2.29.2", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz", - "integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==" + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, "mongodb": { "version": "4.5.0", @@ -8277,9 +8268,9 @@ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" }, "node-fetch": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.3.tgz", - "integrity": "sha512-AXP18u4pidSZ1xYXRDPY/8jdv3RAozIt/WLNR/MBGZAz+xjtlr90RvCnsvHQRiXyWliZF/CpytExp32UU67/SA==", + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz", + "integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==", "requires": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", @@ -8500,9 +8491,9 @@ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "passport": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/passport/-/passport-0.5.2.tgz", - "integrity": "sha512-w9n/Ot5I7orGD4y+7V3EFJCQEznE5RxHamUxcqLT2QoJY0f2JdN8GyHonYFvN0Vz+L6lUJfVhrk2aZz2LbuREw==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.5.3.tgz", + "integrity": "sha512-gGc+70h4gGdBWNsR3FuV3byLDY6KBTJAIExGFXTpQaYfbbcHCBlRRKx7RBQSpqEqc5Hh2qVzRs7ssvSfOpkUEA==", "requires": { "passport-strategy": "1.x.x", "pause": "0.0.1" diff --git a/package.json b/package.json index 986002a7..f8c9523d 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@sentry/node": "^6.19.2", + "@snyk/protect": "^1.984.0", "body-parser": "^1.19.2", "browser-detect": "^0.2.28", "chunk": "0.0.3", @@ -72,15 +73,15 @@ }, "scripts": { "compile": "rm -rf dist/ && tsc", - "start": "node --max-old-space-size=8192 dist/src/app", + "start": "node --max-old-space-size=4096 dist/src/app", "pm2": "pm2 start dist/src/app.js --name 'DEL'", "css-compile": "sh ./scripts/css_build.sh", "setup": "node scripts/setup", "snyk-protect": "snyk-protect", "prepare": "npm run snyk-protect", "dev": "nodemon --exec \"tsc && fuser -k 3001/tcp && node dist/src/app.js\" --ext ts", - "update": "npm run compile && pm2 restart 1", - "updatel": "npm run compile && pm2 restart 1 && pm2 logs 1" + "update": "npm run compile && pm2 restart DEL", + "updatel": "npm run compile && pm2 restart DEL && pm2 logs DEL" }, "repository": { "type": "git", diff --git a/src/Routes/autosync.ts b/src/Routes/autosync.ts index cf7d810f..ec539cbd 100644 --- a/src/Routes/autosync.ts +++ b/src/Routes/autosync.ts @@ -25,6 +25,8 @@ import * as botCache from "../Util/Services/botCaching.js"; import * as serverCache from "../Util/Services/serverCaching.js"; import * as templateCache from "../Util/Services/templateCaching.js"; import * as userCache from "../Util/Services/userCaching.js"; +import * as functions from "../Util/Function/main.js"; +import { MessageEmbed } from "discord.js"; import { APIInvite, APITemplate, RESTGetAPIInviteQuery, RESTPostOAuth2AccessTokenResult, APIApplicationCommand, OAuth2Scopes, Routes, APIApplication, APIUser } from "discord-api-types/v10"; import settings from "../../settings.json" assert { type: "json" }; @@ -46,7 +48,7 @@ router.get('/bots', async (req, res) => { const ids = (await global.redis?.hkeys("bots")).sort() if (!ids) return res.sendStatus(503) - + if (!id) id = ids[0] const botExists: delBot = await global.db @@ -57,7 +59,7 @@ router.get('/bots', async (req, res) => { const app = await discord.bot.api.applications(botExists.clientID || id).rpc.get() as APIApplication let commands: APIApplicationCommand[] = botExists.commands || [] - + if (botExists.scopes?.slashCommands) { const owner = await userCache.getUser(botExists.owner.id) @@ -72,7 +74,7 @@ router.get('/bots', async (req, res) => { auth: { accessToken, refreshToken, - expires: Date.now() + result.expires_in*1000 + expires: Date.now() + result.expires_in * 1000 } } } @@ -81,8 +83,8 @@ router.get('/bots', async (req, res) => { } }) } - - const receivedCommands = await (await fetch('https://discord.com/api/v8'+Routes.applicationCommands(app.id), {headers: {authorization: `Bearer ${owner.auth.accessToken}`}})).json().catch(() => {}) as APIApplicationCommand[] + + const receivedCommands = await (await fetch('https://discord.com/api/v8' + Routes.applicationCommands(app.id), { headers: { authorization: `Bearer ${owner.auth.accessToken}` } })).json().catch(() => { }) as APIApplicationCommand[] if (Array.isArray(receivedCommands)) commands = receivedCommands; } } @@ -90,10 +92,10 @@ router.get('/bots', async (req, res) => { let userFlags = 0 if (botExists.scopes?.bot) { - const user = await discord.bot.api.users(id).get().catch(() => {}) as APIUser + const user = await discord.bot.api.users(id).get().catch(() => { }) as APIUser if (user.public_flags) userFlags = user.public_flags } - + await global.db.collection("bots").updateOne( { _id: id }, { @@ -126,7 +128,7 @@ router.get('/servers', async (req, res) => { const ids = (await global.redis?.hkeys("servers")).sort() if (!ids) return res.sendStatus(503) - + if (!id) id = ids[0] const server: delServer = await global.db @@ -134,12 +136,11 @@ router.get('/servers', async (req, res) => { .findOne({ _id: id }); if (server) try { - const invite = await discord.bot.api.invites(server.inviteCode).get({query: {with_counts: true, with_expiration: true} as RESTGetAPIInviteQuery}) as APIInvite - + const invite = await discord.bot.api.invites(server.inviteCode).get({ query: { with_counts: true, with_expiration: true } as RESTGetAPIInviteQuery }) as APIInvite if (invite.guild.id !== server._id) throw 'Invite points to a different server' if (invite.expires_at) throw 'This invite is set to expire' - + await global.db.collection("servers").updateOne( { _id: id }, { @@ -159,7 +160,47 @@ router.get('/servers', async (req, res) => { await serverCache.updateServer(id); } catch (e) { - discord.channels.alerts.send(`${settings.emoji.warn} failed to autosync server **${server.name}** \`(${id})\`: ${e}\n<${settings.website.url}/servers/${id}>`) + if (e.code != 10006) return; // https://discord.com/developers/docs/topics/opcodes-and-status-codes#json + await global.db.collection("servers").deleteOne({ _id: id }); + await global.db.collection("audit").insertOne({ + type: "REMOVE_SERVER", + executor: "AutoSync", + target: id, + date: Date.now(), + reason: "Failed to autosync server, assuming the invite is invalid.", + reasonType: 5 + }); + + await serverCache.deleteServer(id); + + const embed = new MessageEmbed(); + embed.setColor(0x2f3136); + embed.setTitle("Reason"); + embed.setDescription(req.body.reason); + + discord.channels.logs.send({ + content: `${settings.emoji.delete} **AutoSync System** removed server **${functions.escapeFormatting( + server.name + )}** \`(${server._id})\``, + embeds: [embed] + }); + + const owner = await discord.getMember(server.owner.id); + if (owner) + owner + .send( + `${settings.emoji.delete + } **|** Your server **${functions.escapeFormatting( + server.name + )}** \`(${server._id})\` has been removed!\n**Reason:** \`Our AutoSync system has determined this server has either been deleted, or the invite provided to us has expired. If your server is still active, please repost it with a permanent invite!\`` + ) + .catch((e: string) => { + console.error(e); + }); + + await discord.postWebMetric("server"); + // keeping this here incase the team wants it + // discord.channels.alerts.send(`${settings.emoji.warn} failed to autosync server **${server.name}** \`(${id})\`: ${e}\n<${settings.website.url}/servers/${id}>`) } await global.redis?.hset("autosync", "nextServer", getNext(ids, id)) @@ -172,7 +213,7 @@ router.get('/templates', async (req, res) => { const ids = (await global.redis?.hkeys("templates")).sort() if (!ids) return res.sendStatus(503) - + if (!id) id = ids[0] const dbTemplate: delTemplate = await global.db @@ -181,7 +222,7 @@ router.get('/templates', async (req, res) => { if (dbTemplate) try { const template = await discord.bot.api.guilds.templates(id).get() as APITemplate - + await global.db.collection("templates").updateOne( { _id: id }, { @@ -215,7 +256,52 @@ router.get('/templates', async (req, res) => { await templateCache.updateTemplate(id); } catch (e) { - discord.channels.alerts.send(`${settings.emoji.warn} failed to autosync template **${dbTemplate.name}** \`(${id})\`: ${e}\n<${settings.website.url}/templates/${id}>`) + if (e.code == 10057) return; // https://discord.com/developers/docs/topics/opcodes-and-status-codes#json + // may as well reduce the load on web mods - AJ + await global.db + .collection("templates") + .deleteOne({ _id: id }); + + await global.db.collection("audit").insertOne({ + type: "REMOVE_TEMPLATE", + executor: "AutoSync", + target: id, + date: Date.now(), + reason: "Unknown server template (10057)", + reasonType: 4 + }); + + await templateCache.deleteTemplate(id); + + const embed = new MessageEmbed(); + embed.setColor(0x2f3136); + embed.setTitle("Reason"); + embed.setDescription(req.body.reason); + + discord.channels.logs.send({ + content: `${settings.emoji.delete} **AutoSync System** removed template **${functions.escapeFormatting( + dbTemplate.name + )}** \`(${id})\``, + embeds: [embed] + }); + + const owner = await discord.getMember(dbTemplate.creator.id); + if (owner) + owner + .send( + `${settings.emoji.delete + } **|** Your template **${functions.escapeFormatting( + dbTemplate.name + )}** \`(${id + })\` has been removed!\n**Reason:** \`Our AutoSync system has determined this template has been deleted from discord.\`` + ) + .catch((e) => { + console.error(e); + }); + + await discord.postWebMetric("template"); + // keeping the below just in-case the team wants it still. + // discord.channels.alerts.send(`${settings.emoji.warn} failed to autosync template **${dbTemplate.name}** \`(${id})\`: ${e}\n<${settings.website.url}/templates/${id}>`) } await global.redis?.hset("autosync", "nextTemplate", getNext(ids, id)) diff --git a/src/Routes/bots.ts b/src/Routes/bots.ts index d8026b27..fd7fa104 100644 --- a/src/Routes/bots.ts +++ b/src/Routes/bots.ts @@ -89,9 +89,9 @@ router.get( permission.scopes([OAuth2Scopes.GuildsJoin]), async (req: Request, res: Response) => { - const bots = await botCache.getAllBots(); - - const showResubmitNote = bots.filter(bot => bot.status.archived && bot.owner.id === req.user.id).length > 0 + // in this specific instance it makes more sense to make a mongo query than filtering through the entire redis cache + const showResubmitNote = await global.db.collection("bots").countDocuments({ "owner.id": req.user.id, "status.archived": true }, { limit: 1 }) + // this will return 1/true if something exists/is found, 0 if not. res.locals.premidPageInfo = res.__("premid.bots.submit"); @@ -125,7 +125,7 @@ router.post( const botExists = await global.db .collection("bots") - .findOne({ _id: req.body.id }); + .countDocuments({ _id: req.body.id }, { limit: 1 }); if (botExists) return res.status(409).json({ @@ -170,7 +170,7 @@ router.post( error = true; errors.push(res.__("common.error.bot.arr.clientIDIsUser")); }) - .catch(() => {}); + .catch(() => { }); } if (req.body.invite === "") { @@ -292,13 +292,13 @@ router.post( }); if (fetchServer) - await fetch(`https://stonks.widgetbot.io/api/graphql`, { - method: 'post', - body: JSON.stringify({ - query: `{guild(id:"${req.body.widgetServer}"){id}}` - }), - headers: { 'Content-Type': 'application/json' }, - }).then(async (fetchRes: fetchRes) => { + await fetch(`https://stonks.widgetbot.io/api/graphql`, { + method: 'post', + body: JSON.stringify({ + query: `{guild(id:"${req.body.widgetServer}"){id}}` + }), + headers: { 'Content-Type': 'application/json' }, + }).then(async (fetchRes: fetchRes) => { const data: any = await fetchRes.json(); if (data && !data.guild?.id) { error = true; @@ -498,7 +498,7 @@ router.post( auth: { accessToken, refreshToken, - expires: Date.now() + result.expires_in*1000 + expires: Date.now() + result.expires_in * 1000 } } } @@ -508,14 +508,14 @@ router.post( }) } - const receivedCommands = await (await fetch(DAPI+Routes.applicationCommands(req.body.id), {headers: {authorization: `Bearer ${req.user.db.auth.accessToken}`}})).json().catch(() => {}) as APIApplicationCommand[] + const receivedCommands = await (await fetch(DAPI + Routes.applicationCommands(req.body.id), { headers: { authorization: `Bearer ${req.user.db.auth.accessToken}` } })).json().catch(() => { }) as APIApplicationCommand[] if (Array.isArray(receivedCommands)) commands = receivedCommands; } let userFlags = 0 if (req.body.bot) { - const user = await discord.bot.api.users(req.body.id).get().catch(() => {}) as APIUser + const user = await discord.bot.api.users(req.body.id).get().catch(() => { }) as APIUser if (user.public_flags) userFlags = user.public_flags } @@ -616,15 +616,13 @@ router.post( } } as delBot); - await discord.channels.logs.send( + discord.channels.logs.send( `${settings.emoji.add} **${functions.escapeFormatting( req.user.db.fullUsername - )}** \`(${ - req.user.id + )}** \`(${req.user.id })\` added bot **${functions.escapeFormatting( app.name - )}** \`(${req.body.id})\`\n<${settings.website.url}/bots/${ - req.body.id + )}** \`(${req.body.id})\`\n<${settings.website.url}/bots/${req.body.id }>` ); @@ -1083,7 +1081,7 @@ router.post( res.__("common.error.bot.arr.clientIDIsUser") ); }) - .catch(() => {}); + .catch(() => { }); } const botExists: delBot | undefined = await global.db @@ -1133,7 +1131,7 @@ router.post( res.__("common.error.listing.arr.invite.discordapp") ); } else if (req.body.invite.includes("discord.com") && - (req.body.bot && !req.body.invite.includes(OAuth2Scopes.Bot) || req.body.slashCommands && !req.body.invite.includes(OAuth2Scopes.ApplicationsCommands))) { + (req.body.bot && !req.body.invite.includes(OAuth2Scopes.Bot) || req.body.slashCommands && !req.body.invite.includes(OAuth2Scopes.ApplicationsCommands))) { error = true; errors.push( res.__("common.error.bot.arr.scopesNotInInvite") @@ -1297,7 +1295,7 @@ router.post( }); if (fetchChannel) - await fetch(`https://stonks.widgetbot.io/api/graphql`, { + await fetch(`https://stonks.widgetbot.io/api/graphql`, { method: 'post', body: JSON.stringify({ query: `{channel(id:"${req.body.widgetChannel}"){id}}` @@ -1447,7 +1445,7 @@ router.post( auth: { accessToken, refreshToken, - expires: Date.now() + result.expires_in*1000 + expires: Date.now() + result.expires_in * 1000 } } } @@ -1457,14 +1455,14 @@ router.post( }) } - const receivedCommands = await (await fetch(DAPI+Routes.applicationCommands(bot._id), {headers: {authorization: `Bearer ${req.user.db.auth.accessToken}`}})).json().catch(() => {}) as APIApplicationCommand[] + const receivedCommands = await (await fetch(DAPI + Routes.applicationCommands(bot._id), { headers: { authorization: `Bearer ${req.user.db.auth.accessToken}` } })).json().catch(() => { }) as APIApplicationCommand[] if (Array.isArray(receivedCommands)) commands = receivedCommands; } let userFlags = 0 if (req.body.bot) { - const user = await discord.bot.api.users(bot._id).get().catch(() => {}) as APIUser + const user = await discord.bot.api.users(bot._id).get().catch(() => { }) as APIUser if (user.public_flags) userFlags = user.public_flags } @@ -1631,17 +1629,15 @@ router.post( }); await botCache.updateBot(req.params.id); - await discord.channels.logs.send( - `${settings.emoji.edit} **${functions.escapeFormatting( - req.user.db.fullUsername - )}** \`(${ - req.user.id - })\` edited bot **${functions.escapeFormatting( - app.name - )}** \`(${app.id})\`\n<${settings.website.url}/bots/${ - req.params.id - }>` - ) + discord.channels.logs.send( + `${settings.emoji.edit} **${functions.escapeFormatting( + req.user.db.fullUsername + )}** \`(${req.user.id + })\` edited bot **${functions.escapeFormatting( + app.name + )}** \`(${app.id})\`\n<${settings.website.url}/bots/${req.params.id + }>` + ) .catch((e) => { console.error(e); }); @@ -1763,16 +1759,12 @@ router.get("/:id", variables, async (req: Request, res: Response) => { if (user) { editorsLength !== looped - ? (editors += `${ - sen(user.fullUsername) || "Unknown#0000" - }, `) - : (editors += `${ - sen(user.fullUsername) || "Unknown#0000" - }`); + ? (editors += `${sen(user.fullUsername) || "Unknown#0000" + }, `) + : (editors += `${sen(user.fullUsername) || "Unknown#0000" + }`); } else { if (editorsLength === looped) editors = editors.substring(0, editors.length - 2); @@ -1804,7 +1796,7 @@ router.get( permission.auth, async (req, res) => { res.send(String(await global.redis?.hexists("bots", req.params.id))) -}) + }) router.get( "/:id/src", @@ -1942,7 +1934,7 @@ router.get( } ); - await botCache.updateBot( bot._id); + await botCache.updateBot(bot._id); res.redirect(`/bots/${bot._id}`); } @@ -2095,13 +2087,11 @@ router.get( req: req }); - await discord.channels.logs.send( + discord.channels.logs.send( `${settings.emoji.delete} **${functions.escapeFormatting( req.user.db.fullUsername - )}** \`(${ - req.user.id - })\` deleted bot **${functions.escapeFormatting(bot.name)}** \`(${ - bot._id + )}** \`(${req.user.id + })\` deleted bot **${functions.escapeFormatting(bot.name)}** \`(${bot._id })\`` ); @@ -2157,13 +2147,11 @@ router.get( req: req }); - await discord.channels.logs.send( + discord.channels.logs.send( `${settings.emoji.archive} **${functions.escapeFormatting( req.user.db.fullUsername - )}** \`(${ - req.user.id - })\` archived bot **${functions.escapeFormatting(bot.name)}** \`(${ - bot._id + )}** \`(${req.user.id + })\` archived bot **${functions.escapeFormatting(bot.name)}** \`(${bot._id })\`` ); @@ -2225,13 +2213,11 @@ router.get( req: req }); - await discord.channels.logs.send( + discord.channels.logs.send( `${settings.emoji.hide} **${functions.escapeFormatting( req.user.db.fullUsername - )}** \`(${ - req.user.id - })\` hid bot **${functions.escapeFormatting(bot.name)}** \`(${ - bot._id + )}** \`(${req.user.id + })\` hid bot **${functions.escapeFormatting(bot.name)}** \`(${bot._id })\`` ); @@ -2292,13 +2278,11 @@ router.get( req: req }); - await discord.channels.logs.send( + discord.channels.logs.send( `${settings.emoji.unhide} **${functions.escapeFormatting( req.user.db.fullUsername - )}** \`(${ - req.user.id - })\` unhid bot **${functions.escapeFormatting(bot.name)}** \`(${ - bot._id + )}** \`(${req.user.id + })\` unhid bot **${functions.escapeFormatting(bot.name)}** \`(${bot._id })\`` ); @@ -2414,7 +2398,7 @@ router.post( error = true errors.push(res.__("common.error.bot.arr.clientIDIsUser")); }) - .catch(() => {}); + .catch(() => { }); } const botExists: delBot | undefined = await global.db @@ -2631,13 +2615,13 @@ router.post( }); if (fetchChannel) - await fetch(`https://stonks.widgetbot.io/api/graphql`, { - method: 'post', - body: JSON.stringify({ - query: `{channel(id:"${req.body.widgetChannel}"){id}}` - }), - headers: { 'Content-Type': 'application/json' }, - }).then(async (fetchRes: fetchRes) => { + await fetch(`https://stonks.widgetbot.io/api/graphql`, { + method: 'post', + body: JSON.stringify({ + query: `{channel(id:"${req.body.widgetChannel}"){id}}` + }), + headers: { 'Content-Type': 'application/json' }, + }).then(async (fetchRes: fetchRes) => { const data: any = await fetchRes.json(); if (!data.channel?.id) { error = true; @@ -2776,7 +2760,7 @@ router.post( auth: { accessToken, refreshToken, - expires: Date.now() + result.expires_in*1000 + expires: Date.now() + result.expires_in * 1000 } } } @@ -2786,14 +2770,14 @@ router.post( }) } - const receivedCommands = await (await fetch(DAPI+Routes.applicationCommands(bot._id), {headers: {authorization: `Bearer ${req.user.db.auth.accessToken}`}})).json().catch(() => {}) as APIApplicationCommand[] + const receivedCommands = await (await fetch(DAPI + Routes.applicationCommands(bot._id), { headers: { authorization: `Bearer ${req.user.db.auth.accessToken}` } })).json().catch(() => { }) as APIApplicationCommand[] if (Array.isArray(receivedCommands)) commands = receivedCommands; } let userFlags = 0 if (req.body.bot) { - const user = await discord.bot.api.users(bot._id).get().catch(() => {}) as APIUser + const user = await discord.bot.api.users(bot._id).get().catch(() => { }) as APIUser if (user.public_flags) userFlags = user.public_flags } @@ -2954,16 +2938,14 @@ router.post( await botCache.updateBot(req.params.id); await discord.channels.logs.send( - `${settings.emoji.resubmit} **${functions.escapeFormatting( - req.user.db.fullUsername - )}** \`(${ - req.user.id - })\` resubmitted bot **${functions.escapeFormatting( - app.name - )}** \`(${app.id})\`\n<${settings.website.url}/bots/${ - app.id - }>` - ) + `${settings.emoji.resubmit} **${functions.escapeFormatting( + req.user.db.fullUsername + )}** \`(${req.user.id + })\` resubmitted bot **${functions.escapeFormatting( + app.name + )}** \`(${app.id})\`\n<${settings.website.url}/bots/${app.id + }>` + ) .catch((e) => { console.error(e); }); @@ -3046,16 +3028,14 @@ router.get( ); await discord.channels.logs.send( - `${settings.emoji.check} **${functions.escapeFormatting( - req.user.db.fullUsername - )}** \`(${ - req.user.id - })\` approved bot **${functions.escapeFormatting( - bot.name - )}** \`(${bot._id})\`\n<${settings.website.url}/bots/${ - bot._id - }>` - ) + `${settings.emoji.check} **${functions.escapeFormatting( + req.user.db.fullUsername + )}** \`(${req.user.id + })\` approved bot **${functions.escapeFormatting( + bot.name + )}** \`(${bot._id})\`\n<${settings.website.url}/bots/${bot._id + }>` + ) .catch((e) => { console.error(e); }); @@ -3064,12 +3044,10 @@ router.get( if (owner) owner .send( - `${ - settings.emoji.check + `${settings.emoji.check } **|** Your bot **${functions.escapeFormatting( bot.name - )}** \`(${bot._id})\` has been approved on the website!${ - !bot.scopes || bot.scopes.bot ? '\n\nYour bot will be added to our server within the next 24 hours.' : '' + )}** \`(${bot._id})\` has been approved on the website!${!bot.scopes || bot.scopes.bot ? '\n\nYour bot will be added to our server within the next 24 hours.' : '' }` ) .catch((e) => { @@ -3080,7 +3058,7 @@ router.get( if (mainGuildOwner) mainGuildOwner.roles .add(settings.roles.developer, "User's bot was just approved.") - .catch((e) => { + .catch(async (e) => { console.error(e); discord.channels.alerts.send( `${settings.emoji.error} Failed giving <@${bot.owner.id}> \`${bot.owner.id}\` the role **Bot Developer** upon one of their bots being approved.` @@ -3091,7 +3069,7 @@ router.get( if (mainGuildBot) mainGuildBot.roles .add(settings.roles.bot, "Bot was approved on the website.") - .catch((e) => { + .catch(async (e) => { console.error(e); discord.channels.alerts.send( `${settings.emoji.error} Failed giving <@${bot._id}> \`${bot._id}\` the role **Bot** upon being approved on the website.` @@ -3102,7 +3080,7 @@ router.get( if (botStaffServer) botStaffServer .kick("Bot was approved on the website.") - .catch((e) => { + .catch(async (e) => { console.error(e); discord.channels.alerts.send( `${settings.emoji.error} Failed kicking <@${bot._id}> \`${bot._id}\` from the Testing Server on approval.` @@ -3160,7 +3138,7 @@ router.get( settings.roles.premiumBot, "Bot was given premium on the website." ) - .catch((e) => { + .catch(async (e) => { console.error(e); discord.channels.alerts.send( `${settings.emoji.error} Failed giving <@${botMember.id}> \`${botMember.id}\` the role **Premium Bot** upon being given premium on the website.` @@ -3374,14 +3352,12 @@ router.post( embed.setDescription(req.body.reason); embed.setURL(`${settings.website.url}/bots/${bot._id}`); - await discord.channels.logs.send({ + discord.channels.logs.send({ content: `${settings.emoji.cross} **${functions.escapeFormatting( req.user.db.fullUsername - )}** \`(${ - req.user.id - })\` declined bot **${functions.escapeFormatting(bot.name)}** \`(${ - bot._id - })\``, + )}** \`(${req.user.id + })\` declined bot **${functions.escapeFormatting(bot.name)}** \`(${bot._id + })\``, embeds: [embed] }); @@ -3397,12 +3373,10 @@ router.post( if (owner) owner .send( - `${ - settings.emoji.cross + `${settings.emoji.cross } **|** Your bot **${functions.escapeFormatting( bot.name - )}** \`(${bot._id})\` has been declined.\n**Reason:** \`${ - req.body.reason || "None specified." + )}** \`(${bot._id})\` has been declined.\n**Reason:** \`${req.body.reason || "None specified." }\`` ) .catch((e) => { @@ -3533,14 +3507,13 @@ router.post( embed.setDescription(req.body.reason); embed.setURL(`${settings.website.url}/bots/${bot._id}`); - await discord.channels.logs.send({ + discord.channels.logs.send({ content: `${settings.emoji.unapprove} **${functions.escapeFormatting( req.user.db.fullUsername - )}** \`(${ - req.user.id - })\` unapproved bot **${functions.escapeFormatting( - bot.name - )}** \`(${bot._id})\``, + )}** \`(${req.user.id + })\` unapproved bot **${functions.escapeFormatting( + bot.name + )}** \`(${bot._id})\``, embeds: [embed] }); @@ -3557,12 +3530,10 @@ router.post( if (owner) owner .send( - `${ - settings.emoji.unapprove + `${settings.emoji.unapprove } **|** Your bot **${functions.escapeFormatting( bot.name - )}** \`(${bot._id})\` has been unapproved!\n**Reason:** \`${ - req.body.reason || "None specified." + )}** \`(${bot._id})\` has been unapproved!\n**Reason:** \`${req.body.reason || "None specified." }\`` ) .catch((e) => { @@ -3693,14 +3664,12 @@ router.post( embed.setDescription(req.body.reason); embed.setURL(`${settings.website.url}/bots/${bot._id}`); - await discord.channels.logs.send({ + discord.channels.logs.send({ content: `${settings.emoji.delete} **${functions.escapeFormatting( req.user.db.fullUsername - )}** \`(${ - req.user.id - })\` removed bot **${functions.escapeFormatting(bot.name)}** \`(${ - bot._id - })\``, + )}** \`(${req.user.id + })\` removed bot **${functions.escapeFormatting(bot.name)}** \`(${bot._id + })\``, embeds: [embed] }); @@ -3719,12 +3688,10 @@ router.post( if (owner) owner .send( - `${ - settings.emoji.delete + `${settings.emoji.delete } **|** Your bot **${functions.escapeFormatting( bot.name - )}** \`(${bot._id})\` has been removed!\n**Reason:** \`${ - req.body.reason || "None specified." + )}** \`(${bot._id})\` has been removed!\n**Reason:** \`${req.body.reason || "None specified." }\`` ) .catch((e) => { @@ -3844,14 +3811,12 @@ router.post( embed.setDescription(req.body.reason); embed.setURL(`${settings.website.url}/bots/${bot._id}`); - await discord.channels.logs.send({ + discord.channels.logs.send({ content: `${settings.emoji.hide} **${functions.escapeFormatting( req.user.db.fullUsername - )}** \`(${ - req.user.id - })\` hid bot **${functions.escapeFormatting(bot.name)}** \`(${ - bot._id - })\``, + )}** \`(${req.user.id + })\` hid bot **${functions.escapeFormatting(bot.name)}** \`(${bot._id + })\``, embeds: [embed] }); @@ -3859,12 +3824,10 @@ router.post( if (owner) owner .send( - `${ - settings.emoji.hide + `${settings.emoji.hide } **|** Your bot **${functions.escapeFormatting( bot.name - )}** \`(${bot._id})\` has been hidden!\n**Reason:** \`${ - req.body.reason || "None specified." + )}** \`(${bot._id})\` has been hidden!\n**Reason:** \`${req.body.reason || "None specified." }\`` ) .catch((e) => { @@ -3922,17 +3885,15 @@ router.get( } ); - await discord.channels.logs.send( - `${settings.emoji.unhide} **${functions.escapeFormatting( - req.user.db.fullUsername - )}** \`(${ - req.user.id - })\` unhid bot **${functions.escapeFormatting( - bot.name - )}** \`(${bot._id})\`\n<${settings.website.url}/bots/${ - bot._id - }>` - ) + discord.channels.logs.send( + `${settings.emoji.unhide} **${functions.escapeFormatting( + req.user.db.fullUsername + )}** \`(${req.user.id + })\` unhid bot **${functions.escapeFormatting( + bot.name + )}** \`(${bot._id})\`\n<${settings.website.url}/bots/${bot._id + }>` + ) .catch((e) => { console.error(e); }); @@ -3941,8 +3902,7 @@ router.get( if (owner) owner .send( - `${ - settings.emoji.check + `${settings.emoji.check } **|** Your bot **${functions.escapeFormatting( bot.name )}** \`(${bot._id})\` has been unhidden on the website!` @@ -4002,7 +3962,7 @@ router.get( auth: { accessToken, refreshToken, - expires: Date.now() + result.expires_in*1000 + expires: Date.now() + result.expires_in * 1000 } } } @@ -4012,14 +3972,14 @@ router.get( }) } - const receivedCommands = await (await fetch(DAPI+Routes.applicationCommands(bot._id), {headers: {authorization: `Bearer ${req.user.db.auth.accessToken}`}})).json().catch(() => {}) as APIApplicationCommand[] + const receivedCommands = await (await fetch(DAPI + Routes.applicationCommands(bot._id), { headers: { authorization: `Bearer ${req.user.db.auth.accessToken}` } })).json().catch(() => { }) as APIApplicationCommand[] if (Array.isArray(receivedCommands)) commands = receivedCommands; } let userFlags = 0 if (bot.scopes?.bot) { - const user = await discord.bot.api.users(bot._id).get().catch(() => {}) as APIUser + const user = await discord.bot.api.users(bot._id).get().catch(() => { }) as APIUser if (user.public_flags) userFlags = user.public_flags } diff --git a/src/Routes/index.ts b/src/Routes/index.ts index 828f0f7e..f381ed4a 100644 --- a/src/Routes/index.ts +++ b/src/Routes/index.ts @@ -27,14 +27,14 @@ import * as serverCache from "../Util/Services/serverCaching.js"; import * as templateCache from "../Util/Services/templateCaching.js"; import * as discord from "../Util/Services/discord.js"; import { variables } from "../Util/Function/variables.js"; -import type { GuildMember } from "discord.js"; +import type { Guild, GuildMember, GuildMemberManager } from "discord.js"; const router = express.Router(); const nickSorter = (a, b) => (a.nick || a.user.username).localeCompare(b.nick || b.user.username); function sortAll() { - let members = discord.guilds.main.members; + let members = discord.guilds.main.members as GuildMemberManager; if (!members) throw new Error("Fetching members failed!"); const staff: GuildMember[] = [], donators: GuildMember[] = [], @@ -53,10 +53,10 @@ function sortAll() { member.rank = admin ? "admin" : assistant - ? "assistant" - : mod - ? "mod" - : null; + ? "assistant" + : mod + ? "mod" + : null; const user = discord.bot.users.cache.get(member.id); member.avatar = user.avatar; member.username = user.username; @@ -237,7 +237,10 @@ router.get("/servers", variables, async (req: Request, res: Response) => { if (!req.query.page) req.query.page = "1"; - const servers = (await serverCache.getAllServers()).filter( + const servers = (await serverCache.getAllServers()).slice( + 15 * Number(req.query.page) - 15, + 15 * Number(req.query.page) + ).filter( ({ _id, status }) => status && !status.reviewRequired ); @@ -246,10 +249,7 @@ router.get("/servers", variables, async (req: Request, res: Response) => { subtitle: res.__("common.servers.subtitle"), req, servers, - serversPgArr: servers.slice( - 15 * Number(req.query.page) - 15, - 15 * Number(req.query.page) - ), + serversPgArr: servers, page: req.query.page, pages: Math.ceil(servers.length / 15) }); diff --git a/src/Routes/servers.ts b/src/Routes/servers.ts index 8c25ba08..319b96e5 100644 --- a/src/Routes/servers.ts +++ b/src/Routes/servers.ts @@ -45,6 +45,7 @@ import { ParamsDictionary } from "express-serve-static-core"; import { ParsedQs } from "qs"; const md = new mdi const router = express.Router(); +let reviewRequired = false; // Needs to be outside of the functions or it cannot be referenced outside of x function - AJ function serverType(bodyType: string): number { let type: serverReasons = parseInt(bodyType); @@ -81,7 +82,6 @@ function tagHandler(req: express.Request type !== "GAME_HIGHSCORE_UPDATE" - ); + ({ type }) => type !== "GAME_HIGHSCORE_UPDATE" + ); if (!req.query.page) req.query.page = "1"; @@ -188,7 +188,6 @@ router.get( } res.locals.premidPageInfo = res.__("premid.staff.audit"); - res.render("templates/staff/audit", { title: res.__("page.staff.audit"), subtitle: res.__("page.staff.audit.subtitle"), @@ -197,7 +196,7 @@ router.get( logsPgArr: iteratedLogs, page: req.query.page, pages: Math.ceil(logs.length / 15), - functions + functions, }); } ); diff --git a/src/Routes/templates.ts b/src/Routes/templates.ts index 61492cb5..4bafa8aa 100644 --- a/src/Routes/templates.ts +++ b/src/Routes/templates.ts @@ -196,7 +196,7 @@ router.post( } } as delTemplate); - await discord.channels.logs.send( + discord.channels.logs.send( `${settings.emoji.add} **${functions.escapeFormatting( req.user.db.fullUsername )}** \`(${ @@ -563,7 +563,7 @@ router.post( } ); - await discord.channels.logs.send( + discord.channels.logs.send( `${settings.emoji.edit} **${functions.escapeFormatting( req.user.db.fullUsername )}** \`(${ @@ -701,7 +701,7 @@ router.get( req }); - await discord.channels.logs.send( + discord.channels.logs.send( `${settings.emoji.delete} **${functions.escapeFormatting( req.user.db.fullUsername )}** \`(${ @@ -815,7 +815,7 @@ router.post( embed.setTitle("Reason"); embed.setDescription(req.body.reason); - await discord.channels.logs.send({ + discord.channels.logs.send({ content: `${settings.emoji.delete} **${functions.escapeFormatting( req.user.db.fullUsername )}** \`(${ diff --git a/src/Util/Function/main.ts b/src/Util/Function/main.ts index dc9c8aa3..5ed3a799 100644 --- a/src/Util/Function/main.ts +++ b/src/Util/Function/main.ts @@ -21,12 +21,14 @@ import * as botCache from "../Services/botCaching.js"; import * as userCache from "../Services/userCaching.js"; import { URL } from "url"; import { OAuth2Scopes } from "discord-api-types/v10"; - export const escapeFormatting = (text: string) => { const unescaped = text.replace(/\\(\*|_|`|~|\\)/g, "$1"); const escaped = unescaped.replace(/(\*|_|`|~|\\)/g, "\\$1"); return escaped; }; +// this seems stupid but apparently it should work +import { createRequire } from "module"; +const require = createRequire(import.meta.url); const regions = { "us-west": "US West", @@ -118,8 +120,7 @@ export function parseDate(__, locale: string, rawDate: number): string { if (rawDate === 0) return "???"; const date = new Date(rawDate); - const dateFormat = require(`../../../../node_modules/del-i18n/website/${locale}.json`); - + const dateFormat = require(`../../../../node_modules/del-i18n/website/${locale}.json`) if (dateFormat["common.dateFormat"].includes("{{amPM}}")) { let amPM: string; let hour = date.getUTCHours(); @@ -228,7 +229,7 @@ export function parseAudit(__, auditType: string): auditType { returnType.name = __("page.staff.audit.type.MOD_HIDE_BOT"); returnType.icon = "far fa-eye-slash has-text-white"; break; - case "MOD_UNHIDE_BOT": + case "MOD_UNHIDE_BOT": returnType.name = __("page.staff.audit.type.MOD_UNHIDE_BOT"); returnType.icon = "far fa-eye has-text-white"; break; diff --git a/src/Util/Services/banned.ts b/src/Util/Services/banned.ts index 01bedfa3..f890fd47 100644 --- a/src/Util/Services/banned.ts +++ b/src/Util/Services/banned.ts @@ -17,6 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ +import { Collection, GuildBan } from "discord.js"; import { guilds } from "./discord.js"; export async function check(user: string): Promise { @@ -26,8 +27,8 @@ export async function check(user: string): Promise { export async function updateBanlist() { await global.redis?.del("bans"); - const bans = await guilds.main.bans.fetch().catch(e => console.error(e)); - await global.redis?.hmset( + const bans = await guilds.main.bans.fetch().catch(e => console.error(e)) as Collection; + if(bans.size > 0) await global.redis?.hmset( "bans", ...bans.map((ban) => [ban.user.id, true]) ); diff --git a/src/Util/Services/botCaching.ts b/src/Util/Services/botCaching.ts index 946561d7..a47a58fd 100644 --- a/src/Util/Services/botCaching.ts +++ b/src/Util/Services/botCaching.ts @@ -18,7 +18,6 @@ along with this program. If not, see . */ const prefix = "bots"; - export async function getBot(id: string): Promise { const bot = await global.redis?.hget(prefix, id); if (!bot) return; @@ -59,6 +58,7 @@ export async function uploadBots() { prefix, ...botsDB.map((bot: delBot) => [bot._id, JSON.stringify(bot)]) ); + } export async function deleteBot(id: string) { diff --git a/src/Util/Services/discord.ts b/src/Util/Services/discord.ts index 41b9d491..1752b4bb 100644 --- a/src/Util/Services/discord.ts +++ b/src/Util/Services/discord.ts @@ -27,8 +27,16 @@ import * as botCache from "./botCaching.js"; import { hostname } from "os"; const prefix = "statuses"; +// If someone is to self-host or contribute, setting datadog metrics is a lot, +// if they have nothing set in the secret section of settings.json, let's ignore metrics - AJ +if (settings.secrets.datadog) metrics.init({ host: "", prefix: "", apiKey: settings.secrets.datadog }); -metrics.init({ host: "", prefix: "", apiKey: settings.secrets.datadog }); +// Let's not query the database of users, and bots, and then make changes to it every 5 seconds, that would be a good thing not to do +setInterval(async () => { + postWebMetric("user"); + postWebMetric("bot_unapproved"); + await postTodaysGrowth(); +}, 8.568e+7); // 23.8h, to account for eventual time drift if the site is online for a while (which is the goal lol) - AJ // @ts-expect-error class Client extends Discord.Client { @@ -52,9 +60,9 @@ export const bot = new Client({ }); bot.on("guildBanRemove", async (ban) => { - if (ban.guild.id === settings.guild.main) { - await global.redis?.hdel("bans", ban.user.id); - } + if (ban.guild.id === settings.guild.main) { + await global.redis?.hdel("bans", ban.user.id); + } }); bot.on("ready", async () => { @@ -72,12 +80,12 @@ bot.on("ready", async () => { console.log(`Skipping discord caching. The instance which holds the lock is: ${lock}`); } else { console.time("Bot cache"); - botCache.getAllBots().then(bots => { + botCache.getAllBots().then(async bots => { const botsToFetch = [] - bots.forEach(bot => { - if (!guilds.main.members.cache.has(bot._id)) botsToFetch.push(bot._id) - }) - guilds.main.members.fetch({user: botsToFetch}) + bots.forEach(async bot => { + if (guilds.main.members.cache.has(bot._id)) botsToFetch.push(bot._id) + }); + await guilds.main.members.fetch({ user: botsToFetch }) .then(x => console.log(`Retrieved ${x.size} members!`)) .catch(() => null); // It is most likely that DEL has another instance running to handle this, so catch the error and ignore. }); @@ -98,7 +106,7 @@ bot.on("guildMemberAdd", async (member) => { await global.redis?.hmset( prefix, member.id, - member.presence.status || PresenceUpdateStatus.Offline + member.presence ? member.presence.status || PresenceUpdateStatus.Offline : PresenceUpdateStatus.Offline ); if (member.guild.id === settings.guild.main) await postMetric(); @@ -110,26 +118,28 @@ bot.on("guildMemberRemove", async (member) => { await postMetric(); } }); - export const channels = { - get logs() { return bot.channels.cache.get(settings.channels.webLog) as Discord.TextChannel }, - get alerts() { return bot.channels.cache.get(settings.channels.alerts) as Discord.TextChannel } + // There is a chance this will fail on recent bot restart if it didn't cache the channel yet. + // Using .fetch() will by default cache the channel on success, and then from there it shouldn't need to again + get logs() { return (bot.channels.cache.has(settings.channels.webLog) ? bot.channels.cache.get(settings.channels.webLog) : (async () => { await bot.channels.fetch(settings.channels.webLog) }).call(this)) as Discord.TextChannel }, + get alerts() { return (bot.channels.cache.has(settings.channels.webLog) ? bot.channels.cache.get(settings.channels.alerts) : (async () => { await bot.channels.fetch(settings.channels.alerts) }).call(this)) as Discord.TextChannel } } export const guilds = { - get main() { return bot.guilds.cache.get(settings.guild.main) }, - get testing() { return bot.guilds.cache.get(settings.guild.staff) }, + // same thing as the channels above + get main() { return (bot.guilds.cache.has(settings.guild.main) ? bot.guilds.cache.get(settings.guild.main) : (async () => { await bot.guilds.fetch(settings.guild.main) }).call(this)) as Discord.Guild }, + get testing() { return (bot.guilds.cache.has(settings.guild.staff) ? bot.guilds.cache.get(settings.guild.staff) : (async () => { await bot.guilds.fetch(settings.guild.staff) }).call(this)) as Discord.Guild }, } export async function getMember(id: string) { if (guilds.main) { - return await guilds.main.members.fetch(id).catch(() => {}); + return guilds.main.members.fetch(id).catch(() => { }); } else return undefined; } export async function getTestingGuildMember(id: string) { if (guilds.testing) { - return await guilds.testing.members.fetch(id).catch(() => {}); + return guilds.testing.members.fetch(id).catch(() => { }); } else return undefined; } @@ -158,33 +168,19 @@ export async function uploadStatuses() { export async function postMetric() { const guild = guilds.main; - if (guild) metrics.gauge("del.server.memberCount", guild.memberCount); + if (guild && settings.secrets.datadog) metrics.gauge("del.server.memberCount", (await guild).memberCount); +} +export async function postSpecificMetric(metric: string, gauge: number) { + if (settings.secrets.datadog) metrics.gauge(`${metric}`, gauge); } - export async function postWebMetric(type: string) { if (!global.db) return - const bots: delBot[] = await global.db.collection("bots").find().toArray(); - - const servers: delServer[] = await global.db - .collection("servers") - .find() - .toArray(); - - const templates: delTemplate[] = await global.db - .collection("templates") - .find() - .toArray(); - - const users: delUser[] = await global.db - .collection("users") - .find() - .toArray(); - switch (type) { case "bot": - settings.website.dev - ? metrics.gauge("del.website.dev.botCount", bots.length) - : metrics.gauge("del.website.botCount", bots.length); + const bots = await global.db.collection("bots").estimatedDocumentCount() + if (settings.secrets.datadog) settings.website.dev + ? metrics.gauge("del.website.dev.botCount", bots) + : metrics.gauge("del.website.botCount", bots); const todaysGrowth = await global.db .collection("webOptions") @@ -208,37 +204,40 @@ export async function postWebMetric(type: string) { break; case "bot_unapproved": - const unapprovedBots = bots.filter( - (b) => !b.status.approved && !b.status.archived - ); + const unapprovedBots = await global.db.collection("bots").countDocuments({ $or: [{ "status.archived": false }, { "status.approved": false }] }) - settings.website.dev + if (settings.secrets.datadog) settings.website.dev ? metrics.gauge( - "del.website.dev.botCount.unapproved", - unapprovedBots.length - ) + "del.website.dev.botCount.unapproved", + unapprovedBots + ) : metrics.gauge( - "del.website.botCount.unapproved", - unapprovedBots.length - ); + "del.website.botCount.unapproved", + unapprovedBots + ); break; case "server": - settings.website.dev - ? metrics.gauge("del.website.dev.serverCount", servers.length) - : metrics.gauge("del.website.serverCount", servers.length); + const servers = settings.secrets.datadog ? await global.db.collection("servers").estimatedDocumentCount() : 0 + + if (settings.secrets.datadog) settings.website.dev + ? metrics.gauge("del.website.dev.serverCount", servers) + : metrics.gauge("del.website.serverCount", servers); break; case "template": - settings.website.dev + // if they aren't using datadog, don't make an unnecessary query + const templates = settings.secrets.datadog ? await global.db.collection("templates").estimatedDocumentCount() : 0 + if (settings.secrets.datadog) settings.website.dev ? metrics.gauge( - "del.website.dev.templateCount", - templates.length - ) - : metrics.gauge("del.website.templateCount", templates.length); + "del.website.dev.templateCount", + templates + ) + : metrics.gauge("del.website.templateCount", templates); break; case "user": - settings.website.dev - ? metrics.gauge("del.website.dev.userCount", users.length) - : metrics.gauge("del.website.userCount", users.length); + const users = await global.db.collection("users").estimatedDocumentCount() + if (settings.secrets.datadog) settings.website.dev + ? metrics.gauge("del.website.dev.userCount", users) + : metrics.gauge("del.website.userCount", users); break; } } @@ -257,11 +256,11 @@ export async function postTodaysGrowth() { const date = moment().diff(moment(todaysGrowth.lastPosted), "days"); if (date >= 1) { - settings.website.dev + if (settings.secrets.datadog) settings.website.dev ? metrics.gauge( - "del.website.dev.addedBotsToday", - todaysGrowth.count - ) + "del.website.dev.addedBotsToday", + todaysGrowth.count + ) : metrics.gauge("del.website.addedBotsToday", todaysGrowth.count); await global.db.collection("webOptions").updateOne( @@ -275,9 +274,3 @@ export async function postTodaysGrowth() { ); } else return; } - -setInterval(async () => { - postWebMetric("user"); - postWebMetric("bot_unapproved"); - await postTodaysGrowth(); -}, 5000); diff --git a/src/app.ts b/src/app.ts index 2ec37028..e89e62f1 100644 --- a/src/app.ts +++ b/src/app.ts @@ -46,7 +46,7 @@ import { sitemapIndex, sitemapGenerator } from "./Util/Middleware/sitemap.js"; import i18n from "i18n"; import { MongoClient } from "mongodb"; -import { RedisOptions } from "ioredis"; +import Redis from "ioredis" import { hostname } from "os"; import settings from "../settings.json" assert { type: "json" }; @@ -106,14 +106,16 @@ new Promise((resolve, reject) => { .collection("libraries") .updateOne( { _id: lib.name }, - { $set: { + { + $set: { _id: lib.name, language: lib.language, links: { docs: lib.links.docs, repo: lib.links.repo } - }}, + } + }, { upsert: true } ) .then(() => true) @@ -147,7 +149,7 @@ new Promise((resolve, reject) => { .then(() => true) .catch(() => false); } - let redisConfig: RedisOptions; + let redisConfig: Redis.RedisOptions; if (settings.secrets.redis.sentinels.length > 0) { redisConfig = { @@ -166,7 +168,7 @@ new Promise((resolve, reject) => { }; } - global.redis = new Redis(redisConfig); + global.redis = new Redis(redisConfig); const s = new Redis(redisConfig); /*There is no point in flushing the DEL redis database, it's persistent as is, and will lead to problems. - Ice*/ @@ -225,35 +227,33 @@ new Promise((resolve, reject) => { } await discord.bot.login(settings.secrets.discord.token); - - await new Promise((resolve) => { - discord.bot.once("ready", () => resolve()); - }); - - setTimeout(async () => { - await featuredCache.updateFeaturedBots(); - await discord.postMetric(); - }, 10000); - - await discord.postWebMetric("bot"); - await discord.postWebMetric("bot_unapproved"); - await discord.postWebMetric("server"); - await discord.postWebMetric("template"); - await discord.postWebMetric("user"); - - await (async function discordBotUndefined() { - if ( - typeof discord.bot.guilds !== "undefined" && - typeof discord.guilds.main !== - "undefined" - ) { - await banned.updateBanlist(); - await discord.uploadStatuses(); - } else { - setTimeout(discordBotUndefined, 250); - } - })(); - + // to replace the needed wait time for the below functions, instead of using a redundant blocking promise + // just... do it once it is actually ready -AJ + discord.bot.once("login", async () => { + setTimeout(async () => { + await featuredCache.updateFeaturedBots(); + await discord.postMetric(); + }, 10000); + + await discord.postWebMetric("bot"); + await discord.postWebMetric("bot_unapproved"); + await discord.postWebMetric("server"); + await discord.postWebMetric("template"); + await discord.postWebMetric("user"); + + await (async function discordBotUndefined() { + if ( + typeof discord.bot.guilds !== "undefined" && + typeof discord.guilds.main !== + "undefined" + ) { + await banned.updateBanlist(); + await discord.uploadStatuses(); + } else { + setTimeout(discordBotUndefined, 250); + } + })(); + }) app.set("view engine", "ejs"); app.use( @@ -280,7 +280,7 @@ new Promise((resolve, reject) => { app.use( cookieSession({ name: "delSession", - secret: settings.secrets.cookie, + keys: [settings.secrets.cookie], maxAge: 1000 * 60 * 60 * 24 * 7 }) );