summary refs log tree commit diff
diff options
context:
space:
mode:
authorTheArcaneBrony <myrainbowdash949@gmail.com>2022-08-13 17:16:23 +0200
committerTheArcaneBrony <myrainbowdash949@gmail.com>2022-09-04 10:48:54 +0200
commit8769c7625cafd14e6f304601cc99a195e833d38b (patch)
tree94d37b1ef271490a032cde0605e1b1fb0fe01e0c
parentPlugins finally load! (diff)
downloadserver-8769c7625cafd14e6f304601cc99a195e833d38b.tar.xz
Fix config table, add plugin events, implement onPreMessageEvent, add
http error data field, migrations
-rw-r--r--scripts/build.js2
-rw-r--r--src/Server.ts4
-rw-r--r--src/api/Server.ts2
-rw-r--r--src/api/middlewares/ErrorHandler.ts8
-rw-r--r--src/api/routes/channels/#channel_id/messages/index.ts17
-rw-r--r--src/api/util/handlers/Message.ts11
-rw-r--r--src/cdn/Server.ts3
-rw-r--r--src/gateway/Server.ts3
-rw-r--r--src/plugins/example-plugin/TestPlugin.ts28
-rw-r--r--src/plugins/example-plugin/TestSettings.ts11
-rw-r--r--src/start.ts22
-rw-r--r--src/util/entities/PluginConfig.ts2
-rw-r--r--src/util/entities/User.ts20
-rw-r--r--src/util/entities/index.ts5
-rw-r--r--src/util/migrations/mariadb/1660404644371-PluginConfigs.ts42
-rw-r--r--src/util/migrations/mariadb/1660534571948-UpdateMigrations.ts229
-rw-r--r--src/util/migrations/postgres/1660404619978-PluginConfigs.ts22
-rw-r--r--src/util/migrations/sqlite/1660534525799-UpdateMigrations.ts829
-rw-r--r--src/util/plugin/Plugin.ts155
-rw-r--r--src/util/plugin/PluginConfig.ts30
-rw-r--r--src/util/plugin/PluginEventHandler.ts67
-rw-r--r--src/util/plugin/PluginLoader.ts48
-rw-r--r--src/util/plugin/PluginStore.ts12
-rw-r--r--src/util/plugin/event_types/ChannelCreateEventArgs.ts17
-rw-r--r--src/util/plugin/event_types/GuildCreateEventArgs.ts15
-rw-r--r--src/util/plugin/event_types/LoginEventArgs.ts15
-rw-r--r--src/util/plugin/event_types/MessageEventArgs.ts15
-rw-r--r--src/util/plugin/event_types/PluginLoadedEventArgs.ts (renamed from src/util/plugin/plugin_data_objects/PluginLoadedEventArgs.ts)0
-rw-r--r--src/util/plugin/event_types/RegisterEventArgs.ts17
-rw-r--r--src/util/plugin/event_types/StatusChangeEventArgs.ts16
-rw-r--r--src/util/plugin/event_types/TypingEventArgs.ts17
-rwxr-xr-xsrc/util/plugin/event_types/_gen.sh74
-rw-r--r--src/util/plugin/event_types/_pdo (renamed from src/util/plugin/plugin_data_objects/_pdo)3
-rw-r--r--src/util/plugin/event_types/_todo.txt (renamed from src/util/plugin/plugin_data_objects/_todo.txt)0
-rw-r--r--src/util/plugin/event_types/base/EventResult.ts4
-rw-r--r--src/util/plugin/event_types/base/index.ts1
-rw-r--r--src/util/plugin/event_types/index.ts (renamed from src/util/plugin/plugin_data_objects/index.ts)2
-rw-r--r--src/util/plugin/index.ts5
-rw-r--r--src/util/plugin/plugin_data_objects/ChannelCreateEventArgs.ts7
-rw-r--r--src/util/plugin/plugin_data_objects/GuildCreateEventArgs.ts7
-rw-r--r--src/util/plugin/plugin_data_objects/LoginEventArgs.ts7
-rw-r--r--src/util/plugin/plugin_data_objects/MessageEventArgs.ts7
-rw-r--r--src/util/plugin/plugin_data_objects/RegisterEventArgs.ts7
-rw-r--r--src/util/plugin/plugin_data_objects/TypingEventArgs.ts7
-rwxr-xr-xsrc/util/plugin/plugin_data_objects/_gen.sh21
-rw-r--r--src/util/util/Logo.ts41
-rw-r--r--src/util/util/imports/HTTPError.ts2
47 files changed, 1746 insertions, 133 deletions
diff --git a/scripts/build.js b/scripts/build.js
index 7421aa4f..704909ce 100644
--- a/scripts/build.js
+++ b/scripts/build.js
@@ -29,7 +29,7 @@ if (silent) console.error = console.log = function () {};
 
 if (argv.includes("clean")) {
 	console.log(`[${++i}/${steps}] Cleaning...`);
-	let d = "../" + "/dist";
+	let d = "dist";
 		if (fs.existsSync(d)) {
 			fs.rmSync(d, { recursive: true });
 			if (verbose) console.log(`Deleted ${d}!`);
diff --git a/src/Server.ts b/src/Server.ts
index e672bd0c..85de728f 100644
--- a/src/Server.ts
+++ b/src/Server.ts
@@ -8,6 +8,7 @@ import { Config, getOrInitialiseDatabase } from "@fosscord/util";
 import * as Sentry from "@sentry/node";
 import * as Tracing from "@sentry/tracing";
 import { PluginLoader } from "@fosscord/util";
+import { PluginConfig } from "util/plugin/PluginConfig";
 
 const app = express();
 const server = http.createServer();
@@ -34,6 +35,7 @@ process.on("SIGTERM", () => {
 async function main() {
 	await getOrInitialiseDatabase();
 	await Config.init();
+	await PluginConfig.init();
 
 	//Sentry
 	if (Config.get().sentry.enabled) {
@@ -61,7 +63,7 @@ async function main() {
 	}
 
 	console.log(`[Server] ${green(`listening on port ${bold(port)}`)}`);
-	PluginLoader.loadPlugins();
+	await PluginLoader.loadPlugins();
 }
 
 main().catch(console.error);
diff --git a/src/api/Server.ts b/src/api/Server.ts
index 560014d0..a3e6f23b 100644
--- a/src/api/Server.ts
+++ b/src/api/Server.ts
@@ -12,6 +12,7 @@ import TestClient from "./middlewares/TestClient";
 import { initTranslation } from "./middlewares/Translation";
 import { initInstance } from "./util/handlers/Instance";
 import fs from "fs";
+import { PluginConfig } from "util/plugin/PluginConfig";
 
 export interface FosscordServerOptions extends ServerOptions {}
 
@@ -35,6 +36,7 @@ export class FosscordServer extends Server {
 	async start() {
 		await getOrInitialiseDatabase();
 		await Config.init();
+		await PluginConfig.init();
 		await initEvent();
 		await initInstance();
 
diff --git a/src/api/middlewares/ErrorHandler.ts b/src/api/middlewares/ErrorHandler.ts
index 813adc18..a3333be5 100644
--- a/src/api/middlewares/ErrorHandler.ts
+++ b/src/api/middlewares/ErrorHandler.ts
@@ -10,8 +10,12 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne
 		let httpcode = code;
 		let message = error?.toString();
 		let errors = undefined;
+		let data = undefined;
 
-		if (error instanceof HTTPError && error.code) code = httpcode = error.code;
+		if (error instanceof HTTPError && error.code) {
+			code = httpcode = error.code;
+			if(error.data) data = error.data;
+		}
 		else if (error instanceof ApiError) {
 			code = error.code;
 			message = error.message;
@@ -35,7 +39,7 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne
 
 		if (httpcode > 511) httpcode = 400;
 
-		res.status(httpcode).json({ code: code, message, errors });
+		res.status(httpcode).json({ code: code, message, errors, data });
 	} catch (error) {
 		console.error(`[Internal Server Error] 500`, error);
 		return res.status(500).json({ code: 500, message: "Internal Server Error" });
diff --git a/src/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts
index 5fdcb6f9..d542ea0f 100644
--- a/src/api/routes/channels/#channel_id/messages/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/index.ts
@@ -13,7 +13,11 @@ import {
 	MessageCreateEvent,
 	MessageCreateSchema,
 	Snowflake,
-	uploadFile
+	uploadFile,
+	Member,
+	MessageCreateSchema,
+	PluginEventHandler,
+	PreMessageEventArgs
 } from "@fosscord/util";
 import { Request, Response, Router } from "express";
 import multer from "multer";
@@ -206,9 +210,9 @@ router.post(
 				})
 			);
 		}
-
-		//Defining member fields
-		var member = await Member.findOneOrFail({ where: { id: req.user_id }, relations: ["roles"] });
+	
+	    //Defining member fields
+		var member = await Member.findOneOrFail({ where: { id: req.user_id }, relations: ["roles", "user"] });
 		// TODO: This doesn't work either
 		// member.roles = member.roles.filter((role) => {
 		// 	return role.id !== role.guild_id;
@@ -220,6 +224,11 @@ router.post(
 		// delete message.member.last_message_id;
 		// delete message.member.index;
 
+		let blocks = (await PluginEventHandler.preMessageEvent({
+			message
+		} as PreMessageEventArgs)).filter(x=>x.cancel);
+		if(blocks.length > 0) throw new HTTPError("Message denied.", 400, blocks.filter(x=>x.blockReason).map(x=>x.blockReason));
+
 		await Promise.all([
 			message.save(),
 			emitEvent({ event: "MESSAGE_CREATE", channel_id: channel_id, data: message } as MessageCreateEvent),
diff --git a/src/api/util/handlers/Message.ts b/src/api/util/handlers/Message.ts
index d760d27c..07ed11ad 100644
--- a/src/api/util/handlers/Message.ts
+++ b/src/api/util/handlers/Message.ts
@@ -21,6 +21,13 @@ import {
 	Role,
 	ROLE_MENTION,
 	User,
+	Application,
+	Webhook,
+	Attachment,
+	Config,
+	MessageCreateSchema,
+	PluginEventHandler,
+	PreMessageEventArgs,
 	USER_MENTION,
 	Webhook
 } from "@fosscord/util";
@@ -205,6 +212,10 @@ export async function postHandleMessage(message: Message) {
 export async function sendMessage(opts: MessageOptions) {
 	const message = await handleMessage({ ...opts, timestamp: new Date() });
 
+	if((await PluginEventHandler.preMessageEvent({
+		message
+	} as PreMessageEventArgs)).filter(x=>x.cancel).length > 0) return;
+
 	//TODO: check this, removed toJSON call
 	await Promise.all([
 		Message.insert(message),
diff --git a/src/cdn/Server.ts b/src/cdn/Server.ts
index 9cedaa02..8b684b53 100644
--- a/src/cdn/Server.ts
+++ b/src/cdn/Server.ts
@@ -5,6 +5,8 @@ import path from "path";
 import avatarsRoute from "./routes/avatars";
 import guildProfilesRoute from "./routes/guild-profiles";
 import iconsRoute from "./routes/role-icons";
+import bodyParser from "body-parser";
+import { PluginConfig } from "util/plugin/PluginConfig";
 
 export interface CDNServerOptions extends ServerOptions {}
 
@@ -18,6 +20,7 @@ export class CDNServer extends Server {
 	async start() {
 		await getOrInitialiseDatabase();
 		await Config.init();
+		await PluginConfig.init();
 		this.app.use((req, res, next) => {
 			res.set("Access-Control-Allow-Origin", "*");
 			// TODO: use better CSP policy
diff --git a/src/gateway/Server.ts b/src/gateway/Server.ts
index 97da3fa0..60a82d8f 100644
--- a/src/gateway/Server.ts
+++ b/src/gateway/Server.ts
@@ -3,6 +3,8 @@ import dotenv from "dotenv";
 import http from "http";
 import ws from "ws";
 import { Connection } from "./events/Connection";
+import http from "http";
+import { PluginConfig } from "util/plugin/PluginConfig";
 dotenv.config();
 
 export class Server {
@@ -40,6 +42,7 @@ export class Server {
 	async start(): Promise<void> {
 		await getOrInitialiseDatabase();
 		await Config.init();
+		await PluginConfig.init();
 		await initEvent();
 		if (!this.server.listening) {
 			this.server.listen(this.port);
diff --git a/src/plugins/example-plugin/TestPlugin.ts b/src/plugins/example-plugin/TestPlugin.ts
index 7a86aab2..18e634f7 100644
--- a/src/plugins/example-plugin/TestPlugin.ts
+++ b/src/plugins/example-plugin/TestPlugin.ts
@@ -1,12 +1,26 @@
-import { Plugin } from "@fosscord/util";
+import { setupListener } from "@fosscord/gateway";
+import { Channel, Guild, Plugin, PluginLoadedEventArgs, PluginLoader, PluginManifest, PreMessageEventArgs, PreMessageEventResult } from "@fosscord/util";
+import { TestSettings } from "./TestSettings";
 
 export default class TestPlugin implements Plugin {
-    pluginPath: string;
-    async initConfig(): Promise<void> {
-        
-    }
-    onPluginLoaded() {
+    pluginManifest?: PluginManifest | undefined;
+    settings: TestSettings = new TestSettings();
+    async onPluginLoaded(env: PluginLoadedEventArgs) {
         console.log("Test plugin active!");
+        if(this.pluginManifest) this.settings = PluginLoader.getPluginConfig(this.pluginManifest.id, this.settings) as TestSettings;
+    }
+    async onPreMessage(data: PreMessageEventArgs): Promise<PreMessageEventResult> {
+        let channel = await Channel.findOne({ where: { id: data.message.channel_id } });
+        let guild = await Guild.findOne({ where: { id: data.message.guild_id } });
+        let block = data.message.content?.includes('UwU');
+        
+        let result = {cancel: block} as PreMessageEventResult;
+
+        if(block) {
+            console.log(`[TestPlugin] Blocked message in ${guild?.name}/#${channel?.name} by ${data.message.author?.username}: ${data.message.content}`);
+            result.blockReason = "[TestPlugin] Your message contains UwU! Get bamboozled!";
+        }
+
+        return result;
     }
-    
 }
\ No newline at end of file
diff --git a/src/plugins/example-plugin/TestSettings.ts b/src/plugins/example-plugin/TestSettings.ts
new file mode 100644
index 00000000..d8c52187
--- /dev/null
+++ b/src/plugins/example-plugin/TestSettings.ts
@@ -0,0 +1,11 @@
+export class TestSettings {
+    someInt: number = 10;
+    someStr: string = "asdf";
+    someBool: boolean = true;
+    someDate: Date = new Date();
+    subSettings: SubSettings = new SubSettings();
+}
+
+export class SubSettings {
+    someStr: string = "jklm";
+}
\ No newline at end of file
diff --git a/src/start.ts b/src/start.ts
index cf1a42a5..9d01ce9c 100644
--- a/src/start.ts
+++ b/src/start.ts
@@ -7,6 +7,8 @@ import { bold, cyan, red, yellow } from "picocolors";
 import "reflect-metadata";
 import { initStats } from "./stats";
 config();
+import { execSync } from "child_process";
+import { Logo } from "util/util/Logo";
 
 // TODO: add socket event transmission
 let cores = 1;
@@ -25,17 +27,15 @@ if (cluster.isMaster) {
 		}
 	}
 	const commit = getCommitOrFail();
-
-	console.log(
-		bold(`
-███████╗ ██████╗ ███████╗███████╗ ██████╗ ██████╗ ██████╗ ██████╗
-██╔════╝██╔═══██╗██╔════╝██╔════╝██╔════╝██╔═══██╗██╔══██╗██╔══██╗
-█████╗  ██║   ██║███████╗███████╗██║     ██║   ██║██████╔╝██║  ██║
-██╔══╝  ██║   ██║╚════██║╚════██║██║     ██║   ██║██╔══██╗██║  ██║
-██║     ╚██████╔╝███████║███████║╚██████╗╚██████╔╝██║  ██║██████╔╝
-╚═╝      ╚═════╝ ╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝  ╚═╝╚═════╝
-
-		fosscord-server | ${yellow(`Pre-release (${commit !== null ? commit.slice(0, 7) : "Unknown (Git cannot be found)"})`)}
+Logo.printLogo();
+console.log(bold(`
+		fosscord-server | ${yellow(
+			`Pre-release (${
+				commit !== null
+					? commit.slice(0, 7)
+					: "Unknown (Git cannot be found)"
+			})`
+		)}
 
 Commit Hash: ${commit !== null ? `${cyan(commit)} (${yellow(commit.slice(0, 7))})` : "Unknown (Git cannot be found)"}
 Cores: ${cyan(os.cpus().length)} (Using ${cores} thread(s).)
diff --git a/src/util/entities/PluginConfig.ts b/src/util/entities/PluginConfig.ts
index a1364912..87de5167 100644
--- a/src/util/entities/PluginConfig.ts
+++ b/src/util/entities/PluginConfig.ts
@@ -7,5 +7,5 @@ export class PluginConfigEntity extends BaseClassWithoutId {
 	key: string;
 
 	@Column({ type: "simple-json", nullable: true })
-	value: number | boolean | null | string | undefined;
+	value: number | boolean | null | string | Date | undefined;
 }
\ No newline at end of file
diff --git a/src/util/entities/User.ts b/src/util/entities/User.ts
index 1237b676..57afb132 100644
--- a/src/util/entities/User.ts
+++ b/src/util/entities/User.ts
@@ -233,6 +233,26 @@ export class User extends BaseClass {
 		}
 	}
 
+	/**
+	 *
+	 *
+	 * @static
+	 * @param {{
+	 * 		username: string;
+	 * 		password?: string;
+	 * 		email?: string;
+	 * 		date_of_birth?: Date; // "2000-04-03"
+	 * 		req?: any;
+	 * 	}} {
+	 * 		email,
+	 * 		username,
+	 * 		password,
+	 * 		date_of_birth,
+	 * 		req,
+	 * 	}
+	 * @return {*} 
+	 * @memberof User
+	 */
 	static async register({
 		email,
 		username,
diff --git a/src/util/entities/index.ts b/src/util/entities/index.ts
index 2b91c2ba..444a7e49 100644
--- a/src/util/entities/index.ts
+++ b/src/util/entities/index.ts
@@ -31,3 +31,8 @@ export * from "./User";
 export * from "./UserSettings";
 export * from "./VoiceState";
 export * from "./Webhook";
+export * from "./ClientRelease";
+export * from "./BackupCodes";
+export * from "./Note";
+export * from "./UserSettings";
+export * from "./PluginConfig";
diff --git a/src/util/migrations/mariadb/1660404644371-PluginConfigs.ts b/src/util/migrations/mariadb/1660404644371-PluginConfigs.ts
new file mode 100644
index 00000000..73953ca8
--- /dev/null
+++ b/src/util/migrations/mariadb/1660404644371-PluginConfigs.ts
@@ -0,0 +1,42 @@
+import { MigrationInterface, QueryRunner } from "typeorm";
+
+export class PluginConfigs1660404644371 implements MigrationInterface {
+    name = 'PluginConfigs1660404644371'
+
+    public async up(queryRunner: QueryRunner): Promise<void> {
+        await queryRunner.query(`
+            DROP INDEX \`IDX_76ba283779c8441fd5ff819c8c\` ON \`users\`
+        `);
+        await queryRunner.query(`
+            CREATE TABLE \`plugin_config\` (
+                \`key\` varchar(255) NOT NULL,
+                \`value\` text NULL,
+                PRIMARY KEY (\`key\`)
+            ) ENGINE = InnoDB
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`channels\`
+            ADD \`flags\` int NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`channels\`
+            ADD \`default_thread_rate_limit_per_user\` int NULL
+        `);
+    }
+
+    public async down(queryRunner: QueryRunner): Promise<void> {
+        await queryRunner.query(`
+            ALTER TABLE \`channels\` DROP COLUMN \`default_thread_rate_limit_per_user\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`channels\` DROP COLUMN \`flags\`
+        `);
+        await queryRunner.query(`
+            DROP TABLE \`plugin_config\`
+        `);
+        await queryRunner.query(`
+            CREATE UNIQUE INDEX \`IDX_76ba283779c8441fd5ff819c8c\` ON \`users\` (\`settingsId\`)
+        `);
+    }
+
+}
diff --git a/src/util/migrations/mariadb/1660534571948-UpdateMigrations.ts b/src/util/migrations/mariadb/1660534571948-UpdateMigrations.ts
new file mode 100644
index 00000000..d799fe80
--- /dev/null
+++ b/src/util/migrations/mariadb/1660534571948-UpdateMigrations.ts
@@ -0,0 +1,229 @@
+import { MigrationInterface, QueryRunner } from "typeorm";
+
+export class UpdateMigrations1660534571948 implements MigrationInterface {
+    name = 'UpdateMigrations1660534571948'
+
+    public async up(queryRunner: QueryRunner): Promise<void> {
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP FOREIGN KEY \`FK_e5bf78cdbbe9ba91062d74c5aba\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`invites\` DROP FOREIGN KEY \`FK_15c35422032e0b22b4ada95f48f\`
+        `);
+        await queryRunner.query(`
+            DROP INDEX \`IDX_76ba283779c8441fd5ff819c8c\` ON \`users\`
+        `);
+        await queryRunner.query(`
+            CREATE TABLE \`plugin_config\` (
+                \`key\` varchar(255) NOT NULL,
+                \`value\` text NULL,
+                PRIMARY KEY (\`key\`)
+            ) ENGINE = InnoDB
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`rpc_origins\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`primary_sku_id\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`slug\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`guild_id\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`type\` text NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`hook\` tinyint NOT NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`redirect_uris\` text NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`rpc_application_state\` int NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`store_application_state\` int NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`verification_state\` int NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`interactions_endpoint_url\` varchar(255) NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`integration_public\` tinyint NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`integration_require_code_grant\` tinyint NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`discoverability_state\` int NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`discovery_eligibility_flags\` int NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`tags\` text NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`install_params\` text NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`bot_user_id\` varchar(255) NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD UNIQUE INDEX \`IDX_2ce5a55796fe4c2f77ece57a64\` (\`bot_user_id\`)
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`channels\`
+            ADD \`flags\` int NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`channels\`
+            ADD \`default_thread_rate_limit_per_user\` int NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` CHANGE \`description\` \`description\` varchar(255) NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`flags\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`flags\` int NOT NULL
+        `);
+        await queryRunner.query(`
+            CREATE UNIQUE INDEX \`REL_2ce5a55796fe4c2f77ece57a64\` ON \`applications\` (\`bot_user_id\`)
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD CONSTRAINT \`FK_2ce5a55796fe4c2f77ece57a647\` FOREIGN KEY (\`bot_user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`invites\`
+            ADD CONSTRAINT \`FK_15c35422032e0b22b4ada95f48f\` FOREIGN KEY (\`inviter_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION
+        `);
+    }
+
+    public async down(queryRunner: QueryRunner): Promise<void> {
+        await queryRunner.query(`
+            ALTER TABLE \`invites\` DROP FOREIGN KEY \`FK_15c35422032e0b22b4ada95f48f\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP FOREIGN KEY \`FK_2ce5a55796fe4c2f77ece57a647\`
+        `);
+        await queryRunner.query(`
+            DROP INDEX \`REL_2ce5a55796fe4c2f77ece57a64\` ON \`applications\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`flags\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`flags\` varchar(255) NOT NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` CHANGE \`description\` \`description\` varchar(255) NOT NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`channels\` DROP COLUMN \`default_thread_rate_limit_per_user\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`channels\` DROP COLUMN \`flags\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP INDEX \`IDX_2ce5a55796fe4c2f77ece57a64\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`bot_user_id\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`install_params\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`tags\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`discovery_eligibility_flags\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`discoverability_state\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`integration_require_code_grant\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`integration_public\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`interactions_endpoint_url\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`verification_state\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`store_application_state\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`rpc_application_state\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`redirect_uris\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`hook\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\` DROP COLUMN \`type\`
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`guild_id\` varchar(255) NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`slug\` varchar(255) NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`primary_sku_id\` varchar(255) NULL
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD \`rpc_origins\` text NULL
+        `);
+        await queryRunner.query(`
+            DROP TABLE \`plugin_config\`
+        `);
+        await queryRunner.query(`
+            CREATE UNIQUE INDEX \`IDX_76ba283779c8441fd5ff819c8c\` ON \`users\` (\`settingsId\`)
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`invites\`
+            ADD CONSTRAINT \`FK_15c35422032e0b22b4ada95f48f\` FOREIGN KEY (\`inviter_id\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION
+        `);
+        await queryRunner.query(`
+            ALTER TABLE \`applications\`
+            ADD CONSTRAINT \`FK_e5bf78cdbbe9ba91062d74c5aba\` FOREIGN KEY (\`guild_id\`) REFERENCES \`guilds\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION
+        `);
+    }
+
+}
diff --git a/src/util/migrations/postgres/1660404619978-PluginConfigs.ts b/src/util/migrations/postgres/1660404619978-PluginConfigs.ts
new file mode 100644
index 00000000..d8a5ab4d
--- /dev/null
+++ b/src/util/migrations/postgres/1660404619978-PluginConfigs.ts
@@ -0,0 +1,22 @@
+import { MigrationInterface, QueryRunner } from "typeorm";
+
+export class PluginConfigs1660404619978 implements MigrationInterface {
+    name = 'PluginConfigs1660404619978'
+
+    public async up(queryRunner: QueryRunner): Promise<void> {
+        await queryRunner.query(`
+            CREATE TABLE "plugin_config" (
+                "key" character varying NOT NULL,
+                "value" text,
+                CONSTRAINT "PK_aa929ece56c59233b85a16f62ef" PRIMARY KEY ("key")
+            )
+        `);
+    }
+
+    public async down(queryRunner: QueryRunner): Promise<void> {
+        await queryRunner.query(`
+            DROP TABLE "plugin_config"
+        `);
+    }
+
+}
diff --git a/src/util/migrations/sqlite/1660534525799-UpdateMigrations.ts b/src/util/migrations/sqlite/1660534525799-UpdateMigrations.ts
new file mode 100644
index 00000000..54a59fab
--- /dev/null
+++ b/src/util/migrations/sqlite/1660534525799-UpdateMigrations.ts
@@ -0,0 +1,829 @@
+import { MigrationInterface, QueryRunner } from "typeorm";
+
+export class UpdateMigrations1660534525799 implements MigrationInterface {
+    name = 'UpdateMigrations1660534525799'
+
+    public async up(queryRunner: QueryRunner): Promise<void> {
+        await queryRunner.query(`
+            CREATE TABLE "temporary_applications" (
+                "id" varchar PRIMARY KEY NOT NULL,
+                "name" varchar NOT NULL,
+                "icon" varchar,
+                "description" varchar NOT NULL,
+                "rpc_origins" text,
+                "bot_public" boolean NOT NULL,
+                "bot_require_code_grant" boolean NOT NULL,
+                "terms_of_service_url" varchar,
+                "privacy_policy_url" varchar,
+                "summary" varchar,
+                "verify_key" varchar NOT NULL,
+                "primary_sku_id" varchar,
+                "slug" varchar,
+                "cover_image" varchar,
+                "flags" varchar NOT NULL,
+                "owner_id" varchar,
+                "team_id" varchar,
+                "guild_id" varchar,
+                CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION,
+                CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
+            )
+        `);
+        await queryRunner.query(`
+            INSERT INTO "temporary_applications"(
+                    "id",
+                    "name",
+                    "icon",
+                    "description",
+                    "rpc_origins",
+                    "bot_public",
+                    "bot_require_code_grant",
+                    "terms_of_service_url",
+                    "privacy_policy_url",
+                    "summary",
+                    "verify_key",
+                    "primary_sku_id",
+                    "slug",
+                    "cover_image",
+                    "flags",
+                    "owner_id",
+                    "team_id",
+                    "guild_id"
+                )
+            SELECT "id",
+                "name",
+                "icon",
+                "description",
+                "rpc_origins",
+                "bot_public",
+                "bot_require_code_grant",
+                "terms_of_service_url",
+                "privacy_policy_url",
+                "summary",
+                "verify_key",
+                "primary_sku_id",
+                "slug",
+                "cover_image",
+                "flags",
+                "owner_id",
+                "team_id",
+                "guild_id"
+            FROM "applications"
+        `);
+        await queryRunner.query(`
+            DROP TABLE "applications"
+        `);
+        await queryRunner.query(`
+            ALTER TABLE "temporary_applications"
+                RENAME TO "applications"
+        `);
+        await queryRunner.query(`
+            CREATE TABLE "temporary_applications" (
+                "id" varchar PRIMARY KEY NOT NULL,
+                "name" varchar NOT NULL,
+                "icon" varchar,
+                "description" varchar NOT NULL,
+                "bot_public" boolean NOT NULL,
+                "bot_require_code_grant" boolean NOT NULL,
+                "terms_of_service_url" varchar,
+                "privacy_policy_url" varchar,
+                "summary" varchar,
+                "verify_key" varchar NOT NULL,
+                "cover_image" varchar,
+                "flags" varchar NOT NULL,
+                "owner_id" varchar,
+                "team_id" varchar,
+                CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION,
+                CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
+            )
+        `);
+        await queryRunner.query(`
+            INSERT INTO "temporary_applications"(
+                    "id",
+                    "name",
+                    "icon",
+                    "description",
+                    "bot_public",
+                    "bot_require_code_grant",
+                    "terms_of_service_url",
+                    "privacy_policy_url",
+                    "summary",
+                    "verify_key",
+                    "cover_image",
+                    "flags",
+                    "owner_id",
+                    "team_id"
+                )
+            SELECT "id",
+                "name",
+                "icon",
+                "description",
+                "bot_public",
+                "bot_require_code_grant",
+                "terms_of_service_url",
+                "privacy_policy_url",
+                "summary",
+                "verify_key",
+                "cover_image",
+                "flags",
+                "owner_id",
+                "team_id"
+            FROM "applications"
+        `);
+        await queryRunner.query(`
+            DROP TABLE "applications"
+        `);
+        await queryRunner.query(`
+            ALTER TABLE "temporary_applications"
+                RENAME TO "applications"
+        `);
+        await queryRunner.query(`
+            CREATE TABLE "temporary_applications" (
+                "id" varchar PRIMARY KEY NOT NULL,
+                "name" varchar NOT NULL,
+                "icon" varchar,
+                "description" varchar NOT NULL,
+                "bot_public" boolean NOT NULL,
+                "bot_require_code_grant" boolean NOT NULL,
+                "terms_of_service_url" varchar,
+                "privacy_policy_url" varchar,
+                "summary" varchar,
+                "verify_key" varchar NOT NULL,
+                "cover_image" varchar,
+                "flags" varchar NOT NULL,
+                "owner_id" varchar,
+                "team_id" varchar,
+                "type" text,
+                "hook" boolean NOT NULL,
+                "redirect_uris" text,
+                "rpc_application_state" integer,
+                "store_application_state" integer,
+                "verification_state" integer,
+                "interactions_endpoint_url" varchar,
+                "integration_public" boolean,
+                "integration_require_code_grant" boolean,
+                "discoverability_state" integer,
+                "discovery_eligibility_flags" integer,
+                "tags" text,
+                "install_params" text,
+                "bot_user_id" varchar,
+                CONSTRAINT "UQ_b7f6e13565e920916d902e1f431" UNIQUE ("bot_user_id"),
+                CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION,
+                CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
+            )
+        `);
+        await queryRunner.query(`
+            INSERT INTO "temporary_applications"(
+                    "id",
+                    "name",
+                    "icon",
+                    "description",
+                    "bot_public",
+                    "bot_require_code_grant",
+                    "terms_of_service_url",
+                    "privacy_policy_url",
+                    "summary",
+                    "verify_key",
+                    "cover_image",
+                    "flags",
+                    "owner_id",
+                    "team_id"
+                )
+            SELECT "id",
+                "name",
+                "icon",
+                "description",
+                "bot_public",
+                "bot_require_code_grant",
+                "terms_of_service_url",
+                "privacy_policy_url",
+                "summary",
+                "verify_key",
+                "cover_image",
+                "flags",
+                "owner_id",
+                "team_id"
+            FROM "applications"
+        `);
+        await queryRunner.query(`
+            DROP TABLE "applications"
+        `);
+        await queryRunner.query(`
+            ALTER TABLE "temporary_applications"
+                RENAME TO "applications"
+        `);
+        await queryRunner.query(`
+            CREATE TABLE "temporary_applications" (
+                "id" varchar PRIMARY KEY NOT NULL,
+                "name" varchar NOT NULL,
+                "icon" varchar,
+                "description" varchar,
+                "bot_public" boolean NOT NULL,
+                "bot_require_code_grant" boolean NOT NULL,
+                "terms_of_service_url" varchar,
+                "privacy_policy_url" varchar,
+                "summary" varchar,
+                "verify_key" varchar NOT NULL,
+                "cover_image" varchar,
+                "flags" integer NOT NULL,
+                "owner_id" varchar,
+                "team_id" varchar,
+                "type" text,
+                "hook" boolean NOT NULL,
+                "redirect_uris" text,
+                "rpc_application_state" integer,
+                "store_application_state" integer,
+                "verification_state" integer,
+                "interactions_endpoint_url" varchar,
+                "integration_public" boolean,
+                "integration_require_code_grant" boolean,
+                "discoverability_state" integer,
+                "discovery_eligibility_flags" integer,
+                "tags" text,
+                "install_params" text,
+                "bot_user_id" varchar,
+                CONSTRAINT "UQ_b7f6e13565e920916d902e1f431" UNIQUE ("bot_user_id"),
+                CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION,
+                CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
+            )
+        `);
+        await queryRunner.query(`
+            INSERT INTO "temporary_applications"(
+                    "id",
+                    "name",
+                    "icon",
+                    "description",
+                    "bot_public",
+                    "bot_require_code_grant",
+                    "terms_of_service_url",
+                    "privacy_policy_url",
+                    "summary",
+                    "verify_key",
+                    "cover_image",
+                    "flags",
+                    "owner_id",
+                    "team_id",
+                    "type",
+                    "hook",
+                    "redirect_uris",
+                    "rpc_application_state",
+                    "store_application_state",
+                    "verification_state",
+                    "interactions_endpoint_url",
+                    "integration_public",
+                    "integration_require_code_grant",
+                    "discoverability_state",
+                    "discovery_eligibility_flags",
+                    "tags",
+                    "install_params",
+                    "bot_user_id"
+                )
+            SELECT "id",
+                "name",
+                "icon",
+                "description",
+                "bot_public",
+                "bot_require_code_grant",
+                "terms_of_service_url",
+                "privacy_policy_url",
+                "summary",
+                "verify_key",
+                "cover_image",
+                "flags",
+                "owner_id",
+                "team_id",
+                "type",
+                "hook",
+                "redirect_uris",
+                "rpc_application_state",
+                "store_application_state",
+                "verification_state",
+                "interactions_endpoint_url",
+                "integration_public",
+                "integration_require_code_grant",
+                "discoverability_state",
+                "discovery_eligibility_flags",
+                "tags",
+                "install_params",
+                "bot_user_id"
+            FROM "applications"
+        `);
+        await queryRunner.query(`
+            DROP TABLE "applications"
+        `);
+        await queryRunner.query(`
+            ALTER TABLE "temporary_applications"
+                RENAME TO "applications"
+        `);
+        await queryRunner.query(`
+            CREATE TABLE "temporary_applications" (
+                "id" varchar PRIMARY KEY NOT NULL,
+                "name" varchar NOT NULL,
+                "icon" varchar,
+                "description" varchar,
+                "bot_public" boolean NOT NULL,
+                "bot_require_code_grant" boolean NOT NULL,
+                "terms_of_service_url" varchar,
+                "privacy_policy_url" varchar,
+                "summary" varchar,
+                "verify_key" varchar NOT NULL,
+                "cover_image" varchar,
+                "flags" integer NOT NULL,
+                "owner_id" varchar,
+                "team_id" varchar,
+                "type" text,
+                "hook" boolean NOT NULL,
+                "redirect_uris" text,
+                "rpc_application_state" integer,
+                "store_application_state" integer,
+                "verification_state" integer,
+                "interactions_endpoint_url" varchar,
+                "integration_public" boolean,
+                "integration_require_code_grant" boolean,
+                "discoverability_state" integer,
+                "discovery_eligibility_flags" integer,
+                "tags" text,
+                "install_params" text,
+                "bot_user_id" varchar,
+                CONSTRAINT "UQ_b7f6e13565e920916d902e1f431" UNIQUE ("bot_user_id"),
+                CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION,
+                CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
+                CONSTRAINT "FK_2ce5a55796fe4c2f77ece57a647" FOREIGN KEY ("bot_user_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
+            )
+        `);
+        await queryRunner.query(`
+            INSERT INTO "temporary_applications"(
+                    "id",
+                    "name",
+                    "icon",
+                    "description",
+                    "bot_public",
+                    "bot_require_code_grant",
+                    "terms_of_service_url",
+                    "privacy_policy_url",
+                    "summary",
+                    "verify_key",
+                    "cover_image",
+                    "flags",
+                    "owner_id",
+                    "team_id",
+                    "type",
+                    "hook",
+                    "redirect_uris",
+                    "rpc_application_state",
+                    "store_application_state",
+                    "verification_state",
+                    "interactions_endpoint_url",
+                    "integration_public",
+                    "integration_require_code_grant",
+                    "discoverability_state",
+                    "discovery_eligibility_flags",
+                    "tags",
+                    "install_params",
+                    "bot_user_id"
+                )
+            SELECT "id",
+                "name",
+                "icon",
+                "description",
+                "bot_public",
+                "bot_require_code_grant",
+                "terms_of_service_url",
+                "privacy_policy_url",
+                "summary",
+                "verify_key",
+                "cover_image",
+                "flags",
+                "owner_id",
+                "team_id",
+                "type",
+                "hook",
+                "redirect_uris",
+                "rpc_application_state",
+                "store_application_state",
+                "verification_state",
+                "interactions_endpoint_url",
+                "integration_public",
+                "integration_require_code_grant",
+                "discoverability_state",
+                "discovery_eligibility_flags",
+                "tags",
+                "install_params",
+                "bot_user_id"
+            FROM "applications"
+        `);
+        await queryRunner.query(`
+            DROP TABLE "applications"
+        `);
+        await queryRunner.query(`
+            ALTER TABLE "temporary_applications"
+                RENAME TO "applications"
+        `);
+    }
+
+    public async down(queryRunner: QueryRunner): Promise<void> {
+        await queryRunner.query(`
+            ALTER TABLE "applications"
+                RENAME TO "temporary_applications"
+        `);
+        await queryRunner.query(`
+            CREATE TABLE "applications" (
+                "id" varchar PRIMARY KEY NOT NULL,
+                "name" varchar NOT NULL,
+                "icon" varchar,
+                "description" varchar,
+                "bot_public" boolean NOT NULL,
+                "bot_require_code_grant" boolean NOT NULL,
+                "terms_of_service_url" varchar,
+                "privacy_policy_url" varchar,
+                "summary" varchar,
+                "verify_key" varchar NOT NULL,
+                "cover_image" varchar,
+                "flags" integer NOT NULL,
+                "owner_id" varchar,
+                "team_id" varchar,
+                "type" text,
+                "hook" boolean NOT NULL,
+                "redirect_uris" text,
+                "rpc_application_state" integer,
+                "store_application_state" integer,
+                "verification_state" integer,
+                "interactions_endpoint_url" varchar,
+                "integration_public" boolean,
+                "integration_require_code_grant" boolean,
+                "discoverability_state" integer,
+                "discovery_eligibility_flags" integer,
+                "tags" text,
+                "install_params" text,
+                "bot_user_id" varchar,
+                CONSTRAINT "UQ_b7f6e13565e920916d902e1f431" UNIQUE ("bot_user_id"),
+                CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION,
+                CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
+            )
+        `);
+        await queryRunner.query(`
+            INSERT INTO "applications"(
+                    "id",
+                    "name",
+                    "icon",
+                    "description",
+                    "bot_public",
+                    "bot_require_code_grant",
+                    "terms_of_service_url",
+                    "privacy_policy_url",
+                    "summary",
+                    "verify_key",
+                    "cover_image",
+                    "flags",
+                    "owner_id",
+                    "team_id",
+                    "type",
+                    "hook",
+                    "redirect_uris",
+                    "rpc_application_state",
+                    "store_application_state",
+                    "verification_state",
+                    "interactions_endpoint_url",
+                    "integration_public",
+                    "integration_require_code_grant",
+                    "discoverability_state",
+                    "discovery_eligibility_flags",
+                    "tags",
+                    "install_params",
+                    "bot_user_id"
+                )
+            SELECT "id",
+                "name",
+                "icon",
+                "description",
+                "bot_public",
+                "bot_require_code_grant",
+                "terms_of_service_url",
+                "privacy_policy_url",
+                "summary",
+                "verify_key",
+                "cover_image",
+                "flags",
+                "owner_id",
+                "team_id",
+                "type",
+                "hook",
+                "redirect_uris",
+                "rpc_application_state",
+                "store_application_state",
+                "verification_state",
+                "interactions_endpoint_url",
+                "integration_public",
+                "integration_require_code_grant",
+                "discoverability_state",
+                "discovery_eligibility_flags",
+                "tags",
+                "install_params",
+                "bot_user_id"
+            FROM "temporary_applications"
+        `);
+        await queryRunner.query(`
+            DROP TABLE "temporary_applications"
+        `);
+        await queryRunner.query(`
+            ALTER TABLE "applications"
+                RENAME TO "temporary_applications"
+        `);
+        await queryRunner.query(`
+            CREATE TABLE "applications" (
+                "id" varchar PRIMARY KEY NOT NULL,
+                "name" varchar NOT NULL,
+                "icon" varchar,
+                "description" varchar NOT NULL,
+                "bot_public" boolean NOT NULL,
+                "bot_require_code_grant" boolean NOT NULL,
+                "terms_of_service_url" varchar,
+                "privacy_policy_url" varchar,
+                "summary" varchar,
+                "verify_key" varchar NOT NULL,
+                "cover_image" varchar,
+                "flags" varchar NOT NULL,
+                "owner_id" varchar,
+                "team_id" varchar,
+                "type" text,
+                "hook" boolean NOT NULL,
+                "redirect_uris" text,
+                "rpc_application_state" integer,
+                "store_application_state" integer,
+                "verification_state" integer,
+                "interactions_endpoint_url" varchar,
+                "integration_public" boolean,
+                "integration_require_code_grant" boolean,
+                "discoverability_state" integer,
+                "discovery_eligibility_flags" integer,
+                "tags" text,
+                "install_params" text,
+                "bot_user_id" varchar,
+                CONSTRAINT "UQ_b7f6e13565e920916d902e1f431" UNIQUE ("bot_user_id"),
+                CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION,
+                CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
+            )
+        `);
+        await queryRunner.query(`
+            INSERT INTO "applications"(
+                    "id",
+                    "name",
+                    "icon",
+                    "description",
+                    "bot_public",
+                    "bot_require_code_grant",
+                    "terms_of_service_url",
+                    "privacy_policy_url",
+                    "summary",
+                    "verify_key",
+                    "cover_image",
+                    "flags",
+                    "owner_id",
+                    "team_id",
+                    "type",
+                    "hook",
+                    "redirect_uris",
+                    "rpc_application_state",
+                    "store_application_state",
+                    "verification_state",
+                    "interactions_endpoint_url",
+                    "integration_public",
+                    "integration_require_code_grant",
+                    "discoverability_state",
+                    "discovery_eligibility_flags",
+                    "tags",
+                    "install_params",
+                    "bot_user_id"
+                )
+            SELECT "id",
+                "name",
+                "icon",
+                "description",
+                "bot_public",
+                "bot_require_code_grant",
+                "terms_of_service_url",
+                "privacy_policy_url",
+                "summary",
+                "verify_key",
+                "cover_image",
+                "flags",
+                "owner_id",
+                "team_id",
+                "type",
+                "hook",
+                "redirect_uris",
+                "rpc_application_state",
+                "store_application_state",
+                "verification_state",
+                "interactions_endpoint_url",
+                "integration_public",
+                "integration_require_code_grant",
+                "discoverability_state",
+                "discovery_eligibility_flags",
+                "tags",
+                "install_params",
+                "bot_user_id"
+            FROM "temporary_applications"
+        `);
+        await queryRunner.query(`
+            DROP TABLE "temporary_applications"
+        `);
+        await queryRunner.query(`
+            ALTER TABLE "applications"
+                RENAME TO "temporary_applications"
+        `);
+        await queryRunner.query(`
+            CREATE TABLE "applications" (
+                "id" varchar PRIMARY KEY NOT NULL,
+                "name" varchar NOT NULL,
+                "icon" varchar,
+                "description" varchar NOT NULL,
+                "bot_public" boolean NOT NULL,
+                "bot_require_code_grant" boolean NOT NULL,
+                "terms_of_service_url" varchar,
+                "privacy_policy_url" varchar,
+                "summary" varchar,
+                "verify_key" varchar NOT NULL,
+                "cover_image" varchar,
+                "flags" varchar NOT NULL,
+                "owner_id" varchar,
+                "team_id" varchar,
+                CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION,
+                CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
+            )
+        `);
+        await queryRunner.query(`
+            INSERT INTO "applications"(
+                    "id",
+                    "name",
+                    "icon",
+                    "description",
+                    "bot_public",
+                    "bot_require_code_grant",
+                    "terms_of_service_url",
+                    "privacy_policy_url",
+                    "summary",
+                    "verify_key",
+                    "cover_image",
+                    "flags",
+                    "owner_id",
+                    "team_id"
+                )
+            SELECT "id",
+                "name",
+                "icon",
+                "description",
+                "bot_public",
+                "bot_require_code_grant",
+                "terms_of_service_url",
+                "privacy_policy_url",
+                "summary",
+                "verify_key",
+                "cover_image",
+                "flags",
+                "owner_id",
+                "team_id"
+            FROM "temporary_applications"
+        `);
+        await queryRunner.query(`
+            DROP TABLE "temporary_applications"
+        `);
+        await queryRunner.query(`
+            ALTER TABLE "applications"
+                RENAME TO "temporary_applications"
+        `);
+        await queryRunner.query(`
+            CREATE TABLE "applications" (
+                "id" varchar PRIMARY KEY NOT NULL,
+                "name" varchar NOT NULL,
+                "icon" varchar,
+                "description" varchar NOT NULL,
+                "rpc_origins" text,
+                "bot_public" boolean NOT NULL,
+                "bot_require_code_grant" boolean NOT NULL,
+                "terms_of_service_url" varchar,
+                "privacy_policy_url" varchar,
+                "summary" varchar,
+                "verify_key" varchar NOT NULL,
+                "primary_sku_id" varchar,
+                "slug" varchar,
+                "cover_image" varchar,
+                "flags" varchar NOT NULL,
+                "owner_id" varchar,
+                "team_id" varchar,
+                "guild_id" varchar,
+                CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION,
+                CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
+            )
+        `);
+        await queryRunner.query(`
+            INSERT INTO "applications"(
+                    "id",
+                    "name",
+                    "icon",
+                    "description",
+                    "bot_public",
+                    "bot_require_code_grant",
+                    "terms_of_service_url",
+                    "privacy_policy_url",
+                    "summary",
+                    "verify_key",
+                    "cover_image",
+                    "flags",
+                    "owner_id",
+                    "team_id"
+                )
+            SELECT "id",
+                "name",
+                "icon",
+                "description",
+                "bot_public",
+                "bot_require_code_grant",
+                "terms_of_service_url",
+                "privacy_policy_url",
+                "summary",
+                "verify_key",
+                "cover_image",
+                "flags",
+                "owner_id",
+                "team_id"
+            FROM "temporary_applications"
+        `);
+        await queryRunner.query(`
+            DROP TABLE "temporary_applications"
+        `);
+        await queryRunner.query(`
+            ALTER TABLE "applications"
+                RENAME TO "temporary_applications"
+        `);
+        await queryRunner.query(`
+            CREATE TABLE "applications" (
+                "id" varchar PRIMARY KEY NOT NULL,
+                "name" varchar NOT NULL,
+                "icon" varchar,
+                "description" varchar NOT NULL,
+                "rpc_origins" text,
+                "bot_public" boolean NOT NULL,
+                "bot_require_code_grant" boolean NOT NULL,
+                "terms_of_service_url" varchar,
+                "privacy_policy_url" varchar,
+                "summary" varchar,
+                "verify_key" varchar NOT NULL,
+                "primary_sku_id" varchar,
+                "slug" varchar,
+                "cover_image" varchar,
+                "flags" varchar NOT NULL,
+                "owner_id" varchar,
+                "team_id" varchar,
+                "guild_id" varchar,
+                CONSTRAINT "FK_e5bf78cdbbe9ba91062d74c5aba" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
+                CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION,
+                CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
+            )
+        `);
+        await queryRunner.query(`
+            INSERT INTO "applications"(
+                    "id",
+                    "name",
+                    "icon",
+                    "description",
+                    "rpc_origins",
+                    "bot_public",
+                    "bot_require_code_grant",
+                    "terms_of_service_url",
+                    "privacy_policy_url",
+                    "summary",
+                    "verify_key",
+                    "primary_sku_id",
+                    "slug",
+                    "cover_image",
+                    "flags",
+                    "owner_id",
+                    "team_id",
+                    "guild_id"
+                )
+            SELECT "id",
+                "name",
+                "icon",
+                "description",
+                "rpc_origins",
+                "bot_public",
+                "bot_require_code_grant",
+                "terms_of_service_url",
+                "privacy_policy_url",
+                "summary",
+                "verify_key",
+                "primary_sku_id",
+                "slug",
+                "cover_image",
+                "flags",
+                "owner_id",
+                "team_id",
+                "guild_id"
+            FROM "temporary_applications"
+        `);
+        await queryRunner.query(`
+            DROP TABLE "temporary_applications"
+        `);
+    }
+
+}
diff --git a/src/util/plugin/Plugin.ts b/src/util/plugin/Plugin.ts
index 0fb1732f..6a6f03f4 100644
--- a/src/util/plugin/Plugin.ts
+++ b/src/util/plugin/Plugin.ts
@@ -1,13 +1,22 @@
 import EventEmitter from "events";
-import { PluginLoadedEventArgs, TypedEventEmitter } from "@fosscord/util";
+import { PluginLoadedEventArgs, PluginManifest, TypedEventEmitter } from "@fosscord/util";
+import { PluginConfig } from "./PluginConfig";
+import { PreRegisterEventArgs, PreRegisterEventResult, OnRegisterEventArgs } from '.';
+import { PreMessageEventArgs, PreMessageEventResult, OnMessageEventArgs } from '.';
+import { PreLoginEventArgs, PreLoginEventResult, OnLoginEventArgs } from '.';
+import { PreGuildCreateEventArgs, PreGuildCreateEventResult, OnGuildCreateEventArgs } from '.';
+import { PreChannelCreateEventArgs, PreChannelCreateEventResult, OnChannelCreateEventArgs } from '.';
+import { PreTypingEventArgs, PreTypingEventResult, OnTypingEventArgs } from '.';
+import { PreStatusChangeEventArgs, PreStatusChangeEventResult, OnStatusChangeEventArgs } from '.';
 
-type PluginEvents = {
+
+/*type PluginEvents = {
 	error: (error: Error | unknown) => void;
 	loaded: () => void;
-};
+};*/
 
 //this doesnt work, check later:
- //EventEmitter as new () => TypedEventEmitter<PluginEvents>
+//EventEmitter as new () => TypedEventEmitter<PluginEvents>
 export class Plugin {
 	/**
 	 * Path the plugin resides in.
@@ -15,23 +24,137 @@ export class Plugin {
 	 * @type {string}
 	 * @memberof Plugin
 	 */
-	pluginPath: string;
-	/**
-	 *
-	 *
-	 * @memberof Plugin
-	 */
-	async initConfig() {
-		// insert default config into database?
-		console.log("did you forget to implement initConfig?");
-	}
+	pluginPath?: string;
+	pluginManifest?: PluginManifest;
 	/**
 	 *	
 	 *
 	 * @param {PluginLoadedEventArgs} args Info about plugin environment
 	 * @memberof Plugin
 	 */
-	onPluginLoaded?(args?: PluginLoadedEventArgs) {
-		
+	async onPluginLoaded?(args?: PluginLoadedEventArgs) {
+
 	}
+
+	//generated
+	/**
+	* RegisterEvent: document me
+	*
+	* @param {OnRegisterEventArgs} args Info about what's going on
+	* @memberof Plugin
+	*/
+	async onRegister?(args: OnRegisterEventArgs): Promise<void>;
+
+	/**
+	* RegisterEvent: Executed before changes are announced
+	* document me.
+	*
+	* @param {PreRegisterEventArgs} args Info about what's going on
+	* @return {PreRegisterEventResult} How event should be handled
+	* @memberof Plugin
+	*/
+	async onPreRegister?(args: PreRegisterEventArgs): Promise<PreRegisterEventResult>;
+	/**
+	* MessageEvent: document me
+	*
+	* @param {OnMessageEventArgs} args Info about what's going on
+	* @memberof Plugin
+	*/
+	async onMessage?(args: OnMessageEventArgs): Promise<void>;
+
+	/**
+	* MessageEvent: Executed before changes are announced
+	* document me.
+	*
+	* @param {PreMessageEventArgs} args Info about what's going on
+	* @return {PreMessageEventResult} How event should be handled
+	* @memberof Plugin
+	*/
+	async onPreMessage?(args: PreMessageEventArgs): Promise<PreMessageEventResult>;
+	/**
+	* LoginEvent: document me
+	*
+	* @param {OnLoginEventArgs} args Info about what's going on
+	* @memberof Plugin
+	*/
+	async onLogin?(args: OnLoginEventArgs): Promise<void>;
+
+	/**
+	* LoginEvent: Executed before changes are announced
+	* document me.
+	*
+	* @param {PreLoginEventArgs} args Info about what's going on
+	* @return {PreLoginEventResult} How event should be handled
+	* @memberof Plugin
+	*/
+	async onPreLogin?(args: PreLoginEventArgs): Promise<PreLoginEventResult>;
+	/**
+	* GuildCreateEvent: document me
+	*
+	* @param {OnGuildCreateEventArgs} args Info about what's going on
+	* @memberof Plugin
+	*/
+	async onGuildCreate?(args: OnGuildCreateEventArgs): Promise<void>;
+
+	/**
+	* GuildCreateEvent: Executed before changes are announced
+	* document me.
+	*
+	* @param {PreGuildCreateEventArgs} args Info about what's going on
+	* @return {PreGuildCreateEventResult} How event should be handled
+	* @memberof Plugin
+	*/
+	async onPreGuildCreate?(args: PreGuildCreateEventArgs): Promise<PreGuildCreateEventResult>;
+	/**
+	* ChannelCreateEvent: document me
+	*
+	* @param {OnChannelCreateEventArgs} args Info about what's going on
+	* @memberof Plugin
+	*/
+	async onChannelCreate?(args: OnChannelCreateEventArgs): Promise<void>;
+
+	/**
+	* ChannelCreateEvent: Executed before changes are announced
+	* document me.
+	*
+	* @param {PreChannelCreateEventArgs} args Info about what's going on
+	* @return {PreChannelCreateEventResult} How event should be handled
+	* @memberof Plugin
+	*/
+	async onPreChannelCreate?(args: PreChannelCreateEventArgs): Promise<PreChannelCreateEventResult>;
+	/**
+	* TypingEvent: document me
+	*
+	* @param {OnTypingEventArgs} args Info about what's going on
+	* @memberof Plugin
+	*/
+	async onTyping?(args: OnTypingEventArgs): Promise<void>;
+
+	/**
+	* TypingEvent: Executed before changes are announced
+	* document me.
+	*
+	* @param {PreTypingEventArgs} args Info about what's going on
+	* @return {PreTypingEventResult} How event should be handled
+	* @memberof Plugin
+	*/
+	async onPreTyping?(args: PreTypingEventArgs): Promise<PreTypingEventResult>;
+	/**
+	* StatusChangeEvent: document me
+	*
+	* @param {OnStatusChangeEventArgs} args Info about what's going on
+	* @memberof Plugin
+	*/
+	async onStatusChange?(args: OnStatusChangeEventArgs): Promise<void>;
+
+	/**
+	* StatusChangeEvent: Executed before changes are announced
+	* document me.
+	*
+	* @param {PreStatusChangeEventArgs} args Info about what's going on
+	* @return {PreStatusChangeEventResult} How event should be handled
+	* @memberof Plugin
+	*/
+	async onPreStatusChange?(args: PreStatusChangeEventArgs): Promise<PreStatusChangeEventResult>;
+
 }
diff --git a/src/util/plugin/PluginConfig.ts b/src/util/plugin/PluginConfig.ts
index c7a7db87..883bca7c 100644
--- a/src/util/plugin/PluginConfig.ts
+++ b/src/util/plugin/PluginConfig.ts
@@ -1,26 +1,26 @@
-import { ConfigEntity } from "../entities/Config";
 import fs from "fs";
 import { OrmUtils, Environment } from "..";
+import { PluginConfigEntity } from "util/entities/PluginConfig";
 
 // TODO: yaml instead of json
 const overridePath = process.env.PLUGIN_CONFIG_PATH ?? "";
 
 let config: any;
-let pairs: ConfigEntity[];
+let pairs: PluginConfigEntity[];
 
 // TODO: use events to inform about config updates
 // Config keys are separated with _
 
-export const Config = {
+export const PluginConfig = {
 	init: async function init() {
 		if (config) return config;
-		console.log('[Config] Loading configuration...')
-		pairs = await ConfigEntity.find();
+		console.log('[PluginConfig] Loading configuration...')
+		pairs = await PluginConfigEntity.find();
 		config = pairsToConfig(pairs);
 		//config = (config || {}).merge(new ConfigValue());
 		//config = OrmUtils.mergeDeep(new ConfigValue(), config)
 
-		if(process.env.CONFIG_PATH)
+		if(process.env.PLUGIN_CONFIG_PATH)
 			try {
 				const overrideConfig = JSON.parse(fs.readFileSync(overridePath, { encoding: "utf8" }));
 				config = overrideConfig.merge(config);
@@ -48,26 +48,32 @@ export const Config = {
 
 function applyConfig(val: any) {
 	async function apply(obj: any, key = ""): Promise<any> {
-		if (typeof obj === "object" && obj !== null)
+		if (typeof obj === "object" && obj !== null && !(obj instanceof Date))
 			return Promise.all(Object.keys(obj).map((k) => apply(obj[k], key ? `${key}_${k}` : k)));
 
 		let pair = pairs.find((x) => x.key === key);
-		if (!pair) pair = new ConfigEntity();
+		if (!pair) pair = new PluginConfigEntity();
 
 		pair.key = key;
 		pair.value = obj;
-		return pair.save();
+		if(!pair.key || pair.key == null) {
+			console.log(`[PluginConfig] WARN: Empty key`)
+			console.log(pair);
+			if(Environment.isDebug) debugger;
+		}
+		else
+			return pair.save();
 	}
-	if(process.env.CONFIG_PATH) {
+	if(process.env.PLUGIN_CONFIG_PATH) {
 		if(Environment.isDebug)
-			console.log(`Writing config: ${process.env.CONFIG_PATH}`)
+			console.log(`Writing config: ${process.env.PLUGIN_CONFIG_PATH}`)
 		fs.writeFileSync(overridePath, JSON.stringify(val, null, 4));
 	}
 
 	return apply(val);
 }
 
-function pairsToConfig(pairs: ConfigEntity[]) {
+function pairsToConfig(pairs: PluginConfigEntity[]) {
 	let value: any = {};
 
 	pairs.forEach((p) => {
diff --git a/src/util/plugin/PluginEventHandler.ts b/src/util/plugin/PluginEventHandler.ts
new file mode 100644
index 00000000..d5fc67f4
--- /dev/null
+++ b/src/util/plugin/PluginEventHandler.ts
@@ -0,0 +1,67 @@
+import { PreRegisterEventArgs, OnRegisterEventArgs, PreRegisterEventResult } from './event_types';
+import { PreMessageEventArgs, OnMessageEventArgs, PreMessageEventResult } from './event_types';
+import { PreLoginEventArgs, OnLoginEventArgs, PreLoginEventResult } from './event_types';
+import { PreGuildCreateEventArgs, OnGuildCreateEventArgs, PreGuildCreateEventResult } from './event_types';
+import { PreChannelCreateEventArgs, OnChannelCreateEventArgs, PreChannelCreateEventResult } from './event_types';
+import { PreTypingEventArgs, OnTypingEventArgs, PreTypingEventResult } from './event_types';
+import { PreStatusChangeEventArgs, OnStatusChangeEventArgs, PreStatusChangeEventResult } from './event_types';
+import { PluginStore } from ".";
+
+export class PluginEventHandler {
+    public static async preRegisterEvent(args: PreRegisterEventArgs): Promise<PreRegisterEventResult[]> {
+        return (await Promise.all(PluginStore.plugins.filter(x=>x.onPreRegister).map(x=>x.onPreRegister && x.onPreRegister(args)))).filter(x=>x) as PreRegisterEventResult[];
+    }
+    
+    public static async onRegisterEvent(args: OnRegisterEventArgs): Promise<void> {
+        await Promise.all(PluginStore.plugins.filter(x=>x.onRegister).map(x=>x.onRegister && x.onRegister(args)));
+    }
+    
+    public static async preMessageEvent(args: PreMessageEventArgs): Promise<PreMessageEventResult[]> {
+        return (await Promise.all(PluginStore.plugins.filter(x=>x.onPreMessage).map(x=>x.onPreMessage && x.onPreMessage(args)))).filter(x=>x) as PreMessageEventResult[];
+    }
+    
+    public static async onMessageEvent(args: OnMessageEventArgs): Promise<void> {
+        await Promise.all(PluginStore.plugins.filter(x=>x.onMessage).map(x=>x.onMessage && x.onMessage(args)));
+    }
+    
+    public static async preLoginEvent(args: PreLoginEventArgs): Promise<PreLoginEventResult[]> {
+        return (await Promise.all(PluginStore.plugins.filter(x=>x.onPreLogin).map(x=>x.onPreLogin && x.onPreLogin(args)))).filter(x=>x) as PreLoginEventResult[];
+    }
+    
+    public static async onLoginEvent(args: OnLoginEventArgs): Promise<void> {
+        await Promise.all(PluginStore.plugins.filter(x=>x.onLogin).map(x=>x.onLogin && x.onLogin(args)));
+    }
+    
+    public static async preGuildCreateEvent(args: PreGuildCreateEventArgs): Promise<PreGuildCreateEventResult[]> {
+        return (await Promise.all(PluginStore.plugins.filter(x=>x.onPreGuildCreate).map(x=>x.onPreGuildCreate && x.onPreGuildCreate(args)))).filter(x=>x) as PreGuildCreateEventResult[];
+    }
+    
+    public static async onGuildCreateEvent(args: OnGuildCreateEventArgs): Promise<void> {
+        await Promise.all(PluginStore.plugins.filter(x=>x.onGuildCreate).map(x=>x.onGuildCreate && x.onGuildCreate(args)));
+    }
+    
+    public static async preChannelCreateEvent(args: PreChannelCreateEventArgs): Promise<PreChannelCreateEventResult[]> {
+        return (await Promise.all(PluginStore.plugins.filter(x=>x.onPreChannelCreate).map(x=>x.onPreChannelCreate && x.onPreChannelCreate(args)))).filter(x=>x) as PreChannelCreateEventResult[];
+    }
+    
+    public static async onChannelCreateEvent(args: OnChannelCreateEventArgs): Promise<void> {
+        await Promise.all(PluginStore.plugins.filter(x=>x.onChannelCreate).map(x=>x.onChannelCreate && x.onChannelCreate(args)));
+    }
+    
+    public static async preTypingEvent(args: PreTypingEventArgs): Promise<PreTypingEventResult[]> {
+        return (await Promise.all(PluginStore.plugins.filter(x=>x.onPreTyping).map(x=>x.onPreTyping && x.onPreTyping(args)))).filter(x=>x) as PreTypingEventResult[];
+    }
+    
+    public static async onTypingEvent(args: OnTypingEventArgs): Promise<void> {
+        await Promise.all(PluginStore.plugins.filter(x=>x.onTyping).map(x=>x.onTyping && x.onTyping(args)));
+    }
+    
+    public static async preStatusChangeEvent(args: PreStatusChangeEventArgs): Promise<PreStatusChangeEventResult[]> {
+        return (await Promise.all(PluginStore.plugins.filter(x=>x.onPreStatusChange).map(x=>x.onPreStatusChange && x.onPreStatusChange(args)))).filter(x=>x) as PreStatusChangeEventResult[];
+    }
+    
+    public static async onStatusChangeEvent(args: OnStatusChangeEventArgs): Promise<void> {
+        await Promise.all(PluginStore.plugins.filter(x=>x.onStatusChange).map(x=>x.onStatusChange && x.onStatusChange(args)));
+    }
+    
+}
diff --git a/src/util/plugin/PluginLoader.ts b/src/util/plugin/PluginLoader.ts
index 8c140d29..4dc0129a 100644
--- a/src/util/plugin/PluginLoader.ts
+++ b/src/util/plugin/PluginLoader.ts
@@ -1,14 +1,17 @@
 import path from "path";
 import fs from "fs";
-import { Plugin, PluginLoadedEventArgs, PluginManifest } from "./";
+import { Plugin, PluginLoadedEventArgs, PluginManifest, PluginStore } from "./";
 import { PluginIndex } from "plugins/PluginIndex";
+import { PluginConfig } from "./PluginConfig";
+import { OrmUtils, PluginConfigEntity } from "..";
 
 const root = process.env.PLUGIN_LOCATION || "dist/plugins";
 
 let pluginsLoaded = false;
 export class PluginLoader {
-	public static plugins: Plugin[] = [];
-	public static loadPlugins() {
+	public static async loadPlugins() {
+		if(pluginsLoaded) return;
+		PluginConfig.init();
 		console.log(`Plugin root directory: ${path.resolve(root)}`);
 		const dirs = fs.readdirSync(root).filter((x) => {
 			try {
@@ -18,24 +21,45 @@ export class PluginLoader {
 				return false;
 			}
 		});
-		console.log(dirs);
-		PluginIndex.forEach((x: any)=>{
-			console.log(x.onPluginLoaded)
-		})
+		//console.log(dirs);
+		PluginIndex.forEach((x: any) => {
+			//console.log(x.onPluginLoaded)
+		});
 		dirs.forEach(async (x) => {
 			let modPath = path.resolve(path.join(root, x));
-			console.log(`Trying to load plugin: ${modPath}`);
+			//console.log(`Trying to load plugin: ${modPath}`);
 			const manifest = require(path.join(modPath, "plugin.json")) as PluginManifest;
 			console.log(
 				`Plugin info: ${manifest.name} (${manifest.id}), written by ${manifest.authors}, available at ${manifest.repository}`
 			);
-			const module_ = PluginIndex["example-plugin"];
+			const module_ = PluginIndex[manifest.id];
 			
 			module_.pluginPath = modPath;
-			if(module_.onPluginLoaded) module_.onPluginLoaded({} as PluginLoadedEventArgs); 
-			this.plugins.push(module_);
+			module_.pluginManifest = manifest;
+			Object.freeze(module_.pluginPath);
+			Object.freeze(module_.pluginManifest);
+			
+			if(module_.onPluginLoaded) await module_.onPluginLoaded({} as PluginLoadedEventArgs);
+			PluginStore.plugins.push(module_);
 		});
 
-		console.log(`Done loading ${this.plugins.length} plugins!`)
+		console.log(`Done loading ${PluginStore.plugins.length} plugins!`);
+	}
+	
+	public static getPluginConfig(id: string, defaults?: any): any {
+		let cfg = PluginConfig.get()[id];
+		if(defaults) {
+			if(cfg)
+				cfg = OrmUtils.mergeDeep(defaults, cfg);
+			else
+				cfg = defaults;
+			this.setPluginConfig(id, cfg);
+		}
+		if(!cfg) console.log(`[PluginConfig/WARN] Getting plugin settings for '${id}' returned null! (Did you forget to add settings?)`);
+		return cfg;
+	}
+	public static async setPluginConfig(id: string, config: Partial<any>): Promise<void> {
+		if(!config) console.log(`[PluginConfig/WARN] ${id} tried to set config=null!`);
+		await PluginConfig.set({ [id]: OrmUtils.mergeDeep(PluginLoader.getPluginConfig(id) || {}, config) });
 	}
 }
diff --git a/src/util/plugin/PluginStore.ts b/src/util/plugin/PluginStore.ts
new file mode 100644
index 00000000..60d7f7b7
--- /dev/null
+++ b/src/util/plugin/PluginStore.ts
@@ -0,0 +1,12 @@
+import path from "path";
+import fs from "fs";
+import { Plugin, PluginLoadedEventArgs, PluginManifest } from "./";
+import { PluginIndex } from "plugins/PluginIndex";
+import { PluginConfig } from "./PluginConfig";
+import { OrmUtils, PluginConfigEntity } from "..";
+
+const root = process.env.PLUGIN_LOCATION || "dist/plugins";
+
+export class PluginStore {
+	public static plugins: Plugin[] = [];
+}
diff --git a/src/util/plugin/event_types/ChannelCreateEventArgs.ts b/src/util/plugin/event_types/ChannelCreateEventArgs.ts
new file mode 100644
index 00000000..87fa3691
--- /dev/null
+++ b/src/util/plugin/event_types/ChannelCreateEventArgs.ts
@@ -0,0 +1,17 @@
+import { Channel, Guild, User } from "util/entities";
+import { EventResult } from ".";
+
+export interface PreChannelCreateEventArgs {
+    channel: Channel,
+	guild: Guild,
+	user: User
+}
+export interface PreChannelCreateEventResult extends EventResult {
+    channel: Partial<Channel>
+}
+
+export interface OnChannelCreateEventArgs {
+    channel: Channel,
+	guild: Guild,
+	user: User
+}
diff --git a/src/util/plugin/event_types/GuildCreateEventArgs.ts b/src/util/plugin/event_types/GuildCreateEventArgs.ts
new file mode 100644
index 00000000..7724b4f3
--- /dev/null
+++ b/src/util/plugin/event_types/GuildCreateEventArgs.ts
@@ -0,0 +1,15 @@
+import { Guild, User } from "util/entities";
+import { EventResult } from ".";
+
+export interface PreGuildCreateEventArgs {
+    user: User,
+	guild: Guild
+}
+export interface PreGuildCreateEventResult extends EventResult {
+    guild: Partial<Guild>
+}
+
+export interface OnGuildCreateEventArgs {
+    user: User,
+	guild: Guild
+}
diff --git a/src/util/plugin/event_types/LoginEventArgs.ts b/src/util/plugin/event_types/LoginEventArgs.ts
new file mode 100644
index 00000000..8f80b69f
--- /dev/null
+++ b/src/util/plugin/event_types/LoginEventArgs.ts
@@ -0,0 +1,15 @@
+import { User } from "util/entities";
+import { EventResult } from ".";
+
+export interface PreLoginEventArgs {
+	ip: String,
+	user: User
+}
+export interface PreLoginEventResult extends EventResult {
+    
+}
+
+export interface OnLoginEventArgs {
+	ip: String,
+	user: User
+}
diff --git a/src/util/plugin/event_types/MessageEventArgs.ts b/src/util/plugin/event_types/MessageEventArgs.ts
new file mode 100644
index 00000000..ab276429
--- /dev/null
+++ b/src/util/plugin/event_types/MessageEventArgs.ts
@@ -0,0 +1,15 @@
+import { Message, User } from "util/entities";
+import { EventResult } from ".";
+
+export interface PreMessageEventArgs {
+	user: User,
+    message: Message;
+}
+export interface PreMessageEventResult extends EventResult {
+    message: Partial<Message>
+}
+
+export interface OnMessageEventArgs {
+	user: User,
+    message: Message
+}
diff --git a/src/util/plugin/plugin_data_objects/PluginLoadedEventArgs.ts b/src/util/plugin/event_types/PluginLoadedEventArgs.ts
index 58829f15..58829f15 100644
--- a/src/util/plugin/plugin_data_objects/PluginLoadedEventArgs.ts
+++ b/src/util/plugin/event_types/PluginLoadedEventArgs.ts
diff --git a/src/util/plugin/event_types/RegisterEventArgs.ts b/src/util/plugin/event_types/RegisterEventArgs.ts
new file mode 100644
index 00000000..d36d7e64
--- /dev/null
+++ b/src/util/plugin/event_types/RegisterEventArgs.ts
@@ -0,0 +1,17 @@
+import { User } from "util/entities";
+import { EventResult } from ".";
+
+export interface PreRegisterEventArgs {
+	age: any,
+	user: User,
+	ip: String
+}
+export interface PreRegisterEventResult extends EventResult {
+    user: Partial<User>
+}
+
+export interface OnRegisterEventArgs {
+    age: any,
+	user: User,
+	ip: String
+}
diff --git a/src/util/plugin/event_types/StatusChangeEventArgs.ts b/src/util/plugin/event_types/StatusChangeEventArgs.ts
new file mode 100644
index 00000000..c1a67112
--- /dev/null
+++ b/src/util/plugin/event_types/StatusChangeEventArgs.ts
@@ -0,0 +1,16 @@
+import { User } from "util/entities";
+import { Presence } from "util/interfaces";
+import { EventResult } from ".";
+
+export interface PreStatusChangeEventArgs {
+    user: User,
+	presence: Presence
+}
+export interface PreStatusChangeEventResult extends EventResult {
+    presence: Partial<Presence>
+}
+
+export interface OnStatusChangeEventArgs {
+    user: User,
+	presence: Presence
+}
diff --git a/src/util/plugin/event_types/TypingEventArgs.ts b/src/util/plugin/event_types/TypingEventArgs.ts
new file mode 100644
index 00000000..3ac59b41
--- /dev/null
+++ b/src/util/plugin/event_types/TypingEventArgs.ts
@@ -0,0 +1,17 @@
+import { Channel, Guild, User } from "util/entities";
+import { EventResult } from ".";
+
+export interface PreTypingEventArgs {
+    channel: Channel,
+	guild: Guild,
+	user: User
+}
+export interface PreTypingEventResult extends EventResult {
+    
+}
+
+export interface OnTypingEventArgs {
+    channel: Channel,
+	guild: Guild,
+	user: User
+}
diff --git a/src/util/plugin/event_types/_gen.sh b/src/util/plugin/event_types/_gen.sh
new file mode 100755
index 00000000..9f761d1e
--- /dev/null
+++ b/src/util/plugin/event_types/_gen.sh
@@ -0,0 +1,74 @@
+#!/bin/sh
+rm -f ../plugin.eventfuncs.generated
+rm -f ../plugin.imports.generated
+while read event
+do
+    echo Making event $event...
+    (
+        echo 'import { EventResult } from ".";'
+        echo ''
+        echo "export interface Pre${event}EventArgs {"
+        echo '    '
+        echo '}'
+        echo "export interface Pre${event}EventResult extends EventResult {"
+        echo '    '
+        echo '}'
+        echo ''
+        echo "export interface On${event}EventArgs {"
+        echo '    '
+        echo '}'
+    ) > ${event}EventArgs.ts.generated
+    (
+        echo "    public static async pre${event}Event(args: Pre${event}EventArgs): Promise<Pre${event}EventResult[]> {"
+        echo "        return (await Promise.all(PluginStore.plugins.filter(x=>x.onPre${event}).map(x=>x.onPre${event} && x.onPre${event}(args)))).filter(x=>x) as Pre${event}EventResult[];"
+        echo '    }'
+        echo '    '
+        echo "    public static async on${event}Event(args: On${event}EventArgs): Promise<void> {"
+        echo "        await Promise.all(PluginStore.plugins.filter(x=>x.on${event}).map(x=>x.on${event} && x.on${event}(args)));"
+        echo '    }'
+        echo '    '
+    ) >> ../PluginEventHandler.ts.3
+    (
+        echo "    /**"
+        echo "    * ${event}Event: document me"
+        echo "    *"
+        echo "    * @param {On${event}EventArgs} args Info about what's going on"
+        echo "    * @memberof Plugin"
+        echo "    */"
+        echo "    async on${event}?(args: On${event}EventArgs): Promise<void>;"
+        echo '    '
+        echo "    /**"
+        echo "    * ${event}Event: Executed before changes are announced"
+        echo "    * document me."
+        echo "    *"
+        echo "    * @param {Pre${event}EventArgs} args Info about what's going on"
+        echo "    * @return {Pre${event}EventResult} How event should be handled"
+        echo "    * @memberof Plugin"
+        echo "    */"
+        echo "    async onPre${event}?(args: Pre${event}EventArgs): Promise<Pre${event}EventResult>;"
+    ) >> ../plugin.eventfuncs.generated
+
+    echo "import { Pre${event}EventArgs, On${event}EventArgs, Pre${event}EventResult } from './event_types';" >> ../PluginEventHandler.ts.1
+    echo "import { Pre${event}EventArgs, Pre${event}EventResult, On${event}EventArgs } from '.';" >> ../plugin.imports.generated
+    cmp --silent "${event}EventArgs.ts" "${event}EventArgs.ts.generated" && rm -f "${event}EventArgs.ts.generated"
+done < _pdo
+
+echo 'Building PluginEventHandler...'
+
+rm -f ../PluginEventHandler.ts.generated
+(
+    echo 'import { PluginStore } from ".";'
+    echo ''
+    echo 'export class PluginEventHandler {'
+) > ../PluginEventHandler.ts.2
+echo '}' > ../PluginEventHandler.ts.4
+for i in {1..4}
+do
+    cat "../PluginEventHandler.ts.$i" >> ../PluginEventHandler.ts.generated
+    rm -f ../PluginEventHandler.ts.$i
+done
+cmp --silent ../PluginEventHandler.ts ../PluginEventHandler.ts.generated && rm -f ../PluginEventHandler.ts.generated
+
+echo 'Rebuilding indexes...'
+node ../../../../scripts/gen_index.js .. --recursive
+echo 'Done!'
\ No newline at end of file
diff --git a/src/util/plugin/plugin_data_objects/_pdo b/src/util/plugin/event_types/_pdo
index fa207f77..f6127174 100644
--- a/src/util/plugin/plugin_data_objects/_pdo
+++ b/src/util/plugin/event_types/_pdo
@@ -4,4 +4,5 @@ Login
 GuildCreate
 ChannelCreate
 Typing
-StatusChange
\ No newline at end of file
+StatusChange
+UserProfileUpdate
\ No newline at end of file
diff --git a/src/util/plugin/plugin_data_objects/_todo.txt b/src/util/plugin/event_types/_todo.txt
index a6a78c7e..a6a78c7e 100644
--- a/src/util/plugin/plugin_data_objects/_todo.txt
+++ b/src/util/plugin/event_types/_todo.txt
diff --git a/src/util/plugin/event_types/base/EventResult.ts b/src/util/plugin/event_types/base/EventResult.ts
new file mode 100644
index 00000000..f18837cd
--- /dev/null
+++ b/src/util/plugin/event_types/base/EventResult.ts
@@ -0,0 +1,4 @@
+export interface EventResult {
+    cancel?: boolean;
+    blockReason?: string;
+}
\ No newline at end of file
diff --git a/src/util/plugin/event_types/base/index.ts b/src/util/plugin/event_types/base/index.ts
new file mode 100644
index 00000000..fd0dbd03
--- /dev/null
+++ b/src/util/plugin/event_types/base/index.ts
@@ -0,0 +1 @@
+export * from "./EventResult";
diff --git a/src/util/plugin/plugin_data_objects/index.ts b/src/util/plugin/event_types/index.ts
index c75d43f9..4a585dc0 100644
--- a/src/util/plugin/plugin_data_objects/index.ts
+++ b/src/util/plugin/event_types/index.ts
@@ -4,4 +4,6 @@ export * from "./LoginEventArgs";
 export * from "./MessageEventArgs";
 export * from "./PluginLoadedEventArgs";
 export * from "./RegisterEventArgs";
+export * from "./StatusChangeEventArgs";
 export * from "./TypingEventArgs";
+export * from "./base/index";
diff --git a/src/util/plugin/index.ts b/src/util/plugin/index.ts
index 5974a065..7a297981 100644
--- a/src/util/plugin/index.ts
+++ b/src/util/plugin/index.ts
@@ -1,4 +1,7 @@
 export * from "./Plugin";
+export * from "./PluginConfig";
+export * from "./PluginEventHandler";
 export * from "./PluginLoader";
 export * from "./PluginManifest";
-export * from "./plugin_data_objects/index";
+export * from "./PluginStore";
+export * from "./event_types/index";
diff --git a/src/util/plugin/plugin_data_objects/ChannelCreateEventArgs.ts b/src/util/plugin/plugin_data_objects/ChannelCreateEventArgs.ts
deleted file mode 100644
index ce7dec87..00000000
--- a/src/util/plugin/plugin_data_objects/ChannelCreateEventArgs.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export interface PreChannelCreateEventArgs {
-  
-}
-
-export interface OnChannelCreateEventArgs {
-  
-}
diff --git a/src/util/plugin/plugin_data_objects/GuildCreateEventArgs.ts b/src/util/plugin/plugin_data_objects/GuildCreateEventArgs.ts
deleted file mode 100644
index e10e675a..00000000
--- a/src/util/plugin/plugin_data_objects/GuildCreateEventArgs.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export interface PreGuildCreateEventArgs {
-  
-}
-
-export interface OnGuildCreateEventArgs {
-  
-}
diff --git a/src/util/plugin/plugin_data_objects/LoginEventArgs.ts b/src/util/plugin/plugin_data_objects/LoginEventArgs.ts
deleted file mode 100644
index 391b852e..00000000
--- a/src/util/plugin/plugin_data_objects/LoginEventArgs.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export interface PreLoginEventArgs {
-  
-}
-
-export interface OnLoginEventArgs {
-  
-}
diff --git a/src/util/plugin/plugin_data_objects/MessageEventArgs.ts b/src/util/plugin/plugin_data_objects/MessageEventArgs.ts
deleted file mode 100644
index 0a3498c2..00000000
--- a/src/util/plugin/plugin_data_objects/MessageEventArgs.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export interface PreMessageEventArgs {
-  
-}
-
-export interface OnMessageEventArgs {
-  
-}
diff --git a/src/util/plugin/plugin_data_objects/RegisterEventArgs.ts b/src/util/plugin/plugin_data_objects/RegisterEventArgs.ts
deleted file mode 100644
index 7f7c0c76..00000000
--- a/src/util/plugin/plugin_data_objects/RegisterEventArgs.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export interface PreRegisterEventArgs {
-  
-}
-
-export interface OnRegisterEventArgs {
-  
-}
diff --git a/src/util/plugin/plugin_data_objects/TypingEventArgs.ts b/src/util/plugin/plugin_data_objects/TypingEventArgs.ts
deleted file mode 100644
index f6660692..00000000
--- a/src/util/plugin/plugin_data_objects/TypingEventArgs.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export interface PreTypingEventArgs {
-  
-}
-
-export interface OnTypingEventArgs {
-  
-}
diff --git a/src/util/plugin/plugin_data_objects/_gen.sh b/src/util/plugin/plugin_data_objects/_gen.sh
deleted file mode 100755
index 9fbd1749..00000000
--- a/src/util/plugin/plugin_data_objects/_gen.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/sh
-while read event
-do
-    if [ ! -f "${event}EventArgs.ts" ]
-    then
-        echo Making event $event...
-        (
-            echo "export interface Pre${event}EventArgs {"
-            echo '  '
-            echo '}'
-            echo ''
-            echo "export interface On${event}EventArgs {"
-            echo '  '
-            echo '}'
-        ) > ${event}EventArgs.ts
-    fi
-done < _pdo
-
-echo ''
-
-node ../../../../scripts/gen_index.js .. --recursive
\ No newline at end of file
diff --git a/src/util/util/Logo.ts b/src/util/util/Logo.ts
new file mode 100644
index 00000000..b1627198
--- /dev/null
+++ b/src/util/util/Logo.ts
@@ -0,0 +1,41 @@
+import { existsSync } from "fs";
+import { execSync } from "child_process";
+
+export class Logo {
+    public static printLogo(){
+        if(existsSync("/usr/bin/chafa"))
+        return execSync("chafa https://raw.githubusercontent.com/fosscord/fosscord/master/assets-rebrand/svg/Fosscord-Wordmark-Orange.svg", {
+			env: process.env,
+			encoding: "utf-8",
+            stdio: "inherit",
+            
+		});
+        else console.log(Logo.logoVersions['1'] as string)
+    }
+    private static getConsoleColors(): number {
+        return 1;
+        if(!process.env.TERM) return 1;
+        else {
+            switch (process.env.TERM) {
+                case "":
+                    
+                    break;
+            
+                default:
+                    break;
+            }
+        }
+        return 1;
+    }
+    private static logoVersions: any = {
+        '1':
+           `███████╗ ██████╗ ███████╗███████╗ ██████╗ ██████╗ ██████╗ ██████╗
+            ██╔════╝██╔═══██╗██╔════╝██╔════╝██╔════╝██╔═══██╗██╔══██╗██╔══██╗
+            █████╗  ██║   ██║███████╗███████╗██║     ██║   ██║██████╔╝██║  ██║
+            ██╔══╝  ██║   ██║╚════██║╚════██║██║     ██║   ██║██╔══██╗██║  ██║
+            ██║     ╚██████╔╝███████║███████║╚██████╗╚██████╔╝██║  ██║██████╔╝
+            ╚═╝      ╚═════╝ ╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝  ╚═╝╚═════╝`,
+        '2':``
+            
+    }
+}
\ No newline at end of file
diff --git a/src/util/util/imports/HTTPError.ts b/src/util/util/imports/HTTPError.ts
index 70ba92a8..3439bd00 100644
--- a/src/util/util/imports/HTTPError.ts
+++ b/src/util/util/imports/HTTPError.ts
@@ -1,5 +1,5 @@
 export class HTTPError extends Error {
-	constructor(message: string, public code: number = 400) {
+	constructor(message: string, public code: number = 400, public data: any = null) {
 		super(message);
 	}
 }