summary refs log tree commit diff
path: root/scripts
diff options
context:
space:
mode:
authorMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2022-08-30 15:13:18 +1000
committerMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2022-08-30 15:13:18 +1000
commitc2931f61aa0adb682ab023d85ba599099024d62b (patch)
tree86de9071cbded565fe9e082bd2cc6c611a926c6c /scripts
parentGuild join messages (diff)
parentOop, deprecated typeorm call (diff)
downloadserver-c2931f61aa0adb682ab023d85ba599099024d62b.tar.xz
Merge branch 'staging' into dev/Maddy/feat/welcomeMessages
Diffstat (limited to 'scripts')
-rw-r--r--scripts/benchmark.js26
-rw-r--r--scripts/benchmark/connections.js64
-rw-r--r--scripts/benchmark/index.js4
-rw-r--r--scripts/benchmark/users.js25
-rw-r--r--scripts/build.js116
-rw-r--r--scripts/build/clean.js18
-rw-r--r--scripts/build/compile_tsc.js48
-rw-r--r--scripts/build/plugin_prepare.js31
-rw-r--r--scripts/build/plugin_resources.js13
-rw-r--r--scripts/build/remap_imports.js15
-rw-r--r--scripts/build_new.js31
-rw-r--r--scripts/code_quality.js75
-rw-r--r--scripts/db_migrations.js80
-rw-r--r--scripts/depcheck.js50
-rw-r--r--scripts/depclean.js53
-rw-r--r--scripts/droptables.sql31
-rwxr-xr-xscripts/first_setup.js272
-rw-r--r--scripts/gen_index.js37
-rw-r--r--scripts/generate_openapi.js137
-rw-r--r--scripts/generate_schema.js90
-rw-r--r--scripts/migrate_db_engine.js109
-rw-r--r--scripts/rights.js34
-rw-r--r--scripts/stresstest/.gitignore3
-rw-r--r--scripts/stresstest/accounts.json.example1
-rw-r--r--scripts/stresstest/config.json.example5
-rw-r--r--scripts/stresstest/index.js38
-rw-r--r--scripts/stresstest/package-lock.json856
-rw-r--r--scripts/stresstest/package.json17
-rw-r--r--scripts/stresstest/src/login/index.js20
-rw-r--r--scripts/stresstest/src/message/send.js23
-rw-r--r--scripts/stresstest/src/register/index.js34
-rw-r--r--scripts/update_schemas.js9
-rw-r--r--scripts/utils.js87
-rw-r--r--scripts/utils/ask.js20
34 files changed, 2472 insertions, 0 deletions
diff --git a/scripts/benchmark.js b/scripts/benchmark.js
new file mode 100644
index 00000000..53db92c5
--- /dev/null
+++ b/scripts/benchmark.js
@@ -0,0 +1,26 @@
+const typeorm = require("typeorm");
+const Models = require("../dist/entities");
+const { PrimaryColumn } = require("typeorm");
+
+function shouldIncludeEntity(name) {
+	return ![Models.BaseClassWithoutId, PrimaryColumn, Models.BaseClass, Models.PrimaryGeneratedColumn].map((x) => x?.name).includes(name);
+}
+
+async function main() {
+	console.log("starting");
+	const db = new typeorm.DataSource({
+		type: "sqlite",
+		database: ":memory:",
+		entities: Object.values(Models).filter((x) => x.constructor.name == "Function" && shouldIncludeEntity(x.name)),
+		synchronize: true
+	});
+	await db.initialize();
+	console.log("Initialized database");
+
+	for (var i = 0; i < 100; i++) {
+		await Models.User.register({ username: "User" + i });
+		console.log("registered user " + i);
+	}
+}
+
+main();
diff --git a/scripts/benchmark/connections.js b/scripts/benchmark/connections.js
new file mode 100644
index 00000000..f74d0c6d
--- /dev/null
+++ b/scripts/benchmark/connections.js
@@ -0,0 +1,64 @@
+require("dotenv").config();
+const cluster = require("cluster");
+const WebSocket = require("ws");
+const endpoint = process.env.GATEWAY || "ws://localhost:3001";
+const connections = Number(process.env.CONNECTIONS) || 50;
+const token = process.env.TOKEN;
+let cores = 1;
+try {
+	cores = Number(process.env.THREADS) || os.cpus().length;
+} catch {
+	console.log("[Bundle] Failed to get thread count! Using 1...");
+}
+
+if (!token) {
+	console.error("TOKEN env var missing");
+	process.exit();
+}
+
+if (cluster.isMaster) {
+	for (let i = 0; i < threads; i++) {
+		cluster.fork();
+	}
+
+	cluster.on("exit", (worker, code, signal) => {
+		console.log(`worker ${worker.process.pid} died`);
+	});
+} else {
+	for (let i = 0; i < connections; i++) {
+		connect();
+	}
+}
+
+function connect() {
+	const client = new WebSocket(endpoint);
+	client.on("message", (data) => {
+		data = JSON.parse(data);
+
+		switch (data.op) {
+			case 10:
+				client.interval = setInterval(() => {
+					client.send(JSON.stringify({ op: 1 }));
+				}, data.d.heartbeat_interval);
+
+				client.send(
+					JSON.stringify({
+						op: 2,
+						d: {
+							token,
+							properties: {}
+						}
+					})
+				);
+
+				break;
+		}
+	});
+	client.once("close", (code, reason) => {
+		clearInterval(client.interval);
+		connect();
+	});
+	client.on("error", (err) => {
+		// console.log(err);
+	});
+}
diff --git a/scripts/benchmark/index.js b/scripts/benchmark/index.js
new file mode 100644
index 00000000..37ac5633
--- /dev/null
+++ b/scripts/benchmark/index.js
@@ -0,0 +1,4 @@
+require("dotenv").config();
+
+require("./connections");
+require("./messages");
diff --git a/scripts/benchmark/users.js b/scripts/benchmark/users.js
new file mode 100644
index 00000000..415d6d8b
--- /dev/null
+++ b/scripts/benchmark/users.js
@@ -0,0 +1,25 @@
+require("dotenv").config();
+const fetch = require("node-fetch");
+const count = Number(process.env.COUNT) || 50;
+const endpoint = process.env.API || "http://localhost:3001";
+
+async function main() {
+	for (let i = 0; i < count; i++) {
+		fetch(`${endpoint}/api/auth/register`, {
+			method: "POST",
+			body: JSON.stringify({
+				fingerprint: `${i}.wR8vi8lGlFBJerErO9LG5NViJFw`,
+				username: `test${i}`,
+				invite: null,
+				consent: true,
+				date_of_birth: "2000-01-01",
+				gift_code_sku_id: null,
+				captcha_key: null
+			}),
+			headers: { "content-type": "application/json" }
+		});
+		console.log(i);
+	}
+}
+
+main();
diff --git a/scripts/build.js b/scripts/build.js
new file mode 100644
index 00000000..f618100c
--- /dev/null
+++ b/scripts/build.js
@@ -0,0 +1,116 @@
+const { execSync } = require("child_process");
+const path = require("path");
+const fs = require("fs");
+const { argv, stdout, exit } = require("process");
+const { execIn, parts, getDirs, walk, sanitizeVarName } = require("./utils");
+
+if (argv.includes("help")) {
+	console.log(`Fosscord build script help:
+Arguments:
+  clean			Cleans up previous builds
+  verbose		Enable verbose logging
+  logerrors		Log build errors to console
+  pretty-errors		Pretty-print build errors
+  silent		No output to console or files.
+  propagate-err	Exit script with error code if build fails.`);
+	exit(0);
+}
+
+let steps = 5,
+	i = 0;
+if (argv.includes("clean")) steps++;
+
+const verbose = argv.includes("verbose") || argv.includes("v");
+const logerr = argv.includes("logerrors");
+const pretty = argv.includes("pretty-errors");
+const silent = argv.includes("silent");
+
+if (silent) console.error = console.log = function () {};
+
+if (argv.includes("clean")) {
+	console.log(`[${++i}/${steps}] Cleaning...`);
+	let d = "dist";
+	if (fs.existsSync(d)) {
+		fs.rmSync(d, { recursive: true });
+		if (verbose) console.log(`Deleted ${d}!`);
+	}
+}
+
+console.log(`[${++i}/${steps}] Compiling src files ...`);
+
+let buildFlags = "";
+if (pretty) buildFlags += "--pretty ";
+
+console.log(`[${++i}/${steps}] Building plugin index...`);
+let pluginDir = path.join(__dirname, "..", "src", "plugins");
+let output = 'import { Plugin } from "util/plugin";\n';
+
+const dirs = fs.readdirSync(pluginDir).filter((x) => {
+	try {
+		fs.readdirSync(path.join(pluginDir, x));
+		return true;
+	} catch (e) {
+		return false;
+	}
+});
+dirs.forEach((x) => {
+	let pluginManifest = require(path.join(pluginDir, x, "plugin.json"));
+	output += `import * as ${sanitizeVarName(x)} from "./${x}/${pluginManifest.mainClass}";\n`;
+});
+output += `\nexport const PluginIndex: any = {\n`;
+dirs.forEach((x) => {
+	output += `    "${x}": new ${sanitizeVarName(x)}.default(),\n`; //ctor test: '${path.resolve(path.join(pluginDir, x))}', require('./${x}/plugin.json')
+});
+output += `};`;
+
+fs.writeFileSync(path.join(__dirname, "..", "src", "plugins", "PluginIndex.ts"), output);
+
+if (!argv.includes("copyonly")) {
+	console.log(`[${++i}/${steps}] Compiling source code...`);
+
+	let buildFlags = "";
+	if (pretty) buildFlags += "--pretty ";
+
+	try {
+		execSync(
+			'node "' +
+				path.join(__dirname, "..", "node_modules", "typescript", "lib", "tsc.js") +
+				'" -p "' +
+				path.join(__dirname, "..") +
+				'" ' +
+				buildFlags,
+			{
+				cwd: path.join(__dirname, ".."),
+				shell: true,
+				env: process.env,
+				encoding: "utf8"
+			}
+		);
+	} catch (error) {
+		if (verbose || logerr) {
+			error.stdout.split(/\r?\n/).forEach((line) => {
+				let _line = line.replace("dist/", "", 1);
+				if (!pretty && _line.includes(".ts(")) {
+					//reformat file path for easy jumping
+					_line = _line.replace("(", ":", 1).replace(",", ":", 1).replace(")", "", 1);
+				}
+				console.error(_line);
+			});
+		}
+		console.error(`Build failed! Please check build.log for info!`);
+		if (!silent) {
+			if (pretty) fs.writeFileSync("build.log.ansi", error.stdout);
+			fs.writeFileSync(
+				"build.log",
+				error.stdout.replaceAll(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "")
+			);
+		}
+		throw error;
+	}
+}
+
+console.log(`[${++i}/${steps}] Copying plugin data...`);
+let pluginFiles = walk(pluginDir).filter((x) => !x.endsWith(".ts"));
+pluginFiles.forEach((x) => {
+	fs.copyFileSync(x, x.replace("src", "dist"));
+});
diff --git a/scripts/build/clean.js b/scripts/build/clean.js
new file mode 100644
index 00000000..92ec6d77
--- /dev/null
+++ b/scripts/build/clean.js
@@ -0,0 +1,18 @@
+const { execSync } = require("child_process");
+const path = require("path");
+const fs = require("fs");
+const { argv, stdout, exit } = require("process");
+const { execIn, parts, getDirs, walk, sanitizeVarName } = require("../utils");
+
+module.exports = function (config) {
+	if (fs.existsSync(config.buildLog)) fs.rmSync(config.buildLog);
+	if (fs.existsSync(config.buildLogAnsi)) fs.rmSync(config.buildLogAnsi);
+
+	if (config.clean) {
+		console.log(`==> Cleaning...`);
+		if (fs.existsSync(config.distDir)) {
+			fs.rmSync(config.distDir, { recursive: true });
+			if (config.verbose) console.log(`Deleted ${path.resolve(config.distDir)}!`);
+		}
+	}
+};
diff --git a/scripts/build/compile_tsc.js b/scripts/build/compile_tsc.js
new file mode 100644
index 00000000..179707a3
--- /dev/null
+++ b/scripts/build/compile_tsc.js
@@ -0,0 +1,48 @@
+const { execSync } = require("child_process");
+const path = require("path");
+const fs = require("fs");
+const { argv, stdout, exit } = require("process");
+const { execIn, parts, getDirs, walk, sanitizeVarName } = require("../utils");
+
+module.exports = function (config) {
+	console.log("==> Compiling source with tsc...");
+	let buildFlags = "";
+	if (config.pretty) buildFlags += "--pretty ";
+
+	try {
+		execSync(
+			'node "' +
+				path.join(config.rootDir, "node_modules", "typescript", "lib", "tsc.js") +
+				'" -p "' +
+				path.join(config.rootDir) +
+				'" ' +
+				buildFlags,
+			{
+				cwd: path.join(config.rootDir),
+				shell: true,
+				env: process.env,
+				encoding: "utf8"
+			}
+		);
+	} catch (error) {
+		if (config.verbose || config.logerr) {
+			error.stdout.split(/\r?\n/).forEach((line) => {
+				let _line = line.replace("dist/", "", 1);
+				if (!config.pretty && _line.includes(".ts(")) {
+					//reformat file path for easy jumping
+					_line = _line.replace("(", ":", 1).replace(",", ":", 1).replace(")", "", 1);
+				}
+				console.error(_line);
+			});
+		}
+		console.error(`Build failed! Please check build.log for info!`);
+		if (!config.silent) {
+			if (config.pretty) fs.writeFileSync(path.join(config.rootDir, "build.log.ansi"), error.stdout);
+			fs.writeFileSync(
+				path.join(config.rootDir, "build.log"),
+				error.stdout.replaceAll(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "")
+			);
+		}
+		throw error;
+	}
+};
diff --git a/scripts/build/plugin_prepare.js b/scripts/build/plugin_prepare.js
new file mode 100644
index 00000000..247ad22d
--- /dev/null
+++ b/scripts/build/plugin_prepare.js
@@ -0,0 +1,31 @@
+const { execSync } = require("child_process");
+const path = require("path");
+const fs = require("fs");
+const { argv, stdout, exit } = require("process");
+const { execIn, parts, getDirs, walk, sanitizeVarName } = require("../utils");
+
+module.exports = function (config) {
+	console.log(`==> Building plugin index...`);
+	let output = 'import { Plugin } from "util/plugin";\n';
+
+	const dirs = fs.readdirSync(config.pluginDir).filter((x) => {
+		try {
+			fs.readdirSync(path.join(config.pluginDir, x));
+			return true;
+		} catch (e) {
+			return false;
+		}
+	});
+	dirs.forEach((x) => {
+		let pluginManifest = require(path.join(config.pluginDir, x, "plugin.json"));
+		console.log(`  ==> Registering plugin: ${pluginManifest.name} (${pluginManifest.id}) by ${pluginManifest.authors}`);
+		output += `import * as ${sanitizeVarName(x)} from "./${x}/${pluginManifest.mainClass}";\n`;
+	});
+	output += `\nexport const PluginIndex: any = {\n`;
+	dirs.forEach((x) => {
+		output += `    "${x}": new ${sanitizeVarName(x)}.default(),\n`; //ctor test: '${path.resolve(path.join(pluginDir, x))}', require('./${x}/plugin.json')
+	});
+	output += `};`;
+
+	fs.writeFileSync(path.join(config.pluginDir, "PluginIndex.ts"), output);
+};
diff --git a/scripts/build/plugin_resources.js b/scripts/build/plugin_resources.js
new file mode 100644
index 00000000..5b4b97f2
--- /dev/null
+++ b/scripts/build/plugin_resources.js
@@ -0,0 +1,13 @@
+const { execSync } = require("child_process");
+const path = require("path");
+const fs = require("fs");
+const { argv, stdout, exit } = require("process");
+const { execIn, parts, getDirs, walk, sanitizeVarName } = require("../utils");
+
+module.exports = function (config) {
+	console.log(`==> Copying all plugin resources...`);
+	let pluginFiles = walk(config.pluginDir).filter((x) => !x.endsWith(".ts"));
+	pluginFiles.forEach((x) => {
+		fs.copyFileSync(x, x.replace("src", "dist"));
+	});
+};
diff --git a/scripts/build/remap_imports.js b/scripts/build/remap_imports.js
new file mode 100644
index 00000000..cdcd571a
--- /dev/null
+++ b/scripts/build/remap_imports.js
@@ -0,0 +1,15 @@
+const { execSync } = require("child_process");
+const path = require("path");
+const fs = require("fs");
+const { argv, stdout, exit } = require("process");
+const { execIn, parts, getDirs, walk, sanitizeVarName } = require("../utils");
+
+module.exports = function (config) {
+	console.log(`==> Remapping module imports...`);
+	let files = walk(config.distDir).filter((x) => x.endsWith(".js"));
+	files.forEach((x) => {
+		let fc = fs.readFileSync(x).toString();
+		fc = fc.replaceAll("@fosscord/", "#");
+		fs.writeFileSync(x, fc);
+	});
+};
diff --git a/scripts/build_new.js b/scripts/build_new.js
new file mode 100644
index 00000000..6a56e7f7
--- /dev/null
+++ b/scripts/build_new.js
@@ -0,0 +1,31 @@
+const { execSync } = require("child_process");
+const path = require("path");
+const fs = require("fs");
+const { argv, stdout, exit } = require("process");
+const { execIn, parts, getDirs, walk, sanitizeVarName } = require("./utils");
+
+//file paths
+const rootDir = path.join(__dirname, "..");
+const srcDir = path.join(rootDir, "src");
+const distDir = path.join(rootDir, "dist");
+const scriptsDir = path.join(rootDir, "scripts");
+const configPath = path.join(rootDir, "build.json");
+const buildLog = path.join(rootDir, "build.log");
+const buildLogAnsi = path.join(rootDir, "build.log.ansi");
+const pluginDir = path.join(srcDir, "plugins");
+
+//more, dont export
+const buildStepDir = path.join(scriptsDir, "build");
+
+if (!fs.existsSync(configPath)) {
+	if (!fs.existsSync(path.join(configPath + ".default"))) {
+		console.log("build.json.default not found! Exiting!");
+		exit(1);
+	}
+	fs.copyFileSync(configPath + ".default", configPath);
+}
+let config = { rootDir, srcDir, distDir, configPath, buildLog, buildLogAnsi, pluginDir, ...require(configPath) };
+
+config.steps.pre.forEach((step) => require(path.join(buildStepDir, step))(config));
+require(path.join(buildStepDir, "compile_" + config.compiler))(config);
+config.steps.post.forEach((step) => require(path.join(buildStepDir, step))(config));
diff --git a/scripts/code_quality.js b/scripts/code_quality.js
new file mode 100644
index 00000000..aca01d24
--- /dev/null
+++ b/scripts/code_quality.js
@@ -0,0 +1,75 @@
+const path = require("path");
+const fs = require("fs").promises;
+const { execIn, getLines, walk, projectRoot } = require("./utils");
+
+let printTodos = process.argv.includes("TODOS");
+
+let root = path.resolve(path.join(__dirname, "..", "src"));
+let files = walk(root);
+let _files = [];
+let errors = 0,
+	warnings = 0,
+	todos = 0;
+
+Promise.all(files.map(getFile)).then((f) => {
+	Promise.all(Object.keys(_files).map(checkFile));
+	console.log(`\n${errors} errors, ${warnings} warnings, ${todos} TODOs.`);
+
+	let loc = 0;
+	Object.values(_files).forEach((x) => {
+		loc += x.length;
+	});
+	console.log("\nStats:\n");
+	console.log(`Lines of code: ${loc} lines in ${Object.values(_files).length} files.`);
+
+	debugger;
+});
+
+async function getFile(name) {
+	let contents = (await fs.readFile(name)).toString().split("\n");
+	_files[name] = contents;
+}
+
+async function checkFile(x) {
+	_files[x].forEach((line) => scanLine(x, line));
+}
+
+function log(file, line, msg) {
+	let lineNum = _files[file].indexOf(line) + 1;
+	console.log(msg, "File:", file.replace(root + "/", "") + ":" + lineNum);
+}
+
+function scanLine(x, line) {
+	if (/import/.test(line)) {
+		if (/import {?.*}? from '.*'/.test(line)) {
+			log(x, line, `[WARN] Inconsistent import syntax, please use double quotes!`);
+			warnings++;
+		}
+	} else {
+		if (line.trim().endsWith("TODO:")) {
+			log(x, line, `[ERRO] Empty TODO!`);
+			errors++;
+		} else if (/\/\/\s{0,3}TODO:/.test(line)) {
+			if (printTodos) log(x, line, `[TODO] Found a TODO: ${line.split("TODO:")[1].trim()}.`);
+			todos++;
+		}
+		if (/(:|=)/.test(line)) {
+			if (/(:|=) {2,}/.test(line)) {
+				log(x, line, `[WARN] Multiple spaces in assignment!`);
+				warnings++;
+			}
+			if (/(:|=)\t'/.test(line)) {
+				log(x, line, `[WARN] Tab in assignment!`);
+				warnings++;
+			}
+			if (/(:|=)\w'/.test(line)) {
+				log(x, line, `[WARN] Missing space in assignment!`);
+				warnings++;
+			}
+			if (/(:|=) undefined/.test(line) && !/(:|=){2,} undefined/.test(line)) {
+				log(x, line, `[WARN] Use of undefined!`);
+				warnings++;
+			}
+		}
+	}
+}
diff --git a/scripts/db_migrations.js b/scripts/db_migrations.js
new file mode 100644
index 00000000..df5196b1
--- /dev/null
+++ b/scripts/db_migrations.js
@@ -0,0 +1,80 @@
+#!/usr/bin/node
+const path = require("path");
+const fs = require("fs");
+const { stdout, exit } = require("process");
+const { execIn } = require("./utils.js");
+const { ask } = require("./utils/ask.js");
+
+async function main() {
+    let filename;
+    if(process.argv[2]) filename = process.argv[2];
+    else filename = await ask("Please enter the name of your migration: ");
+    let dbconf;
+    try {
+        dbconf = JSON.parse(fs.readFileSync("dbconf.json"));
+    } catch (e) {
+        console.log("No dbconf.json found!");
+        dbconf = {};
+    }
+
+    if(!dbconf["sqlite"]) 
+        dbconf.sqlite = {
+            conn_str: "migrations.db",
+            migrations_dir: "sqlite",
+            package: "sqlite3"
+        }
+    if(!dbconf["postgres"] && process.env.FC_DB_POSTGRES) {
+        console.log("Found FC_DB_POSTGRES environment variable. Using it!");
+        dbconf.postgres = {
+            conn_str: process.env.FC_DB_POSTGRES,
+            migrations_dir: "postgres",
+            package: "pg"
+        }
+    }
+    if(!dbconf["mariadb"] && process.env.FC_DB_MARIADB){
+        console.log("Found FC_DB_MARIADB environment variable. Using it!");
+        dbconf.mariadb = {
+            conn_str: process.env.FC_DB_MARIADB,
+            migrations_dir: "mariadb",
+            package: "mysql2"
+        }
+    }
+    fs.writeFileSync("dbconf.json", JSON.stringify(dbconf, null, 4));
+
+    //build
+    execIn(`node scripts/build_new.js`, process.cwd(), {stdio: "inherit"});
+
+    if(fs.existsSync(".env") && !fs.existsSync(".env.bak"))
+        fs.renameSync(".env", ".env.bak");
+    Object.keys(dbconf).forEach((db) => {
+        console.log(`Applying migrations for ${db}`);
+        if(!fs.existsSync(path.join("node_modules", dbconf[db].package))) 
+            execIn(`npm i ${dbconf[db].package}`, process.cwd());
+        fs.writeFileSync(
+            `.env`,
+            `DATABASE=${dbconf[db].conn_str}
+    THREADS=1
+    DB_MIGRATE=true
+    DB_VERBOSE=true`
+        );
+        execIn(`node dist/start.js`, process.cwd(), {stdio: "inherit"});
+    });
+
+    Object.keys(dbconf).forEach((db) => {
+        console.log(`Generating new migrations for ${db}`);
+        fs.writeFileSync(
+            `.env`,
+            `DATABASE=${dbconf[db].conn_str}
+    THREADS=1
+    DB_MIGRATE=true
+    DB_VERBOSE=true`
+        );
+        execIn(`node node_modules/typeorm/cli.js migration:generate "src/util/migrations/${db}/${filename}" -d dist/util/util/Database.js -p`, process.cwd(), {stdio: "inherit"});
+    });
+    if(fs.existsSync(".env.bak")) {
+        fs.rmSync(".env");
+        fs.renameSync(".env.bak", ".env");
+    }
+    exit(0);
+}
+main();
\ No newline at end of file
diff --git a/scripts/depcheck.js b/scripts/depcheck.js
new file mode 100644
index 00000000..44ac2bb6
--- /dev/null
+++ b/scripts/depcheck.js
@@ -0,0 +1,50 @@
+const path = require("path");
+const fs = require("fs");
+const { env } = require("process");
+const { execSync } = require("child_process");
+const { argv, stdout, exit } = require("process");
+
+const { execIn, getLines } = require("./utils");
+
+let npmi_extra_flags = "";
+
+const resolveminor = argv.includes("resolveminor");
+if (argv.includes("nobuild")) npmi_extra_flags += "--ignore-scripts ";
+
+parts.forEach((part) => {
+	let partDir = path.join(__dirname, "..", "..", part);
+	let distDir = path.join(partDir, "dist");
+	console.log(`Checking updates for ${part} (${partDir})`);
+	if (part == "bundle") {
+		execIn(`npm run syncdeps`, partDir);
+	}
+	if (resolveminor) {
+		fs.rmSync(path.join(partDir, "node_modules"), {
+			recursive: true,
+			force: true
+		});
+		execIn(`npm i --save --no-fund --no-audit --no-package-lock ${npmi_extra_flags}`, partDir);
+	}
+	let x = [
+		[
+			"pkg",
+			{
+				current: "1.0",
+				wanted: "2.0",
+				latest: "2.0",
+				dependent: "cdn",
+				location: "/usr/src/fosscord/bundle/node_packages/pkg"
+			}
+		]
+	];
+	x = Object.entries(JSON.parse(execIn("npm outdated --json", partDir)));
+	x.forEach((a) => {
+		let pkgname = a[0];
+		let pkginfo = a[1];
+		if (!pkginfo.current) console.log(`MISSING ${pkgname}: ${pkginfo.current} -> ${pkginfo.wanted} (latest: ${pkginfo.latest})`);
+		else if (pkginfo.latest != pkginfo.wanted) {
+			if (pkginfo.current != pkginfo.wanted) console.log(`MINOR   ${pkgname}: ${pkginfo.current} -> ${pkginfo.wanted}`);
+			console.log(`MAJOR   ${pkgname}: ${pkginfo.current} -> ${pkginfo.latest}`);
+		} else console.log(`MINOR   ${pkgname}: ${pkginfo.current} -> ${pkginfo.wanted}`);
+	});
+});
diff --git a/scripts/depclean.js b/scripts/depclean.js
new file mode 100644
index 00000000..5a402331
--- /dev/null
+++ b/scripts/depclean.js
@@ -0,0 +1,53 @@
+const path = require("path");
+const fs = require("fs");
+const { env } = require("process");
+const { execSync } = require("child_process");
+const { argv, stdout, exit } = require("process");
+const { execIn, getLines } = require("./utils");
+
+const bundleRequired = ["@ovos-media/ts-transform-paths"];
+const removeModules = argv.includes("cleanup");
+
+console.log(`Installing all packages...`);
+execIn("npm i", path.join(__dirname, ".."));
+
+let partDir = path.join(__dirname, "..");
+let distDir = path.join(partDir, "dist");
+let start = 0;
+start = getLines(execIn("npm ls --parseable --package-lock-only -a", partDir));
+if (fs.existsSync(distDir))
+	fs.rmSync(distDir, {
+		recursive: true,
+		force: true
+	});
+let x = {
+	dependencies: [],
+	devDependencies: [],
+	invalidDirs: [],
+	invalidFiles: [],
+	missing: [],
+	using: []
+};
+let dcproc = execIn("npx depcheck --json", partDir);
+if (dcproc.stdout) x = JSON.parse(dcproc.stdout);
+else x = JSON.parse(dcproc);
+
+fs.writeFileSync(path.join(__dirname, "..", `depclean.out.json`), JSON.stringify(x, null, "\t"), { encoding: "utf8" });
+
+let depsToRemove = x.dependencies.join(" ");
+if (depsToRemove) execIn(`npm r --save ${depsToRemove}`, partDir);
+
+depsToRemove = x.devDependencies.join(" ");
+if (depsToRemove) execIn(`npm r --save --dev ${depsToRemove}`, partDir);
+
+if (removeModules && fs.existsSync(path.join(partDir, "node_modules")))
+	fs.rmSync(path.join(partDir, "node_modules"), {
+		recursive: true,
+		force: true
+	});
+let end = getLines(execIn("npm ls --parseable --package-lock-only -a", partDir));
+console.log(`${part}: ${start} -> ${end} (diff: ${start - end})`);
+
+console.log("Installing required packages for bundle...");
+
+execIn(`npm i --save ${bundleRequired.join(" ")}`, path.join(__dirname, ".."));
diff --git a/scripts/droptables.sql b/scripts/droptables.sql
new file mode 100644
index 00000000..8a852048
--- /dev/null
+++ b/scripts/droptables.sql
@@ -0,0 +1,31 @@
+DROP TABLE applications;
+DROP TABLE attachments;
+DROP TABLE audit_logs;
+DROP TABLE bans;
+DROP TABLE connected_accounts;
+DROP TABLE emojis;
+DROP TABLE invites;
+DROP TABLE member_roles;
+DROP TABLE message_channel_mentions;
+DROP TABLE message_role_mentions;
+DROP TABLE message_stickers;
+DROP TABLE message_user_mentions;
+DROP TABLE messages;
+DROP TABLE rate_limits;
+DROP TABLE read_states;
+DROP TABLE recipients;
+DROP TABLE relationships;
+DROP TABLE roles;
+DROP TABLE sessions;
+DROP TABLE stickers;
+DROP TABLE team_members;
+DROP TABLE teams;
+DROP TABLE templates;
+DROP TABLE voice_states;
+DROP TABLE webhooks;
+DROP TABLE channels;
+DROP TABLE members;
+DROP TABLE guilds;
+DROP TABLE client_release;
+-- DROP TABLE users;
+-- DROP TABLE config;
\ No newline at end of file
diff --git a/scripts/first_setup.js b/scripts/first_setup.js
new file mode 100755
index 00000000..4ce6e7de
--- /dev/null
+++ b/scripts/first_setup.js
@@ -0,0 +1,272 @@
+#!/usr/bin/node
+const path = require("path");
+const fs = require("fs");
+const { stdout, exit } = require("process");
+const { execIn } = require("./utils.js");
+const { ask } = require("./utils/ask.js");
+
+
+const data = { env: [], config: { register: {} }, extra_pkgs: [] };
+let rights = [];
+
+process.on("SIGINT", function () {
+	console.log("Caught interrupt signal");
+	process.exit();
+});
+
+console.log("Welcome to Fosscord!");
+console.log("Please remember this is pre-release software!");
+console.log("We will guide you through some important setup steps.");
+console.log();
+
+if (fs.existsSync("package-lock.json")) fs.rmSync("package-lock.json");
+if (fs.existsSync("yarn.lock")) fs.rmSync("yarn.lock");
+
+async function main() {
+	printTitle("Step 1: Database setup");
+	console.log("1. PostgreSQL (recommended)");
+	console.log("2. MariaDB/MySQL");
+	console.log("3. SQLite (not recommended, but good for a simple test)");
+
+	while (!data.db) {
+		let answer = await ask("Please select a database type: ");
+		if (answer == "1") {
+			data.db = "postgres";
+			data.extra_pkgs.push("pg");
+		} else if (answer == "2") {
+			data.db = "mariadb";
+			data.extra_pkgs.push("mysql2");
+		} else if (answer == "3") {
+			data.db = "sqlite";
+			data.extra_pkgs.push("sqlite3");
+		} else {
+			console.log("Invalid choice!");
+		}
+	}
+
+	printTitle("Step 2: Database credentials");
+	if (data.db != "sqlite") {
+		console.log("Please enter your database credentials.");
+		console.log("You can leave the password field empty if you don't want to set a password.");
+		console.log();
+		while (!data.db_host) {
+			data.db_host = await ask("Host: ");
+		}
+		while (!data.db_port) {
+			data.db_port = await ask("Port: ");
+		}
+		while (!data.db_user) {
+			data.db_user = await ask("Username: ");
+		}
+		while (!data.db_pass) {
+			data.db_pass = await ask("Password: ");
+		}
+		while (!data.db_name) {
+			data.db_name = await ask("Database name: ");
+		}
+	} else {
+		console.log("SQLite does not use credentials...");
+	}
+
+	printTitle("Step 3: Domain setup");
+	console.log("Please enter your domain.");
+	console.log("You can leave the port field empty if you don't want to set a port.");
+	console.log();
+
+	data.domain = await ask("Domain (default=localhost): ");
+	if (!data.domain) data.domain = "localhost";
+	else data.ssl = /y?/i.test(await ask("SSL/HTTPS (Y/n): "));
+
+	data.port = await ask("Port (default=3001): ");
+	if (!data.port) data.port = "3001";
+
+	if (data.db != "sqlite")
+		data.env.push(`DATABASE=${data.db}://${data.db_user}:${data.db_pass}@${data.db_host}:${data.db_port}/${data.db_name}`);
+	data.env.push(`PORT=${data.port}`);
+	data.env.push("THREADS=1");
+
+	printTitle("Step 4: Default rights");
+	console.log("Please enter the default rights for new users.");
+	console.log("Valid rights are: none, discord, full, custom.");
+	console.log();
+	let lines = fs.readFileSync(path.join(__dirname, "..", "src", "util", "util", "Rights.ts")).toString();
+	let lines2 = lines.split("\n");
+	let lines3 = lines2.filter((y) => y.includes(": BitFlag("));
+	let lines4 = lines3.map((x) => x.split("//")[0].trim());
+
+	let maxRights = 0n;
+	lines4.forEach((x) => {
+		maxRights += eval(`rights.${x.replace(":", " = ").replace(",", ";")}`);
+	});
+	discordRights = maxRights;
+	discordRights -= rights.SEND_BACKDATED_EVENTS;
+	discordRights -= rights.MANAGE_GUILD_DIRECTORY;
+	discordRights -= rights.CREDITABLE;
+	discordRights -= rights.BYPASS_RATE_LIMITS;
+	discordRights -= rights.ADD_MEMBERS;
+	discordRights -= rights.MANAGE_RATE_LIMITS;
+	discordRights -= rights.OPERATOR;
+
+	data.default_rights = await ask("Rights (default=none): ");
+	if (!data.default_rights || data.defaultRights == "none") data.config.register.defaultRights = "0";
+	else if (data.default_rights == "discord") data.config.register.defaultRights = discordRights.toString();
+	else if (data.default_rights == "full") data.config.register.defaultRights = maxRights.toString();
+	else if (data.default_rights == "custom") data.config.register.defaultRights = (await askRights()).toString();
+
+	if (data.domain != "localhost")
+		data.config = {
+			cdn: {
+				endpointPrivate: `http://localhost:${data.port}`,
+				endpointPublic: `${data.ssl ? "https" : "http"}://${data.domain}:${data.port}`
+			},
+			gateway: {
+				endpointPrivate: `ws://localhost:${data.port}`,
+				endpointPublic: `${data.ssl ? "wss" : "ws"}://${data.domain}:${data.port}`
+			},
+			...data.config
+		};
+	printTitle("Step 5: extra options");
+
+	if (/y?/i.test(await ask("Use fast BCrypt implementation (requires a compiler) (Y/n): "))) data.extra_pkgs.push("bcrypt");
+	if (/y?/i.test(await ask("Enable support for widgets (requires compiler, known to fail on some ARM devices.) (Y/n): ")))
+		data.extra_pkgs.push("canvas");
+
+	printTitle("Step 6: finalizing...");
+	//save
+	console.log("==> Writing .env...");
+	fs.writeFileSync(".env", data.env.join("\n"));
+	console.log("==> Writing initial.json");
+	fs.writeFileSync("initial.json", JSON.stringify(data.config, (space = 4)));
+	//install packages...
+	console.log("==> Installing packages...");
+	console.log("  ==> Ensuring yarn is up to date (v3, not v1)...");
+	execIn("npx yarn set version stable", process.cwd());
+	console.log("  ==> Installing base packages");
+	execIn("npx --yes yarn install", process.cwd(), { stdio: "inherit" });
+	if (data.extra_pkgs.length > 0) {
+		console.log("  ==> Checking dependencies...");
+		checkCompilers();
+		if (data.extra_pkgs.includes("canvas")) checkCanvasDeps();
+		if (data.extra_pkgs.includes("bcrypt")) checkBcryptDeps();
+
+		console.log(`  ==> Installing extra packages: ${data.extra_pkgs.join(", ")}...`);
+		execIn(`npx --yes yarn add -O ${data.extra_pkgs.join(" ")}`, process.cwd(), { stdio: "inherit" });
+	}
+
+	console.log("==> Building...");
+	execIn("npx --yes yarn run build", process.cwd(), { stdio: "inherit" });
+	printTitle("Step 6: run your instance!");
+	console.log("Installation is complete!");
+	console.log("You can now start your instance by running 'npm run start:bundle'!");
+	exit(0);
+}
+main();
+
+async function askRights() {
+	let w = 0;
+	let brights = { ...eval(`rights`) };
+	Object.keys(rights).forEach((x) => {
+		brights[x] = false;
+		let str = `[x] ${Object.keys(rights).length}: ${x}`;
+		if (str.length > w) w = str.length;
+	});
+
+	let resp = "";
+	let selectedRights = 0n;
+	while (resp != "q") {
+		selectedRights = 0n;
+		Object.keys(brights).forEach((x) => {
+			if (brights[x]) selectedRights += rights[x];
+		});
+		console.clear();
+		printTitle("Step 4: Default rights");
+		printTitle(`Current rights: ${selectedRights} (0b${selectedRights.toString(2)}, 0x${selectedRights.toString(16)})`);
+		let xpos = 0;
+		Object.keys(rights).forEach((x) => {
+			let str = `[${brights[x] ? "X" : " "}] ${Object.keys(rights).indexOf(x)}: ${x}`.padEnd(w + 1, " ");
+			if (xpos + str.length > stdout.columns) {
+				console.log();
+				xpos = 0;
+			}
+			stdout.write(str);
+			xpos += str.length;
+		});
+
+		console.log();
+		resp = await ask("Enter an option, or q to exit: ");
+		if (/\d{1,}/.test(resp) && resp < Object.keys(rights).length && resp > -1) {
+			brights[Object.keys(brights)[parseInt(resp)]] ^= true;
+		}
+	}
+	return selectedRights;
+}
+
+
+
+function printTitle(input) {
+	let width = stdout.columns / 2 - 1; //40
+	console.log();
+	console.log("-".repeat(width - input.length / 2), input, "-".repeat(width - input.length / 2));
+	console.log();
+}
+
+
+function BitFlag(int) {
+	return 1n << BigInt(int);
+}
+
+function checkCanvasDeps() {
+	if (
+		!(
+			checkDep("pixman", "/usr/include/pixman-1/pixman.h") &&
+			checkDep("pixman", "/usr/lib/libpixman-1.so") &&
+			checkDep("cairo", "/usr/include/cairo/cairo.h") &&
+			checkDep("cairo", "/usr/lib/libcairo.so") &&
+			checkDep("pango", "/usr/include/pango-1.0/pango/pangocairo.h") &&
+			checkDep("pango", "/usr/lib/libpango-1.0.so") &&
+			checkDep("pkgconfig", "/usr/bin/pkg-config")
+		)
+	) {
+		console.log("Canvas requires the following dependencies to be installed: pixman, cairo, pango, pkgconfig");
+		exit(1);
+	}
+}
+function checkBcryptDeps() {
+	/*if (!(checkDep("bcrypt", "/usr/include/bcrypt.h") && checkDep("bcrypt", "/usr/lib/libbcrypt.so"))) {
+		console.log("Bcrypt requires the following dependencies to be installed: bcrypt");
+		exit(1);
+	}*/
+	//TODO: check if required
+}
+
+function checkCompilers() {
+	//check for gcc, grep, make, python-is-python3
+	if (
+		!(
+			checkDep("gcc", "/usr/bin/gcc") &&
+			checkDep("grep", "/usr/bin/grep") &&
+			checkDep("make", "/usr/bin/make") &&
+			checkDep("python3", "/usr/bin/python3")
+		)
+	) {
+		console.log("Compiler requirements not met. Please install the following: gcc, grep, make, python3");
+		exit(1);
+	}
+
+	//check if /usr/bin/python is a symlink to /usr/bin/python3
+	if (!fs.lstatSync("/usr/bin/python").isSymbolicLink()) {
+		console.log("/usr/bin/python is not a symlink. Please make sure it is a symlink to /usr/bin/python3");
+		if (fs.existsSync("/usr/bin/python3")) {
+			console.log("Hint: sudo ln -s /usr/bin/python3 /usr/bin/python");
+		}
+		exit(1);
+	}
+}
+
+function checkDep(name, path, message) {
+	if (!fs.existsSync(path)) {
+		console.log(`${name} not found at ${path}! Installation of some modules may fail!`);
+		console.log(message ?? `Please consult your distro's manual for installation instructions.`);
+	}
+	return fs.existsSync(path);
+}
diff --git a/scripts/gen_index.js b/scripts/gen_index.js
new file mode 100644
index 00000000..8a3c7eb8
--- /dev/null
+++ b/scripts/gen_index.js
@@ -0,0 +1,37 @@
+const path = require("path");
+const fs = require("fs");
+const { execIn, getLines } = require("./utils");
+
+if (!process.argv[2] || !fs.existsSync(process.argv[2])) {
+	console.log("Please pass a directory that exists!");
+	process.exit(1);
+}
+console.log(`// ${process.argv[2]}/index.ts`);
+const recurse = process.argv.includes("--recursive");
+
+const files = fs.readdirSync(process.argv[2]).filter((x) => x.endsWith(".ts") && x != "index.ts");
+
+let output = "";
+
+files.forEach((x) => (output += `export * from "./${x.replaceAll(".ts", "")}";\n`));
+
+const dirs = fs.readdirSync(process.argv[2]).filter((x) => {
+	try {
+		fs.readdirSync(path.join(process.argv[2], x));
+		return true;
+	} catch (e) {
+		return false;
+	}
+});
+dirs.forEach((x) => {
+	output += `export * from "./${x}/index";\n`;
+});
+console.log(output);
+fs.writeFileSync(path.join(process.argv[2], "index.ts"), output);
+
+dirs.forEach((x) => {
+	if (recurse)
+		console.log(
+			execIn([process.argv[0], process.argv[1], `"${path.join(process.argv[2], x)}"`, "--recursive"].join(" "), process.cwd())
+		);
+});
diff --git a/scripts/generate_openapi.js b/scripts/generate_openapi.js
new file mode 100644
index 00000000..9624a5b9
--- /dev/null
+++ b/scripts/generate_openapi.js
@@ -0,0 +1,137 @@
+// https://mermade.github.io/openapi-gui/#
+// https://editor.swagger.io/
+const getRouteDescriptions = require("../jest/getRouteDescriptions");
+const path = require("path");
+const fs = require("fs");
+require("missing-native-js-functions");
+
+const openapiPath = path.join(__dirname, "..", "assets", "openapi.json");
+const SchemaPath = path.join(__dirname, "..", "assets", "schemas.json");
+const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
+const specification = JSON.parse(fs.readFileSync(openapiPath, { encoding: "utf8" }));
+
+function combineSchemas(schemas) {
+	let definitions = {};
+
+	for (const name in schemas) {
+		definitions = {
+			...definitions,
+			...schemas[name].definitions,
+			[name]: { ...schemas[name], definitions: undefined, $schema: undefined }
+		};
+	}
+
+	for (const key in definitions) {
+		specification.components.schemas[key] = definitions[key];
+		delete definitions[key].additionalProperties;
+		delete definitions[key].$schema;
+		const definition = definitions[key];
+
+		if (typeof definition.properties === "object") {
+			for (const property of Object.values(definition.properties)) {
+				if (Array.isArray(property.type)) {
+					if (property.type.includes("null")) {
+						property.type = property.type.find((x) => x !== "null");
+						property.nullable = true;
+					}
+				}
+			}
+		}
+	}
+
+	return definitions;
+}
+
+function getTag(key) {
+	return key.match(/\/([\w-]+)/)[1];
+}
+
+function apiRoutes() {
+	const routes = getRouteDescriptions();
+
+	const tags = Array.from(routes.keys()).map((x) => getTag(x));
+	specification.tags = [...specification.tags.map((x) => x.name), ...tags].unique().map((x) => ({ name: x }));
+
+	routes.forEach((route, pathAndMethod) => {
+		const [p, method] = pathAndMethod.split("|");
+		const path = p.replace(/:(\w+)/g, "{$1}");
+
+		let obj = specification.paths[path]?.[method] || {};
+		if (!obj.description) {
+			const permission = route.permission ? `##### Requires the \`\`${route.permission}\`\` permission\n` : "";
+			const event = route.test?.event ? `##### Fires a \`\`${route.test?.event}\`\` event\n` : "";
+			obj.description = permission + event;
+		}
+		if (route.body) {
+			obj.requestBody = {
+				required: true,
+				content: {
+					"application/json": {
+						schema: { $ref: `#/components/schemas/${route.body}` }
+					}
+				}
+			}.merge(obj.requestBody);
+		}
+		if (!obj.responses) {
+			obj.responses = {
+				default: {
+					description: "not documented"
+				}
+			};
+		}
+		if (route.test?.response) {
+			const status = route.test.response.status || 200;
+			let schema = {
+				allOf: [
+					{
+						$ref: `#/components/schemas/${route.test.response.body}`
+					},
+					{
+						example: route.test.body
+					}
+				]
+			};
+			if (!route.test.body) schema = schema.allOf[0];
+
+			obj.responses = {
+				[status]: {
+					...(route.test.response.body
+						? {
+								description: obj.responses[status].description || "",
+								content: {
+									"application/json": {
+										schema: schema
+									}
+								}
+						  }
+						: {})
+				}
+			}.merge(obj.responses);
+			delete obj.responses.default;
+		}
+		if (p.includes(":")) {
+			obj.parameters = p.match(/:\w+/g)?.map((x) => ({
+				name: x.replace(":", ""),
+				in: "path",
+				required: true,
+				schema: { type: "string" },
+				description: x.replace(":", "")
+			}));
+		}
+		obj.tags = [...(obj.tags || []), getTag(p)].unique();
+
+		specification.paths[path] = { ...specification.paths[path], [method]: obj };
+	});
+}
+
+function main() {
+	combineSchemas(schemas);
+	apiRoutes();
+
+	fs.writeFileSync(
+		openapiPath,
+		JSON.stringify(specification, null, 4).replaceAll("#/definitions", "#/components/schemas").replaceAll("bigint", "number")
+	);
+}
+
+main();
diff --git a/scripts/generate_schema.js b/scripts/generate_schema.js
new file mode 100644
index 00000000..e4bdd0c4
--- /dev/null
+++ b/scripts/generate_schema.js
@@ -0,0 +1,90 @@
+// https://mermade.github.io/openapi-gui/#
+// https://editor.swagger.io/
+const path = require("path");
+const fs = require("fs");
+const TJS = require("typescript-json-schema");
+require("missing-native-js-functions");
+const schemaPath = path.join(__dirname, "..", "assets", "schemas.json");
+
+const settings = {
+	required: true,
+	ignoreErrors: true,
+	excludePrivate: true,
+	defaultNumberType: "integer",
+	noExtraProps: true,
+	defaultProps: false
+};
+const compilerOptions = {
+	strictNullChecks: true
+};
+const Excluded = [
+	"DefaultSchema",
+	"Schema",
+	"EntitySchema",
+	"ServerResponse",
+	"Http2ServerResponse",
+	"global.Express.Response",
+	"Response",
+	"e.Response",
+	"request.Response",
+	"supertest.Response",
+
+	// TODO: Figure out how to exclude schemas from node_modules?
+	"SomeJSONSchema",
+	"UncheckedPartialSchema",
+	"PartialSchema",
+	"UncheckedPropertiesSchema",
+	"PropertiesSchema",
+	"AsyncSchema",
+	"AnySchema"
+];
+
+function modify(obj) {
+	for (let k in obj) {
+		if (typeof obj[k] === "object" && obj[k] !== null) {
+			modify(obj[k]);
+		}
+	}
+}
+
+function main() {
+	const files = [...walk(path.join(__dirname, "..", "src", "util", "schemas"))];
+	const program = TJS.getProgramFromFiles(files, compilerOptions);
+	const generator = TJS.buildGenerator(program, settings);
+	if (!generator || !program) return;
+
+	let schemas = generator.getUserSymbols().filter((x) => (x.endsWith("Schema") || x.endsWith("Response")) && !Excluded.includes(x));
+	console.log(schemas);
+
+	let definitions = {};
+
+	for (const name of schemas) {
+		const part = TJS.generateSchema(program, name, settings, [], generator);
+		if (!part) continue;
+
+		definitions = { ...definitions, [name]: { ...part } };
+	}
+
+	modify(definitions);
+
+	fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4));
+}
+
+main();
+
+function walk(dir) {
+	let results = [];
+	let list = fs.readdirSync(dir);
+	list.forEach(function (file) {
+		file = dir + "/" + file;
+		let stat = fs.statSync(file);
+		if (stat && stat.isDirectory()) {
+			/* Recurse into a subdirectory */
+			results = results.concat(walk(file));
+		} else {
+			if (!file.endsWith(".ts")) return;
+			results.push(file);
+		}
+	});
+	return results;
+}
diff --git a/scripts/migrate_db_engine.js b/scripts/migrate_db_engine.js
new file mode 100644
index 00000000..b5b8008b
--- /dev/null
+++ b/scripts/migrate_db_engine.js
@@ -0,0 +1,109 @@
+const { config } = require("dotenv");
+config();
+const { createConnection } = require("typeorm");
+const { initDatabase } = require("../../dist/util/Database");
+require("missing-native-js-functions");
+const {
+	Application,
+	Attachment,
+	Ban,
+	Channel,
+	ConfigEntity,
+	ConnectedAccount,
+	Emoji,
+	Guild,
+	Invite,
+	Member,
+	Message,
+	ReadState,
+	Recipient,
+	Relationship,
+	Role,
+	Sticker,
+	Team,
+	TeamMember,
+	Template,
+	User,
+	VoiceState,
+	Webhook
+} = require("../../dist/entities/index");
+
+async function main() {
+	if (!process.env.TO) throw new Error("TO database env connection string not set");
+
+	// manually arrange them because of foreign keys
+	const entities = [
+		ConfigEntity,
+		User,
+		Guild,
+		Channel,
+		Invite,
+		Role,
+		Ban,
+		Application,
+		Emoji,
+		ConnectedAccount,
+		Member,
+		ReadState,
+		Recipient,
+		Relationship,
+		Sticker,
+		Team,
+		TeamMember,
+		Template,
+		VoiceState,
+		Webhook,
+		Message,
+		Attachment
+	];
+
+	const oldDB = await initDatabase();
+
+	const type = process.env.TO.includes("://") ? process.env.TO.split(":")[0]?.replace("+srv", "") : "sqlite";
+	const isSqlite = type.includes("sqlite");
+
+	// @ts-ignore
+	const newDB = await createConnection({
+		type,
+		url: isSqlite ? undefined : process.env.TO,
+		database: isSqlite ? process.env.TO : undefined,
+		entities,
+		name: "new",
+		synchronize: true
+	});
+	let i = 0;
+
+	try {
+		for (const entity of entities) {
+			const entries = await oldDB.manager.find(entity);
+
+			// @ts-ignore
+			console.log("migrating " + entries.length + " " + entity.name + " ...");
+
+			for (const entry of entries) {
+				console.log(i++);
+
+				try {
+					await newDB.manager.insert(entity, entry);
+				} catch (error) {
+					try {
+						if (!entry.id) throw new Error("object doesn't have a unique id: " + entry);
+						await newDB.manager.update(entity, { id: entry.id }, entry);
+					} catch (error) {
+						console.error("couldn't migrate " + i + " " + entity.name, error);
+					}
+				}
+			}
+
+			// @ts-ignore
+			console.log("migrated " + entries.length + " " + entity.name);
+		}
+	} catch (error) {
+		console.error(error.message);
+	}
+
+	console.log("SUCCESS migrated all data");
+	await newDB.close();
+}
+
+main().caught();
diff --git a/scripts/rights.js b/scripts/rights.js
new file mode 100644
index 00000000..5ae576ef
--- /dev/null
+++ b/scripts/rights.js
@@ -0,0 +1,34 @@
+const path = require("path");
+const fs = require("fs");
+const { env } = require("process");
+const { execSync } = require("child_process");
+const { argv, stdout, exit } = require("process");
+
+const { execIn, getLines, parts } = require("./utils");
+
+let lines = fs.readFileSync(path.join(__dirname, "..", "src", "util", "util", "Rights.ts")).toString();
+let lines2 = lines.split("\n");
+let lines3 = lines2.filter((y) => y.includes(": BitFlag("));
+let lines4 = lines3.map((x) => x.split("//")[0].trim());
+
+function BitFlag(int) {
+	return 1n << BigInt(int);
+}
+
+let rights = [];
+let maxRights = 0n;
+lines4.forEach((x) => {
+	maxRights += eval(`rights.${x.replace(":", " = ").replace(",", ";")}`);
+});
+//max rights...
+console.log(`Maximum rights: ${maxRights}`);
+//discord rights...
+discordRights = maxRights;
+discordRights -= rights.SEND_BACKDATED_EVENTS;
+discordRights -= rights.MANAGE_GUILD_DIRECTORY;
+discordRights -= rights.CREDITABLE;
+discordRights -= rights.BYPASS_RATE_LIMITS;
+discordRights -= rights.ADD_MEMBERS;
+discordRights -= rights.MANAGE_RATE_LIMITS;
+discordRights -= rights.OPERATOR;
+console.log(`Discord-like rights: ${discordRights}`);
diff --git a/scripts/stresstest/.gitignore b/scripts/stresstest/.gitignore
new file mode 100644
index 00000000..bde26fd4
--- /dev/null
+++ b/scripts/stresstest/.gitignore
@@ -0,0 +1,3 @@
+/node_modules
+config.json
+accounts.json
diff --git a/scripts/stresstest/accounts.json.example b/scripts/stresstest/accounts.json.example
new file mode 100644
index 00000000..61904c5e
--- /dev/null
+++ b/scripts/stresstest/accounts.json.example
@@ -0,0 +1 @@
+[{"email":"org11ncxa8.wi732t9b4o@fosscord.com","password":"x.ibahfyqwle.ne4hajbzp11.gpc4lcup4"},{"email":"rek6kyprik.i5hldol255@fosscord.com","password":"1.o3w16haor2y.0e1ey2yk1x.1r0gn5o5h"},{"email":"07o37povsi.uk5q9dtxbp@fosscord.com","password":"8.6z64gcjavp1n.uar3qqymwfi.g0sfmmbd9m"},{"email":"94di5zaie4.n1vhzdfsj@fosscord.com","password":"1e.k3ijylxme1u.e9xr9yqbrk.3tir7qnvh"},{"email":"zbrqft4chj.yl73e5puq3@fosscord.com","password":"5.nkc3g8cvwl15.dmp8ywmkka.m79e9t4wij"},{"email":"br1wknv7k.6hw6yl69e@fosscord.com","password":"3.gimzx06u7mh.6rjrdflo9j1t.h3d8k2f5t"},{"email":"cdehs12h6h.iexxvg16xf@fosscord.com","password":"1i.5ab7e9rtwl1n.31qtfv7cz9.e1k313py9"},{"email":"pazx37jpra.mgsb8k50ip@fosscord.com","password":"d.eg5dwqvd981e.5qobehiyffe.6k5pb4fqm"},{"email":"vs6k62ak2o.xo1v4w0rj@fosscord.com","password":"c.hrkcrnlxlg1d.w18ztd39d1p.eycgehb49"},{"email":"u5d27rbewm.3p0wa7s899@fosscord.com","password":"s.1r7o1ur8o9k.puzbm1uuta9.an5m8bhh0a"},{"email":"vyp6x66fr.yv74eftomo@fosscord.com","password":"m.w0c7h21asf.pq2lj3uot6a.xnhv9ftqii"},{"email":"da0k6sra2n.qts4gs9ufg@fosscord.com","password":"h.8e42ud5f6r5.896sp1t8y6e.shwe0d8no9"},{"email":"l093koc05n.81vt1v8tsx@fosscord.com","password":"11.zhkv1jbhdf.0ub2po3mnu.no4lq8l4"},{"email":"115tfo7ct.muvy04u0tf@fosscord.com","password":"1c.4bpk2a17z1p.gw2h6qmvhez.57drs0quz"},{"email":"dq6bk1hjch.huw092gkhr@fosscord.com","password":"1l.kp28mclrtee.5i4dmacbpc1j.hcqgemsni"},{"email":"8g8q9v3wmk.l2frwpuds8@fosscord.com","password":"1n.i0wwg0njmv.teaiqjqalt1g.ib6551nh4"},{"email":"5b3y3neqxa.mmi0ex2hxd@fosscord.com","password":"8.tvz7q9uw0hm.6ufz6fu65c1a.88vp9di6e"},{"email":"mg28g3krsp.35h1akxrqj@fosscord.com","password":"s.y8j2n19iffr.qyecyrgo6ig.6hgrc5vy9"},{"email":"ehtumcok2j.2oozlhiq97@fosscord.com","password":"11.uq0up8g8h1q.ofvjsx29yd.pfwen3kr38"},{"email":"le98rah1uc.au4ug9tpnt@fosscord.com","password":"t.q15zsc0q2mt.2nj3jsdxhfc.leb9ba1xku"},{"email":"hrroex9f5n.6cl98h3jsi@fosscord.com","password":"17.qnqqhg2us4.kh92v74atg19.49ufgil7g"},{"email":"w95wrrn48.6gfnue7dcq@fosscord.com","password":"b.jnqgbi89oj1k.8rn0llovbll.kcblui80th"},{"email":"rqo4n0il5w.4gl1u8hlyc@fosscord.com","password":"17.41d1lpjmi0.d8ijhslby11w.sjn7sqhi9"},{"email":"6dv3yp4kon.pk7ye6q1r6@fosscord.com","password":"0.j70py6yysjz.sf56ebpp2gp.z68yo9hiim"},{"email":"knmi9qkige.5v1bg6h09w@fosscord.com","password":"1d.7n58xntwg1s.umnglex7h13.c5xrsfkosm"},{"email":"cefymgc7te.dd81jabws4@fosscord.com","password":"1u.73ea7dde1o.0i1fhyaird.sjk30nky1e"},{"email":"33xcwiqf73.r6khs46a7j@fosscord.com","password":"b.5p5gdmh1891f.11g4590n5a.vfoek6qjb9"},{"email":"9zcgmr84s6.utnlygoubi@fosscord.com","password":"8.g4v53t7kcl16.wgaiufzgg1u.pusdfdneb"},{"email":"26vpzekrdw.3bwq27wla@fosscord.com","password":"11.yxey8293lj1d.nxhkju2eke.hl86mcvswd"},{"email":"vvq6w36r84.lr1auhpfc@fosscord.com","password":"y.7vlqbpftom.6xfrtozd11k.ycf9ifi7o"},{"email":"6ejeiq64yo.zorve5saw4@fosscord.com","password":"o.eue50qp1frq.qi0rwphg3dv.psph7va2fb"},{"email":"6s0hu88ro8.hsckrmud1i@fosscord.com","password":"16.fc836nhb91a.ul37503ppg.l62wgser4i"},{"email":"h8qwchz2x9.5br1kcw1iv@fosscord.com","password":"l.iw1041wgy.6azyc9h6vb.br9cr0dmn8"},{"email":"yx13rst2hu.ybisfdwgv@fosscord.com","password":"8.5yasf5ba619.ir0toxu251p.tbgwjd18f"},{"email":"1j7vrr1trh.wqj0ozl357@fosscord.com","password":"1m.iucscoe7b0.6ca1jfaag361.c2trc36mnvk"},{"email":"9w2w572pzr.fv1rk360pp@fosscord.com","password":"t.labzw6qw8t3.33k42uvhgd1s.e1gj71h14"},{"email":"yf5e43ol4.6509owbcxa@fosscord.com","password":"12.jewy0uvx1m1m.ce28kht6dk.v2p0bzlvz"},{"email":"gzny2o1re.1xrl0ua7yd@fosscord.com","password":"h.3valf7r8jh.6bzfr4ions.r4b2mt0l0g"},{"email":"bc77a5kw9v.hu5barps6q@fosscord.com","password":"1a.jt11azsa81j.4v70jvm9d1o.hflrb1tigk"},{"email":"ltoezpefev.hrvnxmq9ee@fosscord.com","password":"b.v4f5lqrlc912.dx4dd4xq91v.zj345to03"},{"email":"svcpsuoenk.b8mfqxpbzg@fosscord.com","password":"1a.7aobev8b4r.xqqfybkcs1t.cjuswku0a"},{"email":"n0mroewqq.svq5iq57pe@fosscord.com","password":"1l.zxm1xhlavp.65rp7bz57x.01vjajdsc"},{"email":"trly6yupd.dt37kh07dn@fosscord.com","password":"1i.o2ieg72fz1j.er031tzerx.2ngg4dcvlh"},{"email":"ickkf14cqv.9pu2pnmx7n@fosscord.com","password":"18.pyhd9ruatl1k.erfchcjc95.wfd67r5e8x"},{"email":"5o4ornfwy9.yabymb8e2k@fosscord.com","password":"1g.117kmei8df10.cedozr4vee.08te5d44nb"},{"email":"p0ulegfi3.dgmar6qc2v@fosscord.com","password":"1h.tle7s3ed82.un20o5nv3dk.wnz4w802h8"},{"email":"58gejpvr6v.jolxrsl83p@fosscord.com","password":"e.ksw14117hbo.f0pgufr3na.ssrjys23al"},{"email":"vf349zeoja.r8bjel59kd@fosscord.com","password":"1o.79kh6e6glm9.d76d86g1jp16.u37p4jhf7"},{"email":"uc786nn0go.n9ygun6owj@fosscord.com","password":"m.xo4bwhct5be.lpokbj59w8p.z4l52dzv1r"},{"email":"5jgx24s87u.odlx0bfo0r@fosscord.com","password":"1k.ni9jyfol7h1g.vczzsa8dbg.r4bhoh5op"},{"email":"2v44408x8l.unfspunnnm@fosscord.com","password":"16.63njhji5b4.r4xkcf672f1a.x389dr603g"},{"email":"ityj8kcvrm.9djzannsll@fosscord.com","password":"1p.6jdbhaxiqc.nfnpw7e09g8.967dtt2dy3"},{"email":"8csbvl9qot.28etdf4pf@fosscord.com","password":"1b.52rdo5qmj3.ta9jw1wm3k9.m96fe27tp"},{"email":"dqndi38hsq.yv77wk3mov@fosscord.com","password":"k.zpjwpwxmlr1f.tbj03rxayn17.9x451qclu"},{"email":"ohwmvag9j.w6t8ngs4t@fosscord.com","password":"b.h1ta0mly991q.wzu1ssffyk1h.kc10wt8i2"},{"email":"2mmors2h0w.jwukibc7oi@fosscord.com","password":"y.xo4kgepqa1t.b77zwt1in5.3um79fx22r"},{"email":"ux0q6gvwnr.gnywxxrsn@fosscord.com","password":"g.52userbsonu.ny8omqaduf.rvhtwq4jer"},{"email":"0q12b4zet.y87zc04r8r@fosscord.com","password":"1t.79mg1a9q85.k66wagu67j14.ad0gw3caw"},{"email":"gatbconrvq.dsopxa8fkk@fosscord.com","password":"1n.fycl7y9roh1p.4yg37pst4k16.votnvabrf"},{"email":"mmp9g1b1v.xz1w4qzxee@fosscord.com","password":"1k.cjmz3huosl.jh502yz5jf1e.hyce7qc67"},{"email":"5s90s1hbns.b027pfiv3s@fosscord.com","password":"1l.86ipkmi6fg.scabtvproj4.yw4nb9qui9"},{"email":"l4zrvtrbpb.1r627sllk@fosscord.com","password":"1l.zzm1dunzzek.10sr7mp01ly.yyrjj1hsli"},{"email":"xih9rwk90i.rmdifv40g@fosscord.com","password":"15.db9k0pxci1v.hs6l033urm.5a1zv42fhl"},{"email":"55mq93jdq.2dhr1ps4f5@fosscord.com","password":"i.v5hpg2qez1u.xhs32cwes1h.n10pexmfff"},{"email":"5c0vb38rul.5su27w4pn8@fosscord.com","password":"12.y87q6jxq41m.qgiji2j0hm.gmy2wuavc"},{"email":"qjk2eoqeqq.ljq4dig10o@fosscord.com","password":"0.lpu8eio3hra12.mq8qcehpe1e.77p7zilh4"},{"email":"b45ltbf5d.o4oouuik1e@fosscord.com","password":"1u.wb7hn2b1x1k.jys5p3ri4j.9ew9jab3ll"},{"email":"1mw205tjri.gpi2h76eps@fosscord.com","password":"1g.kyh53pnamd13.5yufexmyv1h.r56pmhm7i"},{"email":"8y0psdjq2s.ifqyimhnkj@fosscord.com","password":"1d.fi03hlwk41u.b89w0vrd712.ljudzvdo6"},{"email":"ls73glp0q9.3rtqyb262@fosscord.com","password":"1.z70c4ef5hfi.fes9zmue2it.5cobkz3ah"},{"email":"ipe2um46bi.in93oau1l@fosscord.com","password":"5.a5he7keuru1n.l05ivx4n24.piohqdy51w"},{"email":"mt16ta8diq.krypy2t9cv@fosscord.com","password":"n.zk4goctn5p3.r1fhllqy1m1p.ni2q3y68w"},{"email":"qehwflm0ja.x5uvmxgfle@fosscord.com","password":"1e.r2sj0uimq1f.nmtozr8qd1s.xgvz4d62b"},{"email":"0ppn1iwd6.ivrqbvn17i@fosscord.com","password":"1n.fr6x1pbzjl.c8xwipgo6c.m1me2h2g58"},{"email":"xiiq47ofev.u9z0gndxs6@fosscord.com","password":"1t.7tfe0181ij.jbznx5eebs.ytm50kp5qf"},{"email":"kqhk3lt2mo.o4y7u23zbu@fosscord.com","password":"1b.bkoqmxjcf1l.c5q9oneuz1u.00x93z7l4"},{"email":"ri64c5o5zn.o429slph64@fosscord.com","password":"1r.mre2hu1gpu.401xyxa6eu.j98cetaplg"},{"email":"j5jpukoktw.q5bseyjfu@fosscord.com","password":"h.k1ar11fpx1m.n50t8tz4k4.9oj17rtdjw"},{"email":"cg8gyuhu16.jezv2bo8n@fosscord.com","password":"1c.vyfo117pd1b.hxlc7e9zve.j6ej7ho2rk"},{"email":"7ngysyss7w.yjy0whd5fh@fosscord.com","password":"12.pl4jjp66wi1r.xx7s13gsgy.v2slv2vyx"},{"email":"7uhylwdaiq.w557htx0x@fosscord.com","password":"1j.icm6w8m4mh.4qyoql77m8.ar8kliax0s"},{"email":"y6yn1ckm1e.7xxizerecm@fosscord.com","password":"1e.om7n18zisn1w.usblhxf4p1m.r9ke41xox"},{"email":"uwdsktqhuw.4vmh5gmg7d@fosscord.com","password":"6.cdte4bk24b16.cf1sbtxlx1o.n62w4weh9"},{"email":"8v1nt755y.w0y1jgfcgm@fosscord.com","password":"f.ozxpvznxj41o.bs5s5dhua1l.ffayy0gsy"},{"email":"rmy9b61cij.qir0bjorcm@fosscord.com","password":"1h.bxjxpx0u6f13.e97yh8g761c.j8zog74iql"},{"email":"93ir0yiyi9.1f7bfzt3fb@fosscord.com","password":"18.vky28kwlw14.w1wsoyu6c15.yhxbr725xe"},{"email":"g0kqw9plr.v2zcovhyg6@fosscord.com","password":"1r.3txq1jt4zl1d.ha0ejtekjh.xhjl9e6vqg"},{"email":"xmk2q5zxa.v1ka9gm3a8@fosscord.com","password":"1l.ryvykh3ihm16.rxea04ifq0.h14sz83yisv"},{"email":"mqt2bmltj9.53o16bc6xn@fosscord.com","password":"i.vt66ajtme1f.lllyzaprk16.yb0yh0o1z"},{"email":"4kvjyddsv.7u7lmex2df@fosscord.com","password":"1i.axaegtd0qz.2yvfr5n261g.8s8fprsd8"},{"email":"yigntcopcc.8bchnlmclm@fosscord.com","password":"n.b5yn5xried1d.siep9e4fb1t.h6s6erw5t"},{"email":"meubr1b03t.t97015wih6@fosscord.com","password":"5.wu3izi2gyqi.iurx5qpp7l.znq1htzuel"},{"email":"xz3gta0hi.1x5o83xyee@fosscord.com","password":"14.uafjiryde3.oin9k24w3510.vkjmjleb4i"},{"email":"9jkrkk9r6o.6ossrgj919@fosscord.com","password":"v.u9531wtw2o11.151eg145bf.bk57nd0s6u"},{"email":"kf9fdmnacv.67shfcubvn@fosscord.com","password":"1i.7f1olv2hkt.v2cso7zxlfw.8ylhl33g1"},{"email":"k8zuiett0r.0w299k0t9j@fosscord.com","password":"t.1mrpwsil15.999lbrfvz1h.7od0kjlxo"},{"email":"8m9rt3vgg.vkpf6apx9@fosscord.com","password":"1n.2ohz11tk412.5ezp8ujcwn8.rvqqrozh9s"},{"email":"rfavhpnhc.6xwy7o3ulm@fosscord.com","password":"11.ikd54271zj.vq3brjark7.h1ryvz7ap8"},{"email":"6zmju5azrd.4bes4a3cq@fosscord.com","password":"5.litb6taajto.ownyp3uhjkh.f543o47uc9"},{"email":"ml5pst7t3g.kbvn8b1vg@fosscord.com","password":"1q.co2aumj6fw.fa18frro5e.vnpotfg209"},{"email":"kaa1r6srjs.wjriguic5e@fosscord.com","password":"y.y635jqxai9.s4hcd1weni5.51i7z3c26r"},{"email":"n09uhfkuc5.9aqau9qyk@fosscord.com","password":"1f.wtjqoqzdwg6.mfvvtcwtx91t.8ujt3pwx4"},{"email":"6y5y3px9oa.4183pg5aq6@fosscord.com","password":"11.8a00uh75g1i.d462wzpqv1t.dnd8sdvr"},{"email":"aqdzadem03.f8uv1m4zv4@fosscord.com","password":"4.4ndx89thn53.afcjfzjqe51o.ivaemdp5hf"},{"email":"oqv3944yav.31ccatif3r@fosscord.com","password":"1w.9cstqu9o21f.p40uqca3vl19.iqnn79lqde"},{"email":"akzyzmigv.9c6w5aj4o@fosscord.com","password":"m.m382wa8nznr.szvso4c03ke.ttw2jhnwh8"},{"email":"13dqfm57jo.e05e711ggt@fosscord.com","password":"1b.t1b51jt7lf.rhi4j32rw91u.0foqthilf"},{"email":"3derfs5v66.s2kbedbm3o@fosscord.com","password":"t.e153si8xso1m.9rv9il857fd.e3i0di3ope"},{"email":"92k9vmws7.dt9mvv6ijh@fosscord.com","password":"1r.r8oy0su9c1e.irtwz9gdna.3fddwt8k4n"},{"email":"w1huzvblr.q9qp44japt@fosscord.com","password":"1v.dfdr92srfs.3x2wd25frh15.z73xb3vol"},{"email":"vne3an2fif.32eq9woyl@fosscord.com","password":"7.lurd6n689ek.sf3gedrf711.5xclyfsn3"},{"email":"298zj4dvxf.5sfh7f2e2m@fosscord.com","password":"n.1rbv0z54wr1n.nt2041ujks.0gwbe80zyl"},{"email":"ywp1ssr2zh.gl97epixxu@fosscord.com","password":"1w.gfpvze8vq1p.is7b2795819.4hilzah3"},{"email":"kqzujy4m5j.ocydwl4yyh@fosscord.com","password":"1c.sqxzxuareez.fgczf2qh3en.yi5vo23phn"},{"email":"ck8n5p7d6d.2vu4cdm6iw@fosscord.com","password":"1o.g3lq6grnm1t.otf44zgiw1c.jfdgqubfjl"},{"email":"s3vqe9bzj.muec34461t@fosscord.com","password":"1u.i4u3eidof19.tl8hf5fpdv.mvbij0fdgc"},{"email":"7hedrsktw.oqe4hym4us@fosscord.com","password":"1j.orlptqc2h.hs6661zehh1r.ngepsoldvf"},{"email":"44tm2rsu6j.oxrw5ib1np@fosscord.com","password":"1o.n71dxtllrf9.htwjv6fsi81l.5w9pyr8eee"},{"email":"o28saa2e4t.m49530ir45@fosscord.com","password":"16.z31xrcp6li12.uaklzxvskl.nqyq23zqb"},{"email":"aaz3kkwx2q.u42rdyacy6@fosscord.com","password":"1k.aohk44bxqkq.6lec7k6yfa14.geiq4ok3b"},{"email":"ntw1oc87mh.js3q1iqxrh@fosscord.com","password":"15.li45vduoy15.h90fv4ytl1t.3v78qdvcq"},{"email":"cpkgoh313.lkdhhl039a@fosscord.com","password":"3.fdw00uv0dn1v.qz6frlgeh0.3g0c7xnn9le"},{"email":"wznnajnyww.3f5s5cf0lt@fosscord.com","password":"4.3d2ro1uvag2.9yum8m4gd5t.yd1zriwovn"},{"email":"odwdxlk49g.m113aywba@fosscord.com","password":"y.24hnap1ckh.n0q1dtobf717.0tzaopasse"},{"email":"0xt66uuwbs.24qfa4w82q@fosscord.com","password":"e.3cfcd0usw57.oydvjpl5wm1b.sxnf38ihh"},{"email":"4pxgasro0t.xifcrlp26f@fosscord.com","password":"1d.oxpqgh8jbgb.6epjawtwga1u.o5d33jm"},{"email":"l202g9q8xo.t4ck4xu44v@fosscord.com","password":"d.gyul2yhu7h1g.163rzn4kqik.e5qlstdwp"},{"email":"8mwzma33ko.b9on13ypjl@fosscord.com","password":"b.0sdy90whqr1o.rruwt57r8l14.hjejwclmr"},{"email":"h8dm19fu77.hzpnw8famh@fosscord.com","password":"s.q49kg1uq8gc.046rudurb1o.2lqegjfds"},{"email":"exkp3ve6z7.mdydbk9jy@fosscord.com","password":"16.bq8o0d13sd8.tri7wtdjpro.2ebtbyqgtt"},{"email":"n8fe02yphv.huwi91ywha@fosscord.com","password":"1d.8qp5wkq541k.ulwk4bzjsm19.q3qbxorto"},{"email":"lsslgvrdyb.u86qng3p7o@fosscord.com","password":"11.9q1m8gwavd.9z3kflcg5k1e.lrux8aqm8"},{"email":"0jur86ya2p.gb26btuz7@fosscord.com","password":"n.fflp1f1yksg.10rh6etc61.yld8y7u9hi"},{"email":"raseda2c45.vl9resp89r@fosscord.com","password":"b.6gd8az3ljg.es1yjenqskk.i4i8m466p"},{"email":"jmam7ha069.b96jzg1bkl@fosscord.com","password":"1f.2z41vc92bo.84f3d3j3gra.5yev9enzv"},{"email":"pp2rki7hjd.a037bg6u6@fosscord.com","password":"3.nktq53a97c19.khsapwl0wd.ej16kksime"},{"email":"c35l8m3ikr.e7vx8nmbil@fosscord.com","password":"2.oryjofui8mu.7jes36sirs1u.oclq1geaf"},{"email":"ufhsl7tn5u.j4ey0abswv@fosscord.com","password":"14.uctn73o6h1n.t75arwloxgf.nvgdr4l41o"},{"email":"8ru4fr2ed.kf8ffg9ko8@fosscord.com","password":"n.hqxwr2ypwd1l.vu23byfp3c.nzgszptoqk"},{"email":"6gmjeij67o.ep5256bmf@fosscord.com","password":"1i.237gs5pk5j1w.yvuhvp9ho15.l4qibsw5i"},{"email":"4wrhgqel0w.e0sz7l0zki@fosscord.com","password":"w.g09qtor0p1g.a5uzjl6u3g17.v3z6rhb9h"},{"email":"3860ixs8g.6pha1slnur@fosscord.com","password":"m.o7o62cqw3g.wkkaak7zz8.h82m4nenbf"},{"email":"wnnpg8stto.zwsxqfp38i@fosscord.com","password":"c.k2b6jn1b3r14.ojpvlbxil1r.rpkncuyqp"},{"email":"t04ss33dlw.98dpq7j8rg@fosscord.com","password":"5.7zgfmumai7.iphztcsjfw1h.sq2kp3j9j"},{"email":"hy53et7kw6.vsku4tebj9@fosscord.com","password":"3.ayjddj0roe1m.ngz1qajzlgu.xue35w1d1d"},{"email":"252ueajele.j4euv8la1d@fosscord.com","password":"h.tw1utyw7mh9.ydii1rkvp4.8xafwfxrqd"},{"email":"ye2mi1d86.uqa7ig7qxb@fosscord.com","password":"j.pn3eoar1ft1e.k8febwch91o.fzau5lnbx"},{"email":"4cq5y22mm8.q33hk612wu@fosscord.com","password":"1l.7tx03ihc9e1a.3i2l76ur5.28yffumat8"},{"email":"d9op87vvj7.vbu23p4mnq@fosscord.com","password":"10.z7pgyokesip.0i0axexmwpa.0p5xrlag9k"},{"email":"tnhgsqizxh.14ulf4jinl@fosscord.com","password":"j.8p0jucy5xk10.creosnkf2o.vzznt05x"},{"email":"8h2h3w3ex5.8ogl7f027n@fosscord.com","password":"1b.08wkhdm03g3.8hdklh1zj41t.fq57w9raf"},{"email":"ommn4ocwtn.1fkdjbz2v8@fosscord.com","password":"1e.i8k15b9uk1p.70n34lxbzf.4inv63cwt8"},{"email":"fnxg92zeqn.ljg5uumt3e@fosscord.com","password":"w.ltho2dsgveu.d1ome2w0x8j.7wr2hq1wk8"},{"email":"lzi8aurosp.mck9i974of@fosscord.com","password":"u.zezf4qdz2p0.l2g634tak98.ql0n1tg6sq"},{"email":"fdhv4fccm5.o9x209i94g@fosscord.com","password":"5.2o84u6v43619.4c0c71a9gk7.n9cmjegefv"},{"email":"tuedrm1ajt.bxjgzsyj4s@fosscord.com","password":"1m.ng2h807gvu.rhd056e6bbb.lkvewwp2tg"},{"email":"488bryb32a.x928qzsf8g@fosscord.com","password":"y.n2c3x3irffa.fz9xwiimno8.nnpvm280oc"},{"email":"n928oorjaa.kj35rf9p35@fosscord.com","password":"1l.flvmvopcj16.pmx6n9hi7hi.v9odjzq3at"},{"email":"emzd8qz0f8.b972dvhf0m@fosscord.com","password":"3.7umgbd5apm18.0n6yi8ol9g.m4607npuc"},{"email":"g7jzdulwv5.0a2wzws2ua@fosscord.com","password":"10.1u1sac19wkf.lvi2qwfhtq16.8wbdddpms"},{"email":"giuivahumo.7iqapfnbfr@fosscord.com","password":"q.pzck5qtbype.llhl9ypv6b1e.3dz8gsv0pg"},{"email":"0t84mm2pj9.kycgvqkuag@fosscord.com","password":"1t.kpvjmvipo14.kwv0np3ordv.ustw31ifu"},{"email":"vx5t6yurg7.pocn2c069m@fosscord.com","password":"f.gdwgrk0wia1u.7m1ozam0b0.d5y62kwyih5"},{"email":"p7arvq1hha.7wryrvhvl@fosscord.com","password":"1v.eb62r3rx71h.d3fhbfdxa1l.4gzcu184s"},{"email":"adp64dkhdd.q2nc2qvy3@fosscord.com","password":"q.96rt5rc517h.3f0foodom4.h1wee4z428"},{"email":"pfhrq2kv8.92dq9bxy8a@fosscord.com","password":"1g.nfaha2xx7hi.vtng22emxs1l.fpbra2wo6h"},{"email":"jjnysdssoc.eqr6v2pqeo@fosscord.com","password":"1.112arb9m3cb.w3yfq6ekz1d.keb15ptd5"},{"email":"m5rab6dhrc.p1tnxv9feg@fosscord.com","password":"1.3guc8m7j0uj4.xphgg3121714.7ii7ah6g7f"},{"email":"zgq1iount.blsiqtyvc6@fosscord.com","password":"16.dzt6188au1w.ilwc3p3ds17.7j7lcsqur"},{"email":"ix7nx1ce4a.3wj4gs8b7p@fosscord.com","password":"13.p2v1p2nwa1t.2yarcqsmzk.ay7w9u0p1r"},{"email":"5jh7wm63ug.feyytgy11g@fosscord.com","password":"10.otpp2mz0smb.uv94hcp26c8.a3nlz16n14"},{"email":"9cd7yy1ps4.jet2fn1fdb@fosscord.com","password":"9.bjtfocm7zk12.sushyeb1yg.lhtmj6a70t"},{"email":"20u20f6dlk.l8n5tvh2re@fosscord.com","password":"19.qfrr25rarj.4tzf063a9n4.3i5s3vm30b"},{"email":"b1hnmmwcb.q21mrflg1h@fosscord.com","password":"3.ysh10ultyjz.nz8azt84216.lxn1kgvly"},{"email":"j7nhj8s2d.aqaeidbi8m@fosscord.com","password":"g.nlnw7ejuqbz.41exhwj2wiv.1yr0njmd"}]
\ No newline at end of file
diff --git a/scripts/stresstest/config.json.example b/scripts/stresstest/config.json.example
new file mode 100644
index 00000000..73f52f05
--- /dev/null
+++ b/scripts/stresstest/config.json.example
@@ -0,0 +1,5 @@
+{
+	"url": "",
+	"text-channel": "",
+	"invite": ""
+}
diff --git a/scripts/stresstest/index.js b/scripts/stresstest/index.js
new file mode 100644
index 00000000..740a9011
--- /dev/null
+++ b/scripts/stresstest/index.js
@@ -0,0 +1,38 @@
+const register = require("./src/register");
+const login = require("./src/login/index");
+const config = require("./config.json");
+const figlet = require("figlet");
+const sendMessage = require("./src/message/send");
+const fs = require("fs");
+figlet("Fosscord Stress Test :)", function (err, data) {
+	if (err) {
+		console.log("Something went wrong...");
+		console.dir(err);
+		return;
+	}
+	console.log("\x1b[32m", data);
+});
+setInterval(() => {
+	generate();
+}, 1000 * 5);
+setInterval(() => {
+	getUsers();
+}, 60 * 1000);
+async function generate() {
+	let accounts = await JSON.parse(fs.readFileSync("accounts.json"));
+	console.log(accounts);
+	let account = await register();
+	accounts.push(account);
+	fs.writeFileSync("accounts.json", JSON.stringify(accounts));
+	console.log(accounts.length);
+	let y = await login(account);
+	sendMessage(y);
+}
+async function getUsers() {
+	let accounts = await JSON.parse(fs.readFileSync("accounts.json"));
+	accounts.forEach(async (x) => {
+		let y = await login(x);
+		console.log(y);
+		sendMessage(y);
+	});
+}
diff --git a/scripts/stresstest/package-lock.json b/scripts/stresstest/package-lock.json
new file mode 100644
index 00000000..81c9b817
--- /dev/null
+++ b/scripts/stresstest/package-lock.json
@@ -0,0 +1,856 @@
+{
+	"name": "stresstest",
+	"version": "1.0.0",
+	"lockfileVersion": 2,
+	"requires": true,
+	"packages": {
+		"": {
+			"name": "stresstest",
+			"version": "1.0.0",
+			"license": "ISC",
+			"dependencies": {
+				"figlet": "^1.5.2",
+				"node-fetch": "^2.6.6",
+				"request": "^2.88.2"
+			}
+		},
+		"node_modules/ajv": {
+			"version": "6.12.6",
+			"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+			"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+			"dependencies": {
+				"fast-deep-equal": "^3.1.1",
+				"fast-json-stable-stringify": "^2.0.0",
+				"json-schema-traverse": "^0.4.1",
+				"uri-js": "^4.2.2"
+			},
+			"funding": {
+				"type": "github",
+				"url": "https://github.com/sponsors/epoberezkin"
+			}
+		},
+		"node_modules/asn1": {
+			"version": "0.2.6",
+			"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
+			"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
+			"dependencies": {
+				"safer-buffer": "~2.1.0"
+			}
+		},
+		"node_modules/assert-plus": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+			"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+			"engines": {
+				"node": ">=0.8"
+			}
+		},
+		"node_modules/asynckit": {
+			"version": "0.4.0",
+			"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+			"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+		},
+		"node_modules/aws-sign2": {
+			"version": "0.7.0",
+			"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+			"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/aws4": {
+			"version": "1.11.0",
+			"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
+			"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
+		},
+		"node_modules/bcrypt-pbkdf": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+			"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+			"dependencies": {
+				"tweetnacl": "^0.14.3"
+			}
+		},
+		"node_modules/caseless": {
+			"version": "0.12.0",
+			"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+			"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+		},
+		"node_modules/combined-stream": {
+			"version": "1.0.8",
+			"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+			"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+			"dependencies": {
+				"delayed-stream": "~1.0.0"
+			},
+			"engines": {
+				"node": ">= 0.8"
+			}
+		},
+		"node_modules/core-util-is": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+			"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+		},
+		"node_modules/dashdash": {
+			"version": "1.14.1",
+			"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+			"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+			"dependencies": {
+				"assert-plus": "^1.0.0"
+			},
+			"engines": {
+				"node": ">=0.10"
+			}
+		},
+		"node_modules/delayed-stream": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+			"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+			"engines": {
+				"node": ">=0.4.0"
+			}
+		},
+		"node_modules/ecc-jsbn": {
+			"version": "0.1.2",
+			"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+			"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+			"dependencies": {
+				"jsbn": "~0.1.0",
+				"safer-buffer": "^2.1.0"
+			}
+		},
+		"node_modules/extend": {
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+			"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+		},
+		"node_modules/extsprintf": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+			"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
+			"engines": [
+				"node >=0.6.0"
+			]
+		},
+		"node_modules/fast-deep-equal": {
+			"version": "3.1.3",
+			"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+			"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+		},
+		"node_modules/fast-json-stable-stringify": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+			"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
+		},
+		"node_modules/figlet": {
+			"version": "1.5.2",
+			"resolved": "https://registry.npmjs.org/figlet/-/figlet-1.5.2.tgz",
+			"integrity": "sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==",
+			"engines": {
+				"node": ">= 0.4.0"
+			}
+		},
+		"node_modules/forever-agent": {
+			"version": "0.6.1",
+			"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+			"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/form-data": {
+			"version": "2.3.3",
+			"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+			"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+			"dependencies": {
+				"asynckit": "^0.4.0",
+				"combined-stream": "^1.0.6",
+				"mime-types": "^2.1.12"
+			},
+			"engines": {
+				"node": ">= 0.12"
+			}
+		},
+		"node_modules/getpass": {
+			"version": "0.1.7",
+			"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+			"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+			"dependencies": {
+				"assert-plus": "^1.0.0"
+			}
+		},
+		"node_modules/har-schema": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+			"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
+			"engines": {
+				"node": ">=4"
+			}
+		},
+		"node_modules/har-validator": {
+			"version": "5.1.5",
+			"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
+			"integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
+			"deprecated": "this library is no longer supported",
+			"dependencies": {
+				"ajv": "^6.12.3",
+				"har-schema": "^2.0.0"
+			},
+			"engines": {
+				"node": ">=6"
+			}
+		},
+		"node_modules/http-signature": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+			"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+			"dependencies": {
+				"assert-plus": "^1.0.0",
+				"jsprim": "^1.2.2",
+				"sshpk": "^1.7.0"
+			},
+			"engines": {
+				"node": ">=0.8",
+				"npm": ">=1.3.7"
+			}
+		},
+		"node_modules/is-typedarray": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+			"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+		},
+		"node_modules/isstream": {
+			"version": "0.1.2",
+			"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+			"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+		},
+		"node_modules/jsbn": {
+			"version": "0.1.1",
+			"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+			"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
+		},
+		"node_modules/json-schema": {
+			"version": "0.4.0",
+			"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
+			"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
+		},
+		"node_modules/json-schema-traverse": {
+			"version": "0.4.1",
+			"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+			"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+		},
+		"node_modules/json-stringify-safe": {
+			"version": "5.0.1",
+			"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+			"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+		},
+		"node_modules/jsprim": {
+			"version": "1.4.2",
+			"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
+			"integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
+			"dependencies": {
+				"assert-plus": "1.0.0",
+				"extsprintf": "1.3.0",
+				"json-schema": "0.4.0",
+				"verror": "1.10.0"
+			},
+			"engines": {
+				"node": ">=0.6.0"
+			}
+		},
+		"node_modules/mime-db": {
+			"version": "1.51.0",
+			"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
+			"integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==",
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/mime-types": {
+			"version": "2.1.34",
+			"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
+			"integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
+			"dependencies": {
+				"mime-db": "1.51.0"
+			},
+			"engines": {
+				"node": ">= 0.6"
+			}
+		},
+		"node_modules/node-fetch": {
+			"version": "2.6.7",
+			"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
+			"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+			"dependencies": {
+				"whatwg-url": "^5.0.0"
+			},
+			"engines": {
+				"node": "4.x || >=6.0.0"
+			},
+			"peerDependencies": {
+				"encoding": "^0.1.0"
+			},
+			"peerDependenciesMeta": {
+				"encoding": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/oauth-sign": {
+			"version": "0.9.0",
+			"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+			"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/performance-now": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+			"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+		},
+		"node_modules/psl": {
+			"version": "1.8.0",
+			"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
+			"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
+		},
+		"node_modules/punycode": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+			"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+			"engines": {
+				"node": ">=6"
+			}
+		},
+		"node_modules/qs": {
+			"version": "6.5.2",
+			"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+			"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
+			"engines": {
+				"node": ">=0.6"
+			}
+		},
+		"node_modules/request": {
+			"version": "2.88.2",
+			"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
+			"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
+			"deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142",
+			"dependencies": {
+				"aws-sign2": "~0.7.0",
+				"aws4": "^1.8.0",
+				"caseless": "~0.12.0",
+				"combined-stream": "~1.0.6",
+				"extend": "~3.0.2",
+				"forever-agent": "~0.6.1",
+				"form-data": "~2.3.2",
+				"har-validator": "~5.1.3",
+				"http-signature": "~1.2.0",
+				"is-typedarray": "~1.0.0",
+				"isstream": "~0.1.2",
+				"json-stringify-safe": "~5.0.1",
+				"mime-types": "~2.1.19",
+				"oauth-sign": "~0.9.0",
+				"performance-now": "^2.1.0",
+				"qs": "~6.5.2",
+				"safe-buffer": "^5.1.2",
+				"tough-cookie": "~2.5.0",
+				"tunnel-agent": "^0.6.0",
+				"uuid": "^3.3.2"
+			},
+			"engines": {
+				"node": ">= 6"
+			}
+		},
+		"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/sshpk": {
+			"version": "1.16.1",
+			"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
+			"integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
+			"dependencies": {
+				"asn1": "~0.2.3",
+				"assert-plus": "^1.0.0",
+				"bcrypt-pbkdf": "^1.0.0",
+				"dashdash": "^1.12.0",
+				"ecc-jsbn": "~0.1.1",
+				"getpass": "^0.1.1",
+				"jsbn": "~0.1.0",
+				"safer-buffer": "^2.0.2",
+				"tweetnacl": "~0.14.0"
+			},
+			"bin": {
+				"sshpk-conv": "bin/sshpk-conv",
+				"sshpk-sign": "bin/sshpk-sign",
+				"sshpk-verify": "bin/sshpk-verify"
+			},
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
+		"node_modules/tough-cookie": {
+			"version": "2.5.0",
+			"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+			"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
+			"dependencies": {
+				"psl": "^1.1.28",
+				"punycode": "^2.1.1"
+			},
+			"engines": {
+				"node": ">=0.8"
+			}
+		},
+		"node_modules/tr46": {
+			"version": "0.0.3",
+			"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+			"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
+		},
+		"node_modules/tunnel-agent": {
+			"version": "0.6.0",
+			"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+			"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+			"dependencies": {
+				"safe-buffer": "^5.0.1"
+			},
+			"engines": {
+				"node": "*"
+			}
+		},
+		"node_modules/tweetnacl": {
+			"version": "0.14.5",
+			"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+			"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+		},
+		"node_modules/uri-js": {
+			"version": "4.4.1",
+			"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+			"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+			"dependencies": {
+				"punycode": "^2.1.0"
+			}
+		},
+		"node_modules/uuid": {
+			"version": "3.4.0",
+			"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+			"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
+			"deprecated": "Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.",
+			"bin": {
+				"uuid": "bin/uuid"
+			}
+		},
+		"node_modules/verror": {
+			"version": "1.10.0",
+			"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+			"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+			"engines": [
+				"node >=0.6.0"
+			],
+			"dependencies": {
+				"assert-plus": "^1.0.0",
+				"core-util-is": "1.0.2",
+				"extsprintf": "^1.2.0"
+			}
+		},
+		"node_modules/webidl-conversions": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+			"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
+		},
+		"node_modules/whatwg-url": {
+			"version": "5.0.0",
+			"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+			"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
+			"dependencies": {
+				"tr46": "~0.0.3",
+				"webidl-conversions": "^3.0.0"
+			}
+		}
+	},
+	"dependencies": {
+		"ajv": {
+			"version": "6.12.6",
+			"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+			"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+			"requires": {
+				"fast-deep-equal": "^3.1.1",
+				"fast-json-stable-stringify": "^2.0.0",
+				"json-schema-traverse": "^0.4.1",
+				"uri-js": "^4.2.2"
+			}
+		},
+		"asn1": {
+			"version": "0.2.6",
+			"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
+			"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
+			"requires": {
+				"safer-buffer": "~2.1.0"
+			}
+		},
+		"assert-plus": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+			"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+		},
+		"asynckit": {
+			"version": "0.4.0",
+			"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+			"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+		},
+		"aws-sign2": {
+			"version": "0.7.0",
+			"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+			"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
+		},
+		"aws4": {
+			"version": "1.11.0",
+			"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
+			"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
+		},
+		"bcrypt-pbkdf": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+			"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+			"requires": {
+				"tweetnacl": "^0.14.3"
+			}
+		},
+		"caseless": {
+			"version": "0.12.0",
+			"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+			"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+		},
+		"combined-stream": {
+			"version": "1.0.8",
+			"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+			"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+			"requires": {
+				"delayed-stream": "~1.0.0"
+			}
+		},
+		"core-util-is": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+			"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+		},
+		"dashdash": {
+			"version": "1.14.1",
+			"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+			"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+			"requires": {
+				"assert-plus": "^1.0.0"
+			}
+		},
+		"delayed-stream": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+			"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+		},
+		"ecc-jsbn": {
+			"version": "0.1.2",
+			"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+			"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+			"requires": {
+				"jsbn": "~0.1.0",
+				"safer-buffer": "^2.1.0"
+			}
+		},
+		"extend": {
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+			"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+		},
+		"extsprintf": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+			"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
+		},
+		"fast-deep-equal": {
+			"version": "3.1.3",
+			"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+			"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+		},
+		"fast-json-stable-stringify": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+			"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
+		},
+		"figlet": {
+			"version": "1.5.2",
+			"resolved": "https://registry.npmjs.org/figlet/-/figlet-1.5.2.tgz",
+			"integrity": "sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ=="
+		},
+		"forever-agent": {
+			"version": "0.6.1",
+			"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+			"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+		},
+		"form-data": {
+			"version": "2.3.3",
+			"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+			"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+			"requires": {
+				"asynckit": "^0.4.0",
+				"combined-stream": "^1.0.6",
+				"mime-types": "^2.1.12"
+			}
+		},
+		"getpass": {
+			"version": "0.1.7",
+			"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+			"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+			"requires": {
+				"assert-plus": "^1.0.0"
+			}
+		},
+		"har-schema": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+			"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
+		},
+		"har-validator": {
+			"version": "5.1.5",
+			"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
+			"integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
+			"requires": {
+				"ajv": "^6.12.3",
+				"har-schema": "^2.0.0"
+			}
+		},
+		"http-signature": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+			"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+			"requires": {
+				"assert-plus": "^1.0.0",
+				"jsprim": "^1.2.2",
+				"sshpk": "^1.7.0"
+			}
+		},
+		"is-typedarray": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+			"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+		},
+		"isstream": {
+			"version": "0.1.2",
+			"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+			"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+		},
+		"jsbn": {
+			"version": "0.1.1",
+			"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+			"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
+		},
+		"json-schema": {
+			"version": "0.4.0",
+			"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
+			"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
+		},
+		"json-schema-traverse": {
+			"version": "0.4.1",
+			"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+			"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+		},
+		"json-stringify-safe": {
+			"version": "5.0.1",
+			"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+			"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+		},
+		"jsprim": {
+			"version": "1.4.2",
+			"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
+			"integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
+			"requires": {
+				"assert-plus": "1.0.0",
+				"extsprintf": "1.3.0",
+				"json-schema": "0.4.0",
+				"verror": "1.10.0"
+			}
+		},
+		"mime-db": {
+			"version": "1.51.0",
+			"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
+			"integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g=="
+		},
+		"mime-types": {
+			"version": "2.1.34",
+			"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
+			"integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
+			"requires": {
+				"mime-db": "1.51.0"
+			}
+		},
+		"node-fetch": {
+			"version": "2.6.7",
+			"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
+			"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+			"requires": {
+				"whatwg-url": "^5.0.0"
+			}
+		},
+		"oauth-sign": {
+			"version": "0.9.0",
+			"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+			"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
+		},
+		"performance-now": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+			"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+		},
+		"psl": {
+			"version": "1.8.0",
+			"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
+			"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
+		},
+		"punycode": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+			"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+		},
+		"qs": {
+			"version": "6.5.2",
+			"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+			"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
+		},
+		"request": {
+			"version": "2.88.2",
+			"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
+			"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
+			"requires": {
+				"aws-sign2": "~0.7.0",
+				"aws4": "^1.8.0",
+				"caseless": "~0.12.0",
+				"combined-stream": "~1.0.6",
+				"extend": "~3.0.2",
+				"forever-agent": "~0.6.1",
+				"form-data": "~2.3.2",
+				"har-validator": "~5.1.3",
+				"http-signature": "~1.2.0",
+				"is-typedarray": "~1.0.0",
+				"isstream": "~0.1.2",
+				"json-stringify-safe": "~5.0.1",
+				"mime-types": "~2.1.19",
+				"oauth-sign": "~0.9.0",
+				"performance-now": "^2.1.0",
+				"qs": "~6.5.2",
+				"safe-buffer": "^5.1.2",
+				"tough-cookie": "~2.5.0",
+				"tunnel-agent": "^0.6.0",
+				"uuid": "^3.3.2"
+			}
+		},
+		"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=="
+		},
+		"sshpk": {
+			"version": "1.16.1",
+			"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
+			"integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
+			"requires": {
+				"asn1": "~0.2.3",
+				"assert-plus": "^1.0.0",
+				"bcrypt-pbkdf": "^1.0.0",
+				"dashdash": "^1.12.0",
+				"ecc-jsbn": "~0.1.1",
+				"getpass": "^0.1.1",
+				"jsbn": "~0.1.0",
+				"safer-buffer": "^2.0.2",
+				"tweetnacl": "~0.14.0"
+			}
+		},
+		"tough-cookie": {
+			"version": "2.5.0",
+			"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+			"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
+			"requires": {
+				"psl": "^1.1.28",
+				"punycode": "^2.1.1"
+			}
+		},
+		"tr46": {
+			"version": "0.0.3",
+			"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+			"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
+		},
+		"tunnel-agent": {
+			"version": "0.6.0",
+			"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+			"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+			"requires": {
+				"safe-buffer": "^5.0.1"
+			}
+		},
+		"tweetnacl": {
+			"version": "0.14.5",
+			"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+			"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+		},
+		"uri-js": {
+			"version": "4.4.1",
+			"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+			"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+			"requires": {
+				"punycode": "^2.1.0"
+			}
+		},
+		"uuid": {
+			"version": "3.4.0",
+			"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+			"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
+		},
+		"verror": {
+			"version": "1.10.0",
+			"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+			"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+			"requires": {
+				"assert-plus": "^1.0.0",
+				"core-util-is": "1.0.2",
+				"extsprintf": "^1.2.0"
+			}
+		},
+		"webidl-conversions": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+			"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
+		},
+		"whatwg-url": {
+			"version": "5.0.0",
+			"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+			"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
+			"requires": {
+				"tr46": "~0.0.3",
+				"webidl-conversions": "^3.0.0"
+			}
+		}
+	}
+}
diff --git a/scripts/stresstest/package.json b/scripts/stresstest/package.json
new file mode 100644
index 00000000..8d94d05b
--- /dev/null
+++ b/scripts/stresstest/package.json
@@ -0,0 +1,17 @@
+{
+	"name": "stresstest",
+	"version": "1.0.0",
+	"description": "",
+	"main": "index.js",
+	"scripts": {
+		"test": "echo \"Error: no test specified\" && exit 1",
+		"start": "node ."
+	},
+	"author": "",
+	"license": "ISC",
+	"dependencies": {
+		"figlet": "^1.5.2",
+		"node-fetch": "^2.6.6",
+		"request": "^2.88.2"
+	}
+}
diff --git a/scripts/stresstest/src/login/index.js b/scripts/stresstest/src/login/index.js
new file mode 100644
index 00000000..b153550e
--- /dev/null
+++ b/scripts/stresstest/src/login/index.js
@@ -0,0 +1,20 @@
+const fetch = require("node-fetch");
+const fs = require("fs");
+let config = require("../../config.json");
+module.exports = login;
+async function login(account) {
+	let body = {
+		fingerprint: "805826570869932034.wR8vi8lGlFBJerErO9LG5NViJFw",
+		login: account.email,
+		password: account.password
+	};
+	let x = await fetch(config.url + "/auth/login", {
+		method: "POST",
+		headers: { "Content-Type": "application/json" },
+		body: JSON.stringify(body)
+	});
+	console.log(x);
+	x = await x.json();
+	console.log(x);
+	return x;
+}
diff --git a/scripts/stresstest/src/message/send.js b/scripts/stresstest/src/message/send.js
new file mode 100644
index 00000000..d1b86914
--- /dev/null
+++ b/scripts/stresstest/src/message/send.js
@@ -0,0 +1,23 @@
+const fetch = require("node-fetch");
+const fs = require("fs");
+let config = require("./../../config.json");
+module.exports = sendMessage;
+async function sendMessage(account) {
+	let body = {
+		fingerprint: "805826570869932034.wR8vi8lGlFBJerErO9LG5NViJFw",
+		content: "Test",
+		tts: false
+	};
+	let x = await fetch(config.url + "/channels/" + config["text-channel"] + "/messages", {
+		method: "POST",
+		headers: {
+			"Content-Type": "application/json",
+			Authorization: account.token
+		},
+		body: JSON.stringify(body)
+	});
+	console.log(x);
+	x = await x.json();
+	console.log(x);
+	return x;
+}
diff --git a/scripts/stresstest/src/register/index.js b/scripts/stresstest/src/register/index.js
new file mode 100644
index 00000000..578b9022
--- /dev/null
+++ b/scripts/stresstest/src/register/index.js
@@ -0,0 +1,34 @@
+const fetch = require("node-fetch");
+const fs = require("fs");
+let config = require("./../../config.json");
+module.exports = generate;
+async function generate() {
+	let mail = (Math.random() + 10).toString(36).substring(2);
+	mail = mail + "." + (Math.random() + 10).toString(36).substring(2) + "@stresstest.com";
+	let password =
+		(Math.random() * 69).toString(36).substring(-7) +
+		(Math.random() * 69).toString(36).substring(-7) +
+		(Math.random() * 69).toString(36).substring(-8);
+	console.log(mail);
+	console.log(password);
+	let body = {
+		fingerprint: "805826570869932034.wR8vi8lGlFBJerErO9LG5NViJFw",
+		email: mail,
+		username: "Fosscord Stress Test",
+		password: password,
+		invite: config.invite,
+		consent: true,
+		date_of_birth: "2000-04-04",
+		gift_code_sku_id: null,
+		captcha_key: null
+	};
+	let x = await fetch(config.url + "/auth/register", {
+		method: "POST",
+		headers: { "Content-Type": "application/json" },
+		body: JSON.stringify(body)
+	});
+	console.log(x);
+	x = await x.json();
+	console.log(x);
+	return { email: mail, password: password };
+}
diff --git a/scripts/update_schemas.js b/scripts/update_schemas.js
new file mode 100644
index 00000000..151b52d2
--- /dev/null
+++ b/scripts/update_schemas.js
@@ -0,0 +1,9 @@
+const path = require("path");
+const fs = require("fs");
+const { env } = require("process");
+const { execSync } = require("child_process");
+const { argv, stdout, exit } = require("process");
+
+const { execIn, getLines, parts } = require("./utils");
+
+execIn("node scripts/generate_schema.js", path.join("."));
diff --git a/scripts/utils.js b/scripts/utils.js
new file mode 100644
index 00000000..b679392b
--- /dev/null
+++ b/scripts/utils.js
@@ -0,0 +1,87 @@
+const path = require("path");
+const fs = require("fs");
+const { env } = require("process");
+const { execSync } = require("child_process");
+const { argv, stdout, exit } = require("process");
+
+const projectRoot = path.resolve(path.join(__dirname, ".."));
+
+function copyRecursiveSync(src, dest) {
+	//if (verbose) console.log(`cpsync: ${src} -> ${dest}`);
+	let exists = fs.existsSync(src);
+	if (!exists) {
+		console.log(src + " doesn't exist, not copying!");
+		return;
+	}
+	let stats = exists && fs.statSync(src);
+	let isDirectory = exists && stats.isDirectory();
+	if (isDirectory) {
+		fs.mkdirSync(dest, { recursive: true });
+		fs.readdirSync(src).forEach(function (childItemName) {
+			copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName));
+		});
+	} else {
+		fs.copyFileSync(src, dest);
+	}
+}
+
+function execIn(cmd, workdir, opts) {
+	try {
+		return execSync(cmd, {
+			cwd: workdir,
+			shell: true,
+			env: process.env,
+			encoding: "utf-8",
+			...opts
+		});
+	} catch (error) {
+		return error.stdout;
+	}
+}
+
+function getLines(output) {
+	return output.split("\n").length;
+}
+
+function getDirs(dir) {
+	return fs.readdirSync(dir).filter((x) => {
+		try {
+			fs.readdirSync(dir.join(dir, x));
+			return true;
+		} catch (e) {
+			return false;
+		}
+	});
+}
+
+function walk(dir) {
+	let results = [];
+	let list = fs.readdirSync(dir);
+	list.forEach(function (file) {
+		file = dir + "/" + file;
+		let stat = fs.statSync(file);
+		if (stat && stat.isDirectory()) {
+			/* Recurse into a subdirectory */
+			results = results.concat(walk(file));
+		} else {
+			results.push(file);
+		}
+	});
+	return results;
+}
+
+function sanitizeVarName(str) {
+	return str.replace("-", "_").replace(/[^\w\s]/gi, "");
+}
+
+module.exports = {
+	//consts
+	projectRoot,
+	//functions
+	copyRecursiveSync,
+	execIn,
+	getLines,
+	getDirs,
+	walk,
+	sanitizeVarName
+};
diff --git a/scripts/utils/ask.js b/scripts/utils/ask.js
new file mode 100644
index 00000000..cb8a29f6
--- /dev/null
+++ b/scripts/utils/ask.js
@@ -0,0 +1,20 @@
+const readline = require("readline");
+const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
+
+async function ask(question) {
+	return new Promise((resolve, _reject) => {
+		return rl.question(question, (answer) => {
+			resolve(answer);
+		});
+	}).catch((err) => {
+		console.log(err);
+	});
+}
+async function askBool(question) {
+	return /y?/i.test(await ask(question));
+}
+
+module.exports = {
+    ask,
+    askBool
+}
\ No newline at end of file