summary refs log tree commit diff
path: root/slowcord
diff options
context:
space:
mode:
Diffstat (limited to 'slowcord')
-rw-r--r--slowcord/.env.template5
-rw-r--r--slowcord/.vscode/launch.json14
-rw-r--r--slowcord/.vscode/tasks.json13
-rw-r--r--slowcord/README.md1
-rw-r--r--slowcord/build/index.js102
-rw-r--r--slowcord/build/index.js.map1
-rw-r--r--slowcord/package-lock.json1435
-rw-r--r--slowcord/package.json33
-rw-r--r--slowcord/public/login.html65
-rw-r--r--slowcord/src/index.ts104
-rw-r--r--slowcord/tsconfig.json99
11 files changed, 1872 insertions, 0 deletions
diff --git a/slowcord/.env.template b/slowcord/.env.template
new file mode 100644
index 00000000..34f6abeb
--- /dev/null
+++ b/slowcord/.env.template
@@ -0,0 +1,5 @@
+DATABASE=../bundle/database.db
+PORT=3010
+DISCORD_CLIENT_ID=
+DISCORD_SECRET=
+DISCORD_REDIRECT=
\ No newline at end of file
diff --git a/slowcord/.vscode/launch.json b/slowcord/.vscode/launch.json
new file mode 100644
index 00000000..0467baec
--- /dev/null
+++ b/slowcord/.vscode/launch.json
@@ -0,0 +1,14 @@
+{
+	"configurations": [
+		{
+			"name": "Launch Program",
+			"program": "${workspaceFolder}/build/index.js",
+			"request": "launch",
+			"skipFiles": [
+				"<node_internals>/**"
+			],
+			"type": "node",
+			"preLaunchTask": "tsc: build - tsconfig.json"
+		}
+	]
+}
\ No newline at end of file
diff --git a/slowcord/.vscode/tasks.json b/slowcord/.vscode/tasks.json
new file mode 100644
index 00000000..356126d8
--- /dev/null
+++ b/slowcord/.vscode/tasks.json
@@ -0,0 +1,13 @@
+{
+	"version": "2.0.0",
+	"tasks": [
+		{
+			"type": "npm",
+			"script": "build",
+			"group": "build",
+			"problemMatcher": [],
+			"label": "npm: build",
+			"detail": "tsc -b"
+		}
+	]
+}
\ No newline at end of file
diff --git a/slowcord/README.md b/slowcord/README.md
new file mode 100644
index 00000000..629e7724
--- /dev/null
+++ b/slowcord/README.md
@@ -0,0 +1 @@
+Additional resources/services for [Slowcord](slowcord.maddy.k.vu)
\ No newline at end of file
diff --git a/slowcord/build/index.js b/slowcord/build/index.js
new file mode 100644
index 00000000..484113ed
--- /dev/null
+++ b/slowcord/build/index.js
@@ -0,0 +1,102 @@
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+var _a;
+import "dotenv/config";
+import express from "express";
+import cookieParser from "cookie-parser";
+import { initDatabase, generateToken, User, Config } from "@fosscord/util";
+import path from "path";
+import fetch from "node-fetch";
+const app = express();
+app.use(cookieParser());
+const port = process.env.PORT;
+class Discord {
+}
+_a = Discord;
+Discord.getAccessToken = (req, res) => __awaiter(void 0, void 0, void 0, function* () {
+    const { code } = req.query;
+    const body = new URLSearchParams(Object.entries({
+        client_id: process.env.DISCORD_CLIENT_ID,
+        client_secret: process.env.DISCORD_SECRET,
+        redirect_uri: process.env.DISCORD_REDIRECT,
+        code: code,
+        grant_type: "authorization_code",
+    })).toString();
+    const resp = yield fetch("https://discord.com/api/oauth2/token", {
+        method: "POST",
+        headers: {
+            "Content-Type": "application/x-www-form-urlencoded",
+        },
+        body: body
+    });
+    const json = yield resp.json();
+    if (json.error)
+        return null;
+    return {
+        access_token: json.access_token,
+        token_type: json.token_type,
+        expires_in: json.expires_in,
+        refresh_token: json.refresh_token,
+        scope: json.scope,
+    };
+});
+Discord.getUserDetails = (token) => __awaiter(void 0, void 0, void 0, function* () {
+    const resp = yield fetch("https://discord.com/api/users/@me", {
+        headers: {
+            "Authorization": `Bearer ${token}`,
+        }
+    });
+    const json = yield resp.json();
+    if (!json.username || !json.email)
+        return null; // eh, deal with bad code later
+    return {
+        email: json.email,
+        username: json.username,
+    };
+});
+const handlers = {
+    "discord": Discord,
+};
+app.get("/oauth/:type", (req, res) => __awaiter(void 0, void 0, void 0, function* () {
+    const { type } = req.params;
+    if (!type)
+        return res.sendStatus(400);
+    const handler = handlers[type];
+    if (!handler)
+        return res.sendStatus(400);
+    const data = yield handler.getAccessToken(req, res);
+    if (!data)
+        return res.sendStatus(500);
+    const details = yield handler.getUserDetails(data.access_token);
+    if (!details)
+        return res.sendStatus(500);
+    let user = yield User.findOne({ where: { email: details.email } });
+    if (!user) {
+        user = yield User.register({
+            email: details.email,
+            username: details.username,
+            req
+        });
+    }
+    const token = yield generateToken(user.id);
+    res.cookie("token", token);
+    res.sendFile(path.join(__dirname, "../public/login.html"));
+}));
+app.get("*", (req, res) => {
+    res.sendFile(path.join(__dirname, "../public/login.html"));
+});
+(() => __awaiter(void 0, void 0, void 0, function* () {
+    yield initDatabase();
+    yield Config.init();
+    app.listen(port, () => {
+        console.log(`Listening on port ${port}`);
+    });
+}))();
+//# sourceMappingURL=index.js.map
\ No newline at end of file
diff --git a/slowcord/build/index.js.map b/slowcord/build/index.js.map
new file mode 100644
index 00000000..ca656f86
--- /dev/null
+++ b/slowcord/build/index.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,OAAO,eAAe,CAAC;AACvB,OAAO,OAA8B,MAAM,SAAS,CAAC;AACrD,OAAO,YAAY,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC3E,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,KAAK,MAAM,YAAY,CAAC;AAE/B,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;AACtB,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC;AACxB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;AAE9B,MAAM,OAAO;;;AACL,sBAAc,GAAG,CAAO,GAAY,EAAE,GAAa,EAAE,EAAE;IAC7D,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC;IAE3B,MAAM,IAAI,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC;QAC/C,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,iBAA2B;QAClD,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,cAAwB;QACnD,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,gBAA0B;QACpD,IAAI,EAAE,IAAc;QACpB,UAAU,EAAE,oBAAoB;KAChC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IAEf,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,sCAAsC,EAAE;QAChE,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACR,cAAc,EAAE,mCAAmC;SACnD;QACD,IAAI,EAAE,IAAI;KACV,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAS,CAAC;IACtC,IAAI,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAE5B,OAAO;QACN,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,KAAK,EAAE,IAAI,CAAC,KAAK;KACjB,CAAC;AACH,CAAC,CAAC,CAAA;AAEK,sBAAc,GAAG,CAAO,KAAa,EAAE,EAAE;IAC/C,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,mCAAmC,EAAE;QAC7D,OAAO,EAAE;YACR,eAAe,EAAE,UAAU,KAAK,EAAE;SAClC;KACD,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAS,CAAC;IACtC,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC,CAAC,+BAA+B;IAE/E,OAAO;QACN,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACvB,CAAC;AACH,CAAC,CAAC,CAAA;AAGH,MAAM,QAAQ,GAA4B;IACzC,SAAS,EAAE,OAAO;CAClB,CAAC;AAEF,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,CAAO,GAAG,EAAE,GAAG,EAAE,EAAE;IAC1C,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;IAC5B,IAAI,CAAC,IAAI;QAAE,OAAO,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,CAAC,OAAO;QAAE,OAAO,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAEzC,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACpD,IAAI,CAAC,IAAI;QAAE,OAAO,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAEtC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAChE,IAAI,CAAC,OAAO;QAAE,OAAO,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAEzC,IAAI,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACnE,IAAI,CAAC,IAAI,EAAE;QACV,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC;YAC1B,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,GAAG;SACH,CAAC,CAAC;KACH;IAED,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAE3C,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAE3B,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC,CAAC;AAC5D,CAAC,CAAA,CAAC,CAAC;AAEH,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IACzB,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC,CAAC;AAC5D,CAAC,CAAC,CAAC;AAEH,CAAC,GAAS,EAAE;IACX,MAAM,YAAY,EAAE,CAAC;IACrB,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;IAEpB,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QACrB,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,EAAE,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACJ,CAAC,CAAA,CAAC,EAAE,CAAC"}
\ No newline at end of file
diff --git a/slowcord/package-lock.json b/slowcord/package-lock.json
new file mode 100644
index 00000000..7ec24a87
--- /dev/null
+++ b/slowcord/package-lock.json
@@ -0,0 +1,1435 @@
+{
+	"name": "slowcord",
+	"version": "1.0.0",
+	"lockfileVersion": 2,
+	"requires": true,
+	"packages": {
+		"": {
+			"name": "slowcord",
+			"version": "1.0.0",
+			"license": "AGPL-3.0-only",
+			"dependencies": {
+				"@fosscord/util": "file:../util",
+				"cookie-parser": "^1.4.6",
+				"dotenv": "^16.0.1",
+				"express": "^4.18.1",
+				"node-fetch": "^3.2.6"
+			},
+			"devDependencies": {
+				"@types/cookie-parser": "^1.4.3",
+				"@types/express": "^4.17.13",
+				"@types/node": "^18.0.0"
+			}
+		},
+		"../util": {
+			"name": "@fosscord/util",
+			"version": "1.0.0",
+			"hasInstallScript": true,
+			"license": "AGPL-3.0-only",
+			"dependencies": {
+				"amqplib": "^0.8.0",
+				"form-data": "^4.0.0",
+				"jsonwebtoken": "^8.5.1",
+				"lambert-server": "^1.2.12",
+				"missing-native-js-functions": "^1.2.18",
+				"multer": "^1.4.3",
+				"node-fetch": "^2.6.2",
+				"patch-package": "^6.4.7",
+				"pg": "^8.7.1",
+				"picocolors": "^1.0.0",
+				"proxy-agent": "^5.0.0",
+				"reflect-metadata": "^0.1.13",
+				"typeorm": "^0.2.38",
+				"typescript": "^4.4.2",
+				"typescript-json-schema": "^0.50.1"
+			},
+			"devDependencies": {
+				"@types/amqplib": "^0.8.1",
+				"@types/jsonwebtoken": "^8.5.0",
+				"@types/multer": "^1.4.7",
+				"@types/node": "^14.17.9",
+				"@types/node-fetch": "^2.5.12",
+				"jest": "^27.0.6",
+				"ts-node": "^10.2.1"
+			}
+		},
+		"node_modules/@fosscord/util": {
+			"resolved": "../util",
+			"link": true
+		},
+		"node_modules/@types/body-parser": {
+			"version": "1.19.2",
+			"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
+			"integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==",
+			"dev": true,
+			"dependencies": {
+				"@types/connect": "*",
+				"@types/node": "*"
+			}
+		},
+		"node_modules/@types/connect": {
+			"version": "3.4.35",
+			"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
+			"integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==",
+			"dev": true,
+			"dependencies": {
+				"@types/node": "*"
+			}
+		},
+		"node_modules/@types/cookie-parser": {
+			"version": "1.4.3",
+			"resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz",
+			"integrity": "sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==",
+			"dev": true,
+			"dependencies": {
+				"@types/express": "*"
+			}
+		},
+		"node_modules/@types/express": {
+			"version": "4.17.13",
+			"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz",
+			"integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==",
+			"dev": true,
+			"dependencies": {
+				"@types/body-parser": "*",
+				"@types/express-serve-static-core": "^4.17.18",
+				"@types/qs": "*",
+				"@types/serve-static": "*"
+			}
+		},
+		"node_modules/@types/express-serve-static-core": {
+			"version": "4.17.29",
+			"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.29.tgz",
+			"integrity": "sha512-uMd++6dMKS32EOuw1Uli3e3BPgdLIXmezcfHv7N4c1s3gkhikBplORPpMq3fuWkxncZN1reb16d5n8yhQ80x7Q==",
+			"dev": true,
+			"dependencies": {
+				"@types/node": "*",
+				"@types/qs": "*",
+				"@types/range-parser": "*"
+			}
+		},
+		"node_modules/@types/mime": {
+			"version": "1.3.2",
+			"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
+			"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==",
+			"dev": true
+		},
+		"node_modules/@types/node": {
+			"version": "18.0.0",
+			"resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz",
+			"integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==",
+			"dev": true
+		},
+		"node_modules/@types/qs": {
+			"version": "6.9.7",
+			"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
+			"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
+			"dev": true
+		},
+		"node_modules/@types/range-parser": {
+			"version": "1.2.4",
+			"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
+			"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
+			"dev": true
+		},
+		"node_modules/@types/serve-static": {
+			"version": "1.13.10",
+			"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz",
+			"integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==",
+			"dev": true,
+			"dependencies": {
+				"@types/mime": "^1",
+				"@types/node": "*"
+			}
+		},
+		"node_modules/accepts": {
+			"version": "1.3.8",
+			"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+			"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+			"dependencies": {
+				"mime-types": "~2.1.34",
+				"negotiator": "0.6.3"
+			},
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"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/body-parser": {
+			"version": "1.20.0",
+			"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
+			"integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==",
+			"dependencies": {
+				"bytes": "3.1.2",
+				"content-type": "~1.0.4",
+				"debug": "2.6.9",
+				"depd": "2.0.0",
+				"destroy": "1.2.0",
+				"http-errors": "2.0.0",
+				"iconv-lite": "0.4.24",
+				"on-finished": "2.4.1",
+				"qs": "6.10.3",
+				"raw-body": "2.5.1",
+				"type-is": "~1.6.18",
+				"unpipe": "1.0.0"
+			},
+			"engines": {
+				"node": ">= 0.8",
+				"npm": "1.2.8000 || >= 1.4.16"
+			}
+		},
+		"node_modules/bytes": {
+			"version": "3.1.2",
+			"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+			"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/call-bind": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+			"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+			"dependencies": {
+				"function-bind": "^1.1.1",
+				"get-intrinsic": "^1.0.2"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/content-disposition": {
+			"version": "0.5.4",
+			"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+			"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+			"dependencies": {
+				"safe-buffer": "5.2.1"
+			},
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/content-type": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+			"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/cookie": {
+			"version": "0.5.0",
+			"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+			"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/cookie-parser": {
+			"version": "1.4.6",
+			"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
+			"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
+			"dependencies": {
+				"cookie": "0.4.1",
+				"cookie-signature": "1.0.6"
+			},
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/cookie-parser/node_modules/cookie": {
+			"version": "0.4.1",
+			"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
+			"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/cookie-signature": {
+			"version": "1.0.6",
+			"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+			"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
+		},
+		"node_modules/data-uri-to-buffer": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz",
+			"integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==",
+			"engines": {
+				"node": ">= 12"
+			}
+		},
+		"node_modules/debug": {
+			"version": "2.6.9",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+			"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+			"dependencies": {
+				"ms": "2.0.0"
+			}
+		},
+		"node_modules/depd": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+			"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/destroy": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+			"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+			"engines": {
+				"node": ">= 0.8",
+				"npm": "1.2.8000 || >= 1.4.16"
+			}
+		},
+		"node_modules/dotenv": {
+			"version": "16.0.1",
+			"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz",
+			"integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==",
+			"engines": {
+				"node": ">=12"
+			}
+		},
+		"node_modules/ee-first": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+			"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+		},
+		"node_modules/encodeurl": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+			"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/escape-html": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+			"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+		},
+		"node_modules/etag": {
+			"version": "1.8.1",
+			"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+			"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/express": {
+			"version": "4.18.1",
+			"resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz",
+			"integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==",
+			"dependencies": {
+				"accepts": "~1.3.8",
+				"array-flatten": "1.1.1",
+				"body-parser": "1.20.0",
+				"content-disposition": "0.5.4",
+				"content-type": "~1.0.4",
+				"cookie": "0.5.0",
+				"cookie-signature": "1.0.6",
+				"debug": "2.6.9",
+				"depd": "2.0.0",
+				"encodeurl": "~1.0.2",
+				"escape-html": "~1.0.3",
+				"etag": "~1.8.1",
+				"finalhandler": "1.2.0",
+				"fresh": "0.5.2",
+				"http-errors": "2.0.0",
+				"merge-descriptors": "1.0.1",
+				"methods": "~1.1.2",
+				"on-finished": "2.4.1",
+				"parseurl": "~1.3.3",
+				"path-to-regexp": "0.1.7",
+				"proxy-addr": "~2.0.7",
+				"qs": "6.10.3",
+				"range-parser": "~1.2.1",
+				"safe-buffer": "5.2.1",
+				"send": "0.18.0",
+				"serve-static": "1.15.0",
+				"setprototypeof": "1.2.0",
+				"statuses": "2.0.1",
+				"type-is": "~1.6.18",
+				"utils-merge": "1.0.1",
+				"vary": "~1.1.2"
+			},
+			"engines": {
+				"node": ">= 0.10.0"
+			}
+		},
+		"node_modules/fetch-blob": {
+			"version": "3.1.5",
+			"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.5.tgz",
+			"integrity": "sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==",
+			"funding": [
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/jimmywarting"
+				},
+				{
+					"type": "paypal",
+					"url": "https://paypal.me/jimmywarting"
+				}
+			],
+			"dependencies": {
+				"node-domexception": "^1.0.0",
+				"web-streams-polyfill": "^3.0.3"
+			},
+			"engines": {
+				"node": "^12.20 || >= 14.13"
+			}
+		},
+		"node_modules/finalhandler": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+			"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+			"dependencies": {
+				"debug": "2.6.9",
+				"encodeurl": "~1.0.2",
+				"escape-html": "~1.0.3",
+				"on-finished": "2.4.1",
+				"parseurl": "~1.3.3",
+				"statuses": "2.0.1",
+				"unpipe": "~1.0.0"
+			},
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/formdata-polyfill": {
+			"version": "4.0.10",
+			"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+			"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+			"dependencies": {
+				"fetch-blob": "^3.1.2"
+			},
+			"engines": {
+				"node": ">=12.20.0"
+			}
+		},
+		"node_modules/forwarded": {
+			"version": "0.2.0",
+			"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+			"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/fresh": {
+			"version": "0.5.2",
+			"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+			"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/function-bind": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+			"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+		},
+		"node_modules/get-intrinsic": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz",
+			"integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==",
+			"dependencies": {
+				"function-bind": "^1.1.1",
+				"has": "^1.0.3",
+				"has-symbols": "^1.0.3"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/has": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+			"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+			"dependencies": {
+				"function-bind": "^1.1.1"
+			},
+			"engines": {
+				"node": ">= 0.4.0"
+			}
+		},
+		"node_modules/has-symbols": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+			"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+			"engines": {
+				"node": ">= 0.4"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/http-errors": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+			"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+			"dependencies": {
+				"depd": "2.0.0",
+				"inherits": "2.0.4",
+				"setprototypeof": "1.2.0",
+				"statuses": "2.0.1",
+				"toidentifier": "1.0.1"
+			},
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/iconv-lite": {
+			"version": "0.4.24",
+			"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+			"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+			"dependencies": {
+				"safer-buffer": ">= 2.1.2 < 3"
+			},
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/inherits": {
+			"version": "2.0.4",
+			"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+			"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+		},
+		"node_modules/ipaddr.js": {
+			"version": "1.9.1",
+			"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+			"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+			"engines": {
+				"node": ">= 0.10"
+			}
+		},
+		"node_modules/media-typer": {
+			"version": "0.3.0",
+			"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+			"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/merge-descriptors": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+			"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+		},
+		"node_modules/methods": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+			"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/mime": {
+			"version": "1.6.0",
+			"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+			"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+			"bin": {
+				"mime": "cli.js"
+			},
+			"engines": {
+				"node": ">=4"
+			}
+		},
+		"node_modules/mime-db": {
+			"version": "1.52.0",
+			"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+			"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/mime-types": {
+			"version": "2.1.35",
+			"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+			"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+			"dependencies": {
+				"mime-db": "1.52.0"
+			},
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/ms": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+			"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+		},
+		"node_modules/negotiator": {
+			"version": "0.6.3",
+			"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+			"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/node-domexception": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+			"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+			"funding": [
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/jimmywarting"
+				},
+				{
+					"type": "github",
+					"url": "https://paypal.me/jimmywarting"
+				}
+			],
+			"engines": {
+				"node": ">=10.5.0"
+			}
+		},
+		"node_modules/node-fetch": {
+			"version": "3.2.6",
+			"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.6.tgz",
+			"integrity": "sha512-LAy/HZnLADOVkVPubaxHDft29booGglPFDr2Hw0J1AercRh01UiVFm++KMDnJeH9sHgNB4hsXPii7Sgym/sTbw==",
+			"dependencies": {
+				"data-uri-to-buffer": "^4.0.0",
+				"fetch-blob": "^3.1.4",
+				"formdata-polyfill": "^4.0.10"
+			},
+			"engines": {
+				"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/node-fetch"
+			}
+		},
+		"node_modules/object-inspect": {
+			"version": "1.12.2",
+			"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
+			"integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/on-finished": {
+			"version": "2.4.1",
+			"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+			"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+			"dependencies": {
+				"ee-first": "1.1.1"
+			},
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/parseurl": {
+			"version": "1.3.3",
+			"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+			"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/path-to-regexp": {
+			"version": "0.1.7",
+			"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+			"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+		},
+		"node_modules/proxy-addr": {
+			"version": "2.0.7",
+			"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+			"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+			"dependencies": {
+				"forwarded": "0.2.0",
+				"ipaddr.js": "1.9.1"
+			},
+			"engines": {
+				"node": ">= 0.10"
+			}
+		},
+		"node_modules/qs": {
+			"version": "6.10.3",
+			"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
+			"integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==",
+			"dependencies": {
+				"side-channel": "^1.0.4"
+			},
+			"engines": {
+				"node": ">=0.6"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/range-parser": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+			"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/raw-body": {
+			"version": "2.5.1",
+			"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
+			"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
+			"dependencies": {
+				"bytes": "3.1.2",
+				"http-errors": "2.0.0",
+				"iconv-lite": "0.4.24",
+				"unpipe": "1.0.0"
+			},
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/safe-buffer": {
+			"version": "5.2.1",
+			"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+			"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+			"funding": [
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/feross"
+				},
+				{
+					"type": "patreon",
+					"url": "https://www.patreon.com/feross"
+				},
+				{
+					"type": "consulting",
+					"url": "https://feross.org/support"
+				}
+			]
+		},
+		"node_modules/safer-buffer": {
+			"version": "2.1.2",
+			"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+			"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+		},
+		"node_modules/send": {
+			"version": "0.18.0",
+			"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+			"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+			"dependencies": {
+				"debug": "2.6.9",
+				"depd": "2.0.0",
+				"destroy": "1.2.0",
+				"encodeurl": "~1.0.2",
+				"escape-html": "~1.0.3",
+				"etag": "~1.8.1",
+				"fresh": "0.5.2",
+				"http-errors": "2.0.0",
+				"mime": "1.6.0",
+				"ms": "2.1.3",
+				"on-finished": "2.4.1",
+				"range-parser": "~1.2.1",
+				"statuses": "2.0.1"
+			},
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/send/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=="
+		},
+		"node_modules/serve-static": {
+			"version": "1.15.0",
+			"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
+			"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+			"dependencies": {
+				"encodeurl": "~1.0.2",
+				"escape-html": "~1.0.3",
+				"parseurl": "~1.3.3",
+				"send": "0.18.0"
+			},
+			"engines": {
+				"node": ">= 0.8.0"
+			}
+		},
+		"node_modules/setprototypeof": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+			"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+		},
+		"node_modules/side-channel": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+			"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+			"dependencies": {
+				"call-bind": "^1.0.0",
+				"get-intrinsic": "^1.0.2",
+				"object-inspect": "^1.9.0"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/ljharb"
+			}
+		},
+		"node_modules/statuses": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+			"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/toidentifier": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+			"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+			"engines": {
+				"node": ">=0.6"
+			}
+		},
+		"node_modules/type-is": {
+			"version": "1.6.18",
+			"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+			"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+			"dependencies": {
+				"media-typer": "0.3.0",
+				"mime-types": "~2.1.24"
+			},
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/unpipe": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+			"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/utils-merge": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+			"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+			"engines": {
+				"node": ">= 0.4.0"
+			}
+		},
+		"node_modules/vary": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+			"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/web-streams-polyfill": {
+			"version": "3.2.1",
+			"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
+			"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
+			"engines": {
+				"node": ">= 8"
+			}
+		}
+	},
+	"dependencies": {
+		"@fosscord/util": {
+			"version": "file:../util",
+			"requires": {
+				"@types/amqplib": "^0.8.1",
+				"@types/jsonwebtoken": "^8.5.0",
+				"@types/multer": "^1.4.7",
+				"@types/node": "^14.17.9",
+				"@types/node-fetch": "^2.5.12",
+				"amqplib": "^0.8.0",
+				"form-data": "^4.0.0",
+				"jest": "^27.0.6",
+				"jsonwebtoken": "^8.5.1",
+				"lambert-server": "^1.2.12",
+				"missing-native-js-functions": "^1.2.18",
+				"multer": "^1.4.3",
+				"node-fetch": "^2.6.2",
+				"patch-package": "^6.4.7",
+				"pg": "^8.7.1",
+				"picocolors": "^1.0.0",
+				"proxy-agent": "^5.0.0",
+				"reflect-metadata": "^0.1.13",
+				"ts-node": "^10.2.1",
+				"typeorm": "^0.2.38",
+				"typescript": "^4.4.2",
+				"typescript-json-schema": "^0.50.1"
+			}
+		},
+		"@types/body-parser": {
+			"version": "1.19.2",
+			"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
+			"integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==",
+			"dev": true,
+			"requires": {
+				"@types/connect": "*",
+				"@types/node": "*"
+			}
+		},
+		"@types/connect": {
+			"version": "3.4.35",
+			"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
+			"integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==",
+			"dev": true,
+			"requires": {
+				"@types/node": "*"
+			}
+		},
+		"@types/cookie-parser": {
+			"version": "1.4.3",
+			"resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz",
+			"integrity": "sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==",
+			"dev": true,
+			"requires": {
+				"@types/express": "*"
+			}
+		},
+		"@types/express": {
+			"version": "4.17.13",
+			"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz",
+			"integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==",
+			"dev": true,
+			"requires": {
+				"@types/body-parser": "*",
+				"@types/express-serve-static-core": "^4.17.18",
+				"@types/qs": "*",
+				"@types/serve-static": "*"
+			}
+		},
+		"@types/express-serve-static-core": {
+			"version": "4.17.29",
+			"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.29.tgz",
+			"integrity": "sha512-uMd++6dMKS32EOuw1Uli3e3BPgdLIXmezcfHv7N4c1s3gkhikBplORPpMq3fuWkxncZN1reb16d5n8yhQ80x7Q==",
+			"dev": true,
+			"requires": {
+				"@types/node": "*",
+				"@types/qs": "*",
+				"@types/range-parser": "*"
+			}
+		},
+		"@types/mime": {
+			"version": "1.3.2",
+			"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
+			"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==",
+			"dev": true
+		},
+		"@types/node": {
+			"version": "18.0.0",
+			"resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz",
+			"integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==",
+			"dev": true
+		},
+		"@types/qs": {
+			"version": "6.9.7",
+			"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
+			"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
+			"dev": true
+		},
+		"@types/range-parser": {
+			"version": "1.2.4",
+			"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
+			"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
+			"dev": true
+		},
+		"@types/serve-static": {
+			"version": "1.13.10",
+			"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz",
+			"integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==",
+			"dev": true,
+			"requires": {
+				"@types/mime": "^1",
+				"@types/node": "*"
+			}
+		},
+		"accepts": {
+			"version": "1.3.8",
+			"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+			"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+			"requires": {
+				"mime-types": "~2.1.34",
+				"negotiator": "0.6.3"
+			}
+		},
+		"array-flatten": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+			"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
+		},
+		"body-parser": {
+			"version": "1.20.0",
+			"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
+			"integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==",
+			"requires": {
+				"bytes": "3.1.2",
+				"content-type": "~1.0.4",
+				"debug": "2.6.9",
+				"depd": "2.0.0",
+				"destroy": "1.2.0",
+				"http-errors": "2.0.0",
+				"iconv-lite": "0.4.24",
+				"on-finished": "2.4.1",
+				"qs": "6.10.3",
+				"raw-body": "2.5.1",
+				"type-is": "~1.6.18",
+				"unpipe": "1.0.0"
+			}
+		},
+		"bytes": {
+			"version": "3.1.2",
+			"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+			"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
+		},
+		"call-bind": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+			"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+			"requires": {
+				"function-bind": "^1.1.1",
+				"get-intrinsic": "^1.0.2"
+			}
+		},
+		"content-disposition": {
+			"version": "0.5.4",
+			"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+			"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+			"requires": {
+				"safe-buffer": "5.2.1"
+			}
+		},
+		"content-type": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+			"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
+		},
+		"cookie": {
+			"version": "0.5.0",
+			"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+			"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
+		},
+		"cookie-parser": {
+			"version": "1.4.6",
+			"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
+			"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
+			"requires": {
+				"cookie": "0.4.1",
+				"cookie-signature": "1.0.6"
+			},
+			"dependencies": {
+				"cookie": {
+					"version": "0.4.1",
+					"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
+					"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
+				}
+			}
+		},
+		"cookie-signature": {
+			"version": "1.0.6",
+			"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+			"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
+		},
+		"data-uri-to-buffer": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz",
+			"integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA=="
+		},
+		"debug": {
+			"version": "2.6.9",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+			"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+			"requires": {
+				"ms": "2.0.0"
+			}
+		},
+		"depd": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+			"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
+		},
+		"destroy": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+			"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
+		},
+		"dotenv": {
+			"version": "16.0.1",
+			"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz",
+			"integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ=="
+		},
+		"ee-first": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+			"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+		},
+		"encodeurl": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+			"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
+		},
+		"escape-html": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+			"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+		},
+		"etag": {
+			"version": "1.8.1",
+			"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+			"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
+		},
+		"express": {
+			"version": "4.18.1",
+			"resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz",
+			"integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==",
+			"requires": {
+				"accepts": "~1.3.8",
+				"array-flatten": "1.1.1",
+				"body-parser": "1.20.0",
+				"content-disposition": "0.5.4",
+				"content-type": "~1.0.4",
+				"cookie": "0.5.0",
+				"cookie-signature": "1.0.6",
+				"debug": "2.6.9",
+				"depd": "2.0.0",
+				"encodeurl": "~1.0.2",
+				"escape-html": "~1.0.3",
+				"etag": "~1.8.1",
+				"finalhandler": "1.2.0",
+				"fresh": "0.5.2",
+				"http-errors": "2.0.0",
+				"merge-descriptors": "1.0.1",
+				"methods": "~1.1.2",
+				"on-finished": "2.4.1",
+				"parseurl": "~1.3.3",
+				"path-to-regexp": "0.1.7",
+				"proxy-addr": "~2.0.7",
+				"qs": "6.10.3",
+				"range-parser": "~1.2.1",
+				"safe-buffer": "5.2.1",
+				"send": "0.18.0",
+				"serve-static": "1.15.0",
+				"setprototypeof": "1.2.0",
+				"statuses": "2.0.1",
+				"type-is": "~1.6.18",
+				"utils-merge": "1.0.1",
+				"vary": "~1.1.2"
+			}
+		},
+		"fetch-blob": {
+			"version": "3.1.5",
+			"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.5.tgz",
+			"integrity": "sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==",
+			"requires": {
+				"node-domexception": "^1.0.0",
+				"web-streams-polyfill": "^3.0.3"
+			}
+		},
+		"finalhandler": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+			"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+			"requires": {
+				"debug": "2.6.9",
+				"encodeurl": "~1.0.2",
+				"escape-html": "~1.0.3",
+				"on-finished": "2.4.1",
+				"parseurl": "~1.3.3",
+				"statuses": "2.0.1",
+				"unpipe": "~1.0.0"
+			}
+		},
+		"formdata-polyfill": {
+			"version": "4.0.10",
+			"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+			"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+			"requires": {
+				"fetch-blob": "^3.1.2"
+			}
+		},
+		"forwarded": {
+			"version": "0.2.0",
+			"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+			"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
+		},
+		"fresh": {
+			"version": "0.5.2",
+			"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+			"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="
+		},
+		"function-bind": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+			"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+		},
+		"get-intrinsic": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz",
+			"integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==",
+			"requires": {
+				"function-bind": "^1.1.1",
+				"has": "^1.0.3",
+				"has-symbols": "^1.0.3"
+			}
+		},
+		"has": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+			"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+			"requires": {
+				"function-bind": "^1.1.1"
+			}
+		},
+		"has-symbols": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+			"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
+		},
+		"http-errors": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+			"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+			"requires": {
+				"depd": "2.0.0",
+				"inherits": "2.0.4",
+				"setprototypeof": "1.2.0",
+				"statuses": "2.0.1",
+				"toidentifier": "1.0.1"
+			}
+		},
+		"iconv-lite": {
+			"version": "0.4.24",
+			"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+			"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+			"requires": {
+				"safer-buffer": ">= 2.1.2 < 3"
+			}
+		},
+		"inherits": {
+			"version": "2.0.4",
+			"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+			"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+		},
+		"ipaddr.js": {
+			"version": "1.9.1",
+			"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+			"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
+		},
+		"media-typer": {
+			"version": "0.3.0",
+			"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+			"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="
+		},
+		"merge-descriptors": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+			"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+		},
+		"methods": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+			"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
+		},
+		"mime": {
+			"version": "1.6.0",
+			"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+			"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
+		},
+		"mime-db": {
+			"version": "1.52.0",
+			"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+			"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
+		},
+		"mime-types": {
+			"version": "2.1.35",
+			"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+			"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+			"requires": {
+				"mime-db": "1.52.0"
+			}
+		},
+		"ms": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+			"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+		},
+		"negotiator": {
+			"version": "0.6.3",
+			"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+			"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
+		},
+		"node-domexception": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+			"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="
+		},
+		"node-fetch": {
+			"version": "3.2.6",
+			"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.6.tgz",
+			"integrity": "sha512-LAy/HZnLADOVkVPubaxHDft29booGglPFDr2Hw0J1AercRh01UiVFm++KMDnJeH9sHgNB4hsXPii7Sgym/sTbw==",
+			"requires": {
+				"data-uri-to-buffer": "^4.0.0",
+				"fetch-blob": "^3.1.4",
+				"formdata-polyfill": "^4.0.10"
+			}
+		},
+		"object-inspect": {
+			"version": "1.12.2",
+			"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
+			"integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ=="
+		},
+		"on-finished": {
+			"version": "2.4.1",
+			"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+			"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+			"requires": {
+				"ee-first": "1.1.1"
+			}
+		},
+		"parseurl": {
+			"version": "1.3.3",
+			"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+			"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
+		},
+		"path-to-regexp": {
+			"version": "0.1.7",
+			"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+			"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+		},
+		"proxy-addr": {
+			"version": "2.0.7",
+			"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+			"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+			"requires": {
+				"forwarded": "0.2.0",
+				"ipaddr.js": "1.9.1"
+			}
+		},
+		"qs": {
+			"version": "6.10.3",
+			"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
+			"integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==",
+			"requires": {
+				"side-channel": "^1.0.4"
+			}
+		},
+		"range-parser": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+			"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
+		},
+		"raw-body": {
+			"version": "2.5.1",
+			"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
+			"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
+			"requires": {
+				"bytes": "3.1.2",
+				"http-errors": "2.0.0",
+				"iconv-lite": "0.4.24",
+				"unpipe": "1.0.0"
+			}
+		},
+		"safe-buffer": {
+			"version": "5.2.1",
+			"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+			"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+		},
+		"safer-buffer": {
+			"version": "2.1.2",
+			"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+			"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+		},
+		"send": {
+			"version": "0.18.0",
+			"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+			"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+			"requires": {
+				"debug": "2.6.9",
+				"depd": "2.0.0",
+				"destroy": "1.2.0",
+				"encodeurl": "~1.0.2",
+				"escape-html": "~1.0.3",
+				"etag": "~1.8.1",
+				"fresh": "0.5.2",
+				"http-errors": "2.0.0",
+				"mime": "1.6.0",
+				"ms": "2.1.3",
+				"on-finished": "2.4.1",
+				"range-parser": "~1.2.1",
+				"statuses": "2.0.1"
+			},
+			"dependencies": {
+				"ms": {
+					"version": "2.1.3",
+					"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+					"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+				}
+			}
+		},
+		"serve-static": {
+			"version": "1.15.0",
+			"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
+			"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+			"requires": {
+				"encodeurl": "~1.0.2",
+				"escape-html": "~1.0.3",
+				"parseurl": "~1.3.3",
+				"send": "0.18.0"
+			}
+		},
+		"setprototypeof": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+			"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+		},
+		"side-channel": {
+			"version": "1.0.4",
+			"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+			"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+			"requires": {
+				"call-bind": "^1.0.0",
+				"get-intrinsic": "^1.0.2",
+				"object-inspect": "^1.9.0"
+			}
+		},
+		"statuses": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+			"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
+		},
+		"toidentifier": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+			"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
+		},
+		"type-is": {
+			"version": "1.6.18",
+			"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+			"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+			"requires": {
+				"media-typer": "0.3.0",
+				"mime-types": "~2.1.24"
+			}
+		},
+		"unpipe": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+			"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
+		},
+		"utils-merge": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+			"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
+		},
+		"vary": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+			"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
+		},
+		"web-streams-polyfill": {
+			"version": "3.2.1",
+			"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
+			"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q=="
+		}
+	}
+}
diff --git a/slowcord/package.json b/slowcord/package.json
new file mode 100644
index 00000000..9a64c2b3
--- /dev/null
+++ b/slowcord/package.json
@@ -0,0 +1,33 @@
+{
+	"name": "slowcord",
+	"version": "1.0.0",
+	"description": "Slowcord additional services",
+	"main": "build/index.js",
+	"scripts": {
+		"build": "tsc -b",
+		"run": "node build/index.js"
+	},
+	"repository": {
+		"type": "git",
+		"url": "git+https://github.com/maddyunderstars/fosscord-server.git"
+	},
+	"author": "",
+	"license": "AGPL-3.0-only",
+	"bugs": {
+		"url": "https://github.com/maddyunderstars/fosscord-server/issues"
+	},
+	"homepage": "https://github.com/maddyunderstars/fosscord-server#readme",
+	"dependencies": {
+		"@fosscord/util": "file:../util",
+		"cookie-parser": "^1.4.6",
+		"dotenv": "^16.0.1",
+		"express": "^4.18.1",
+		"node-fetch": "^3.2.6"
+	},
+	"devDependencies": {
+		"@types/cookie-parser": "^1.4.3",
+		"@types/express": "^4.17.13",
+		"@types/node": "^18.0.0"
+	},
+	"type": "module"
+}
diff --git a/slowcord/public/login.html b/slowcord/public/login.html
new file mode 100644
index 00000000..a695e597
--- /dev/null
+++ b/slowcord/public/login.html
@@ -0,0 +1,65 @@
+<html lang="en">
+
+<head>
+	<meta charset="UTF-8">
+	<meta http-equiv="X-UA-Compatible" content="IE=edge">
+	<meta name="viewport" content="width=device-width, initial-scale=1.0">
+	<title>Slowcord</title>
+</head>
+
+<body>
+	<div class="content">
+		<form action="javascript:void(0);">
+			<input type="email" name="email" />
+			<input type="password" name="password" />
+			<input type="submit" />
+
+			<a
+				href="https://discord.com/api/oauth2/authorize?client_id=990585211966324806&redirect_uri=https%3A%2F%2Fslowcord.maddy.k.vu%2Foauth%2Fdiscord&response_type=code&scope=identify%20email">
+				Login with Discord
+			</a>
+		</form>
+	</div>
+
+	<script>
+		/* https://stackoverflow.com/questions/5639346/what-is-the-shortest-function-for-reading-a-cookie-by-name-in-javascript */
+		const getCookieValue = (name) => (
+			document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)')?.pop() || ''
+		);
+
+		let token = getCookieValue("token");
+		if (token) {
+			document.cookie = "";	// don't care
+			window.localStorage.setItem("token", json.token);
+			window.location.href = "/app";
+		}
+
+		token = window.localStorage.getItem("token");
+		if (token) window.location.href = "/app";
+
+		document.forms[0].addEventListener("submit", async (e) => {
+			const data = new FormData(e.target);
+			const email = data.get("email");
+			const password = data.get("password");
+
+			const response = await fetch("/api/v9/auth/login", {
+				method: "POST",
+				headers: {
+					"Content-Type": "application/json",
+				},
+				body: JSON.stringify({
+					login: email,
+					password: password,
+				})
+			});
+
+			const json = response.json();
+			if (json.token) {
+				window.localStorage.setItem("token", json.token);
+				window.location.href = "/app";
+			}
+		})
+	</script>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/slowcord/src/index.ts b/slowcord/src/index.ts
new file mode 100644
index 00000000..3d397aaf
--- /dev/null
+++ b/slowcord/src/index.ts
@@ -0,0 +1,104 @@
+import "dotenv/config";
+import express, { Request, Response } from "express";
+import cookieParser from "cookie-parser";
+import { initDatabase, generateToken, User, Config } from "@fosscord/util";
+import path from "path";
+import fetch from "node-fetch";
+
+const app = express();
+app.use(cookieParser());
+const port = process.env.PORT;
+
+class Discord {
+	static getAccessToken = async (req: Request, res: Response) => {
+		const { code } = req.query;
+
+		const body = new URLSearchParams(Object.entries({
+			client_id: process.env.DISCORD_CLIENT_ID as string,
+			client_secret: process.env.DISCORD_SECRET as string,
+			redirect_uri: process.env.DISCORD_REDIRECT as string,
+			code: code as string,
+			grant_type: "authorization_code",
+		})).toString();
+
+		const resp = await fetch("https://discord.com/api/oauth2/token", {
+			method: "POST",
+			headers: {
+				"Content-Type": "application/x-www-form-urlencoded",
+			},
+			body: body
+		});
+
+		const json = await resp.json() as any;
+		if (json.error) return null;
+
+		return {
+			access_token: json.access_token,
+			token_type: json.token_type,
+			expires_in: json.expires_in,
+			refresh_token: json.refresh_token,
+			scope: json.scope,
+		};
+	};
+
+	static getUserDetails = async (token: string) => {
+		const resp = await fetch("https://discord.com/api/users/@me", {
+			headers: {
+				"Authorization": `Bearer ${token}`,
+			}
+		});
+
+		const json = await resp.json() as any;
+		if (!json.username || !json.email) return null;	// eh, deal with bad code later
+
+		return {
+			email: json.email,
+			username: json.username,
+		};
+	};
+}
+
+const handlers: { [key: string]: any; } = {
+	"discord": Discord,
+};
+
+app.get("/oauth/:type", async (req, res) => {
+	const { type } = req.params;
+	if (!type) return res.sendStatus(400);
+	const handler = handlers[type];
+	if (!handler) return res.sendStatus(400);
+
+	const data = await handler.getAccessToken(req, res);
+	if (!data) return res.sendStatus(500);
+
+	const details = await handler.getUserDetails(data.access_token);
+	if (!details) return res.sendStatus(500);
+
+	let user = await User.findOne({ where: { email: details.email } });
+	if (!user) {
+		user = await User.register({
+			email: details.email,
+			username: details.username,
+			req
+		});
+	}
+
+	const token = await generateToken(user.id);
+
+	res.cookie("token", token);
+
+	res.sendFile(path.join(__dirname, "../public/login.html"));
+});
+
+app.get("*", (req, res) => {
+	res.sendFile(path.join(__dirname, "../public/login.html"));
+});
+
+(async () => {
+	await initDatabase();
+	await Config.init();
+
+	app.listen(port, () => {
+		console.log(`Listening on port ${port}`);
+	});
+})();
\ No newline at end of file
diff --git a/slowcord/tsconfig.json b/slowcord/tsconfig.json
new file mode 100644
index 00000000..b8a5965d
--- /dev/null
+++ b/slowcord/tsconfig.json
@@ -0,0 +1,99 @@
+{
+	"exclude": [
+		"node_modules"
+	],
+	"include": [
+		"src/**/*.ts"
+	],
+	"compilerOptions": {
+		/* Visit https://aka.ms/tsconfig.json to read more about this file */
+		/* Projects */
+		// "incremental": true,                              /* Enable incremental compilation */
+		// "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
+		// "tsBuildInfoFile": "./",                          /* Specify the folder for .tsbuildinfo incremental compilation files. */
+		// "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects */
+		// "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
+		// "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */
+		/* Language and Environment */
+		"target": "ES6", 								/* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+		"lib": ["ES2021"],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+		// "jsx": "preserve",                                /* Specify what JSX code is generated. */
+		"experimentalDecorators": true,                   /* Enable experimental support for TC39 stage 2 draft decorators. */
+		// "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
+		// "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
+		// "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
+		// "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
+		// "reactNamespace": "",                             /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
+		// "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
+		// "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
+		/* Modules */
+		"module": "ES2020", 								/* Specify what module code is generated. */
+		// "rootDir": "./",                                  /* Specify the root folder within your source files. */
+		"moduleResolution": "node",                       /* Specify how TypeScript looks up a file from a given module specifier. */
+		// "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
+		// "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */
+		// "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
+		// "typeRoots": [],                                  /* Specify multiple folders that act like `./node_modules/@types`. */
+		"types": ["node"],                                      /* Specify type package names to be included without being referenced in a source file. */
+		// "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
+		// "resolveJsonModule": true,                        /* Enable importing .json files */
+		// "noResolve": true,                                /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
+		/* JavaScript Support */
+		// "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
+		// "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
+		// "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
+		/* Emit */
+		// "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
+		// "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
+		// "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
+		"sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
+		// "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
+		"outDir": "./build",                                   /* Specify an output folder for all emitted files. */
+		// "removeComments": true,                           /* Disable emitting comments. */
+		// "noEmit": true,                                   /* Disable emitting files from a compilation. */
+		// "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
+		// "importsNotUsedAsValues": "remove",               /* Specify emit/checking behavior for imports that are only used for types */
+		// "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
+		// "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
+		// "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
+		// "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
+		// "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
+		// "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
+		// "newLine": "crlf",                                /* Set the newline character for emitting files. */
+		// "stripInternal": true,                            /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
+		// "noEmitHelpers": true,                            /* Disable generating custom helper functions like `__extends` in compiled output. */
+		// "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
+		// "preserveConstEnums": true,                       /* Disable erasing `const enum` declarations in generated code. */
+		// "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */
+		// "preserveValueImports": true,                     /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
+		/* Interop Constraints */
+		// "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
+		// "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
+		"esModuleInterop": true, 							 /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
+		// "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
+		"forceConsistentCasingInFileNames": true, 			 /* Ensure that casing is correct in imports. */
+		/* Type Checking */
+		"strict": true, 									 /* Enable all strict type-checking options. */
+		// "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied `any` type.. */
+		// "strictNullChecks": true,                         /* When type checking, take into account `null` and `undefined`. */
+		// "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
+		// "strictBindCallApply": true,                      /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
+		"strictPropertyInitialization": false,             /* Check for class properties that are declared but not set in the constructor. */
+		// "noImplicitThis": true,                           /* Enable error reporting when `this` is given the type `any`. */
+		// "useUnknownInCatchVariables": true,               /* Type catch clause variables as 'unknown' instead of 'any'. */
+		// "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
+		// "noUnusedLocals": true,                           /* Enable error reporting when a local variables aren't read. */
+		// "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read */
+		// "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
+		// "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
+		// "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
+		// "noUncheckedIndexedAccess": true,                 /* Include 'undefined' in index signature results */
+		// "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
+		// "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type */
+		// "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
+		// "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */
+		/* Completeness */
+		// "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
+		"skipLibCheck": true								 /* Skip type checking all .d.ts files. */
+	}
+}
\ No newline at end of file