Merge branch 'development' of github.com:clicksminuteper/nucleus into development
diff --git a/.gitignore b/.gitignore
index 72a0e85..bfedc85 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,11 @@
 dist/
 .history/
 node_modules/
-src/config/
+src/config/*
+!src/config/format.ts
+!src/config/default.json
+!src/config/emojis.json
+src/config/main.json
 .vscode/
 yarn-error.log
 yarn.lock
diff --git a/ClicksMigratingProblems/index.js b/ClicksMigratingProblems/index.js
index ca6e63d..c6f77bf 100644
--- a/ClicksMigratingProblems/index.js
+++ b/ClicksMigratingProblems/index.js
@@ -62,7 +62,7 @@
                 "message": null,
                 "role": null,
             },
-            "welcomeRole": data.welcome ? (data.welcome.role !== null ? data.welcome.role.toString() : null) : null,
+            "role": data.welcome ? (data.welcome.role !== null ? data.welcome.role.toString() : null) : null,
             "channel": data.welcome ? (data.welcome.message.text !== null ? data.welcome.message.channel.toString() : null) : null,
             "message": data.welcome ? (data.welcome.message.text) : null
         },
diff --git a/TODO b/TODO
index cdcd42a..579b574 100644
--- a/TODO
+++ b/TODO
@@ -1,6 +1,6 @@
 Role all
 Server rules
-! A way to add buttons to verify / role menu
+verificationRequired on welcome
 
 ROLE MENU SETTINGS
 
diff --git a/TODO.json b/TODO.json
index 7a13d1e..802aee6 100644
--- a/TODO.json
+++ b/TODO.json
@@ -1,11 +1,5 @@
 
 {
-	"logging": {
-		"logs": {
-			"enabled": true,
-			"toLog": "3fffff"
-		}
-	},
 	"filters": {
 		"images": {
 			"NSFW": false,
@@ -30,11 +24,5 @@
 		}
 	},
 	"roleMenu": [],
-	"tracks": [],
-	"welcome": {
-		"enabled": false,
-		"welcomeRole": null,
-		"channel": null,
-		"message": null
-	}
+	"tracks": []
 }
\ No newline at end of file
diff --git a/src/Unfinished/all.ts b/src/Unfinished/all.ts
index 5c482f1..d49633c 100644
--- a/src/Unfinished/all.ts
+++ b/src/Unfinished/all.ts
@@ -78,7 +78,7 @@
     }
 }
 
-const callback = async (interaction: CommandInteraction) => {
+const callback = async (interaction: CommandInteraction): Promise<any> => {
     await interaction.reply({embeds: LoadingEmbed, ephemeral: true, fetchReply: true})
     let filters: Filter[] = [
         filterList.member.has.role("959901346000154674"),
diff --git a/src/actions/tickets/create.ts b/src/actions/tickets/create.ts
index 722eb3f..28eb435 100644
--- a/src/actions/tickets/create.ts
+++ b/src/actions/tickets/create.ts
@@ -17,6 +17,7 @@
         return await interaction.reply({embeds: [new EmojiEmbed()
             .setTitle("Tickets are disabled")
             .setDescription("Please enable tickets in the configuration to use this command.")
+            .setFooter({text: interaction.member.permissions.has("MANAGE_GUILD") ? "You can enable it by running /settings tickets" : ""})
             .setStatus("Danger")
             .setEmoji("CONTROL.BLOCKCROSS")
         ], ephemeral: true});
diff --git a/src/commands/createTestButton.ts b/src/commands/createTestButton.ts
deleted file mode 100644
index c04e13d..0000000
--- a/src/commands/createTestButton.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { CommandInteraction, MessageActionRow, MessageButton } from "discord.js";
-import { SlashCommandBuilder } from "@discordjs/builders";
-import { WrappedCheck } from "jshaiku";
-
-const command = new SlashCommandBuilder()
-    .setName("createtestbutton")
-    .setDescription("creates a test button") // TODO: remove for release
-
-const callback = (interaction: CommandInteraction) => {
-    interaction.reply({components: [new MessageActionRow().addComponents([
-        new MessageButton()
-            .setCustomId("createticket")
-            .setLabel("Create Ticket")
-            .setStyle("PRIMARY")
-            .setDisabled(false),
-        new MessageButton()
-            .setCustomId("verifybutton")
-            .setLabel("Verify")
-            .setStyle("PRIMARY")
-            .setDisabled(false),
-        new MessageButton()
-            .setCustomId("rolemenu")
-            .setLabel("Get roles")
-            .setStyle("PRIMARY")
-            .setDisabled(false)
-    ])]});
-}
-
-const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
-    return true;
-}
-
-export { command };
-export { callback };
-export { check };
\ No newline at end of file
diff --git a/src/commands/help.ts b/src/commands/help.ts
index 568a90c..c3b015c 100644
--- a/src/commands/help.ts
+++ b/src/commands/help.ts
@@ -6,7 +6,7 @@
     .setName("help")
     .setDescription("Shows help for commands")
 
-const callback = (interaction: CommandInteraction) => {
+const callback = async (interaction: CommandInteraction): Promise<any> => {
     interaction.reply("hel p"); // TODO: FINISH THIS FOR RELEASE
 }
 
diff --git a/src/commands/nucleus/invite.ts b/src/commands/nucleus/invite.ts
index 7f7d4b8..96e1449 100644
--- a/src/commands/nucleus/invite.ts
+++ b/src/commands/nucleus/invite.ts
@@ -9,7 +9,7 @@
     .setName("invite")
     .setDescription("Invites Nucleus to your server")
 
-const callback = (interaction: CommandInteraction) => {
+const callback = async (interaction: CommandInteraction): Promise<any> => {
     interaction.reply({embeds: [new EmojiEmbed()
         .setTitle("Invite")
         .setDescription("You can invite Nucleus to your server by clicking the button below")
diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts
index 740ab7b..9d273b9 100644
--- a/src/commands/nucleus/premium.ts
+++ b/src/commands/nucleus/premium.ts
@@ -8,7 +8,7 @@
     .setName("premium")
     .setDescription("Information about Nucleus Premium")
 
-const callback = (interaction: CommandInteraction) => {
+const callback = async (interaction: CommandInteraction): Promise<any> => {
     interaction.reply({embeds: [new EmojiEmbed()
         .setTitle("Premium")
         .setDescription(
diff --git a/src/commands/nucleus/stats.ts b/src/commands/nucleus/stats.ts
index cb10e7a..beea94b 100644
--- a/src/commands/nucleus/stats.ts
+++ b/src/commands/nucleus/stats.ts
@@ -9,7 +9,7 @@
     .setName("stats")
     .setDescription("Gets the bot's stats")
 
-const callback = (interaction: CommandInteraction) => {
+const callback = async (interaction: CommandInteraction): Promise<any> => {
     interaction.reply({
         embeds: [new EmojiEmbed()
             .setTitle("Stats")
diff --git a/src/commands/settings/filters.ts b/src/commands/settings/filters.ts
new file mode 100644
index 0000000..183b91c
--- /dev/null
+++ b/src/commands/settings/filters.ts
@@ -0,0 +1,29 @@
+import { LoadingEmbed } from './../../utils/defaultEmbeds.js';
+import Discord, { CommandInteraction, MessageActionRow, MessageButton, MessageSelectMenu } from "discord.js";
+import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import { WrappedCheck } from "jshaiku";
+import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import client from '../../utils/client.js';
+import confirmationMessage from '../../utils/confirmationMessage.js';
+import generateKeyValueList from '../../utils/generateKeyValueList.js';
+import { ChannelType } from 'discord-api-types';
+import getEmojiByName from '../../utils/getEmojiByName.js';
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+    builder
+    .setName("filter")
+    .setDescription("Setting for message filters")
+
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+
+}
+
+const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
+    let member = (interaction.member as Discord.GuildMember)
+    if (!member.permissions.has("MANAGE_GUILD")) throw "You must have the *Manage Server* permission to use this command"
+    return true;
+}
+
+export { command };
+export { callback };
+export { check };
\ No newline at end of file
diff --git a/src/commands/settings/logs/_meta.ts b/src/commands/settings/logs/_meta.ts
index 15a6fd4..f46987f 100644
--- a/src/commands/settings/logs/_meta.ts
+++ b/src/commands/settings/logs/_meta.ts
@@ -1,4 +1,4 @@
-const name = "log";
+const name = "logs";
 const description = "Settings for logging";
 
 export { name, description };
\ No newline at end of file
diff --git a/src/commands/settings/logs/events.ts b/src/commands/settings/logs/events.ts
index dac200c..ef303cb 100644
--- a/src/commands/settings/logs/events.ts
+++ b/src/commands/settings/logs/events.ts
@@ -1,17 +1,106 @@
-import { CommandInteraction } from "discord.js";
+import { LoadingEmbed } from './../../../utils/defaultEmbeds.js';
+import Discord, { CommandInteraction, MessageActionRow, MessageButton, MessageSelectMenu } from "discord.js";
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
+import EmojiEmbed from "../../../utils/generateEmojiEmbed.js";
+import client from '../../../utils/client.js';
+import { toHexArray, toHexInteger } from "../../../utils/calculate.js";
+
+
+const logs = {
+    "channelUpdate": "Channels created, deleted or modified",
+    "emojiUpdate": "Server emojis modified",
+    "stickerUpdate": "Server stickers modified",
+    "guildUpdate": "Server settings updated",
+    "guildMemberUpdate": "Member updated (i.e. nickname)",
+    "guildMemberPunish": "Members punished (i.e. muted, banned, kicked)",
+    "guildRoleUpdate": "Role settings changed",
+    "guildInviteUpdate": "Server invite created or deleted",
+    "messageUpdate": "Message edited",
+    "messageDelete": "Message deleted",
+    "messageDeleteBulk": "Messages purged",
+    "messageReactionUpdate": "Message reactions cleared",
+    "messageMassPing": "Message pings multiple members at once",
+    "messageAnnounce": "Message published in announcement channel",
+    "threadUpdate": "Thread created or deleted",
+    "webhookUpdate": "Webhooks created or deleted",
+    "guildMemberVerify": "Member runs verify",
+    "autoModeratorDeleted": "Messages auto deleted by Nucleus",
+    "nucleusSettingsUpdated": "Nucleus' settings updated by a moderator",
+    "ticketUpdate": "Tickets created or deleted",
+}
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
     .setName("events")
     .setDescription("Sets what events should be logged")
 
-const callback = (interaction: CommandInteraction) => {
-    interaction.reply("This command is not yet finished [settings/log/events]");
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+    await interaction.reply({embeds: LoadingEmbed, fetchReply: true, ephemeral: true});
+    let m;
+    while (true) {
+        let config = await client.database.guilds.read(interaction.guild.id)
+        let converted = toHexArray(config.logging.logs.toLog)
+        m = await interaction.editReply({embeds: [new EmojiEmbed()
+            .setTitle("Logging Events")
+            .setDescription("Below are the events being logged in the server. You can toggle them on and off in the dropdown")
+            .setStatus("Success")
+            .setEmoji("CHANNEL.TEXT.CREATE")
+        ], components: [
+            new MessageActionRow().addComponents([new MessageSelectMenu()
+                .setPlaceholder("Set events to log")
+                .setMaxValues(Object.keys(logs).length)
+                .setCustomId("logs")
+                .setMinValues(0)
+                .setOptions(Object.keys(logs).map((e, i) => ({
+                    label: logs[e],
+                    value: i.toString(),
+                    default: converted.includes(e)
+                })))
+            ]),
+            new MessageActionRow().addComponents([
+                new MessageButton()
+                    .setLabel("Select all")
+                    .setStyle("PRIMARY")
+                    .setCustomId("all"),
+                new MessageButton()
+                    .setLabel("Select none")
+                    .setStyle("DANGER")
+                    .setCustomId("none")
+            ])
+        ]})
+        let i;
+        try {
+            i = await m.awaitMessageComponent({ time: 300000 });
+        } catch (e) {
+            break
+        }
+        i.deferUpdate()
+        if (i.customId === "logs") {
+            let selected = i.values;
+            let newLogs = toHexInteger(selected.map(e => Object.keys(logs)[parseInt(e)]))
+            await client.database.guilds.write(interaction.guild.id, {"logging.logs.toLog": newLogs})
+        } else if (i.customId === "all") {
+            let newLogs = toHexInteger(Object.keys(logs).map(e => e))
+            await client.database.guilds.write(interaction.guild.id, {"logging.logs.toLog": newLogs})
+        } else if (i.customId === "none") {
+            await client.database.guilds.write(interaction.guild.id, {"logging.logs.toLog": 0})
+        } else {
+            break
+        }
+    }
+    m = await interaction.editReply({embeds: [new EmojiEmbed()
+        .setTitle("Logging Events")
+        .setDescription("Below are the events being logged in the server. You can toggle them on and off in the dropdown")
+        .setFooter({text: "Message timed out"})
+        .setStatus("Success")
+        .setEmoji("CHANNEL.TEXT.CREATE")
+    ]})
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
+    let member = (interaction.member as Discord.GuildMember)
+    if (!member.permissions.has("MANAGE_GUILD")) throw "You must have the *Manage Server* permission to use this command"
     return true;
 }
 
diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts
index d972674..be15869 100644
--- a/src/commands/settings/stats.ts
+++ b/src/commands/settings/stats.ts
@@ -144,12 +144,7 @@
             await client.database.guilds.write(interaction.guild.id, null, toRemove.map(k => `stats.${k}`));
         }
     }
-    await interaction.editReply({embeds: [new EmojiEmbed()
-        .setTitle("Stats Channel")
-        .setDescription("The following channels update when someone joins or leaves the server. You can select a channel to remove it from the list.")
-        .setStatus("Danger")
-        .setEmoji("CHANNEL.TEXT.DELETE")
-    ], components: []})
+    await interaction.editReply({embeds: [m.embeds[0].setFooter({text: "Message closed"})], components: []});
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts
index f199ac3..44f974e 100644
--- a/src/commands/settings/tickets.ts
+++ b/src/commands/settings/tickets.ts
@@ -192,6 +192,11 @@
                     .setEmoji(getEmojiByName("TICKETS.OTHER", "id"))
                     .setStyle("SECONDARY")
                     .setCustomId("manageTypes"),
+                new MessageButton()
+                    .setLabel("Add create ticket button")
+                    .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
+                    .setStyle("PRIMARY")
+                    .setCustomId("send"),
             ])]
         });
         let i;
@@ -217,6 +222,122 @@
                 await client.database.guilds.write(interaction.guild.id, null, ["tickets.supportRole"])
                 data.supportRole = undefined;
             } else lastClicked = "sup";
+        } else if (i.component.customId === "send") {
+            const ticketMessages = [
+                {label: "Create ticket", description: "Click the button below to create a ticket"},
+                {label: "Issues, questions or feedback?", description: "Click below to open a ticket and get help from our staff team"},
+                {label: "Contact Us", description: "Click the button below to speak to us privately"},
+            ]
+            while (true) {
+                let enabled = data.enabled && data.category !== null;
+                await interaction.editReply({embeds: [new EmojiEmbed()
+                    .setTitle("Ticket Button")
+                    .setDescription("Select a message template to send in this channel")
+                    .setFooter({text: enabled ? "" : "Tickets are not set up correctly so the button may not work for users. Check the main menu to find which options must be set."})
+                    .setStatus(enabled ? "Success" : "Warning")
+                    .setEmoji("GUILD.ROLES.CREATE")
+                ], components: [
+                    new MessageActionRow().addComponents([
+                        new MessageSelectMenu().setOptions(ticketMessages.map((t: {label: string, description: string, value?: string}, index) => {
+                            t.value = index.toString(); return t as {value: string, label: string, description: string}
+                        })).setCustomId("template").setMaxValues(1).setMinValues(1).setPlaceholder("Select a message template"),
+                    ]),
+                    new MessageActionRow().addComponents([
+                        new MessageButton()
+                            .setCustomId("back")
+                            .setLabel("Back")
+                            .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                            .setStyle("DANGER"),
+                        new MessageButton()
+                            .setCustomId("blank")
+                            .setLabel("Empty")
+                            .setStyle("SECONDARY"),
+                        new MessageButton()
+                            .setCustomId("custom")
+                            .setLabel("Custom")
+                            .setEmoji(getEmojiByName("TICKETS.OTHER", "id"))
+                            .setStyle("PRIMARY")
+                    ])
+                ]});
+                let i;
+                try {
+                    i = await m.awaitMessageComponent({time: 300000});
+                } catch(e) { break }
+                if (i.component.customId === "template") {
+                    i.deferUpdate()
+                    await interaction.channel.send({embeds: [new EmojiEmbed()
+                        .setTitle(ticketMessages[parseInt(i.values[0])].label)
+                        .setDescription(ticketMessages[parseInt(i.values[0])].description)
+                        .setStatus("Success")
+                        .setEmoji("GUILD.TICKET.OPEN")
+                    ], components: [new MessageActionRow().addComponents([new MessageButton()
+                        .setLabel("Create Ticket")
+                        .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
+                        .setStyle("SUCCESS")
+                        .setCustomId("createticket")
+                    ])]});
+                    break
+                } else if (i.component.customId === "blank") {
+                    i.deferUpdate()
+                    await interaction.channel.send({components: [new MessageActionRow().addComponents([new MessageButton()
+                        .setLabel("Create Ticket")
+                        .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
+                        .setStyle("SUCCESS")
+                        .setCustomId("createticket")
+                    ])]});
+                    break
+                } else if (i.component.customId === "custom") {
+                    await i.showModal(new Discord.Modal().setCustomId("modal").setTitle(`Enter embed details`).addComponents(
+                        new MessageActionRow<TextInputComponent>().addComponents(new TextInputComponent()
+                            .setCustomId("title")
+                            .setLabel("Title")
+                            .setMaxLength(256)
+                            .setRequired(true)
+                            .setStyle("SHORT")
+                        ),
+                        new MessageActionRow<TextInputComponent>().addComponents(new TextInputComponent()
+                            .setCustomId("description")
+                            .setLabel("Description")
+                            .setMaxLength(4000)
+                            .setRequired(true)
+                            .setStyle("PARAGRAPH")
+                        )
+                    ))
+                    await interaction.editReply({
+                        embeds: [new EmojiEmbed()
+                            .setTitle("Ticket Button")
+                            .setDescription("Modal opened. If you can't see it, click back and try again.")
+                            .setStatus("Success")
+                            .setEmoji("GUILD.TICKET.OPEN")
+                        ], components: [new MessageActionRow().addComponents([new MessageButton()
+                            .setLabel("Back")
+                            .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                            .setStyle("PRIMARY")
+                            .setCustomId("back")
+                        ])]
+                    });
+                    let out;
+                    try {
+                        out = await modalInteractionCollector(m, (m) => m.channel.id === interaction.channel.id, (m) => m.customId === "modify")
+                    } catch (e) { break }
+                    if (out.fields) {
+                        let title = out.fields.getTextInputValue("title");
+                        let description = out.fields.getTextInputValue("description");
+                        await interaction.channel.send({embeds: [new EmojiEmbed()
+                            .setTitle(title)
+                            .setDescription(description)
+                            .setStatus("Success")
+                            .setEmoji("GUILD.TICKET.OPEN")
+                        ], components: [new MessageActionRow().addComponents([new MessageButton()
+                            .setLabel("Create Ticket")
+                            .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
+                            .setStyle("SUCCESS")
+                            .setCustomId("createticket")
+                        ])]});
+                        break
+                    } else { continue }
+                }
+            }
         } else if (i.component.customId === "enabled") {
             await client.database.guilds.write(interaction.guild.id, { "tickets.enabled": !data.enabled })
             data.enabled = !data.enabled;
diff --git a/src/commands/settings/verify.ts b/src/commands/settings/verify.ts
index 14dfe8d..a77f2f4 100644
--- a/src/commands/settings/verify.ts
+++ b/src/commands/settings/verify.ts
@@ -1,11 +1,12 @@
 import { LoadingEmbed } from './../../utils/defaultEmbeds.js';
-import Discord, { CommandInteraction, MessageActionRow, MessageButton } from "discord.js";
+import Discord, { CommandInteraction, Emoji, MessageActionRow, MessageButton, MessageSelectMenu, TextInputComponent } from "discord.js";
 import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
 import confirmationMessage from "../../utils/confirmationMessage.js";
 import getEmojiByName from "../../utils/getEmojiByName.js";
 import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
 import { WrappedCheck } from "jshaiku";
 import client from "../../utils/client.js";
+import { modalInteractionCollector } from '../../utils/dualCollector.js';
 
 const command = (builder: SlashCommandSubcommandBuilder) =>
     builder
@@ -53,7 +54,7 @@
                     let data = {
                         meta:{
                             type: 'verifyRoleChanged',
-                            displayName: 'Ignored Groups Changed',
+                            displayName: 'Verify Role Changed',
                             calculateType: 'nucleusSettingsUpdated',
                             color: NucleusColors.green,
                             emoji: "CONTROL.BLOCKTICK",
@@ -103,7 +104,12 @@
                 .setLabel(clicks ? "Click again to confirm" : "Reset role")
                 .setEmoji(getEmojiByName(clicks ? "TICKETS.ISSUE" : "CONTROL.CROSS", "id"))
                 .setStyle("DANGER")
-                .setDisabled(!role)
+                .setDisabled(!role),
+            new MessageButton()
+                .setCustomId("send")
+                .setLabel("Add verify button")
+                .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id"))
+                .setStyle("PRIMARY")
         ])]});
         let i;
         try {
@@ -117,23 +123,127 @@
                 await client.database.guilds.write(interaction.guild.id, null, ["verify.role", "verify.enabled"])
                 role = undefined;
             }
+        } else if (i.component.customId === "send") {
+            const verifyMessages = [
+                {label: "Verify", description: "Click the button below to get verified"},
+                {label: "Get verified", description: "To get access to the rest of the server, click the button below"},
+                {label: "Ready to verify?", description: "Click the button below to verify yourself"},
+            ]
+            while (true) {
+                await interaction.editReply({embeds: [new EmojiEmbed()
+                    .setTitle("Verify Button")
+                    .setDescription("Select a message template to send in this channel")
+                    .setFooter({text: role ? "" : "You do no have a verify role set so the button will not work."})
+                    .setStatus(role ? "Success" : "Warning")
+                    .setEmoji("GUILD.ROLES.CREATE")
+                ], components: [
+                    new MessageActionRow().addComponents([
+                        new MessageSelectMenu().setOptions(verifyMessages.map((t: {label: string, description: string, value?: string}, index) => {
+                            t.value = index.toString(); return t as {value: string, label: string, description: string}
+                        })).setCustomId("template").setMaxValues(1).setMinValues(1).setPlaceholder("Select a message template"),
+                    ]),
+                    new MessageActionRow().addComponents([
+                        new MessageButton()
+                            .setCustomId("back")
+                            .setLabel("Back")
+                            .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                            .setStyle("DANGER"),
+                        new MessageButton()
+                            .setCustomId("blank")
+                            .setLabel("Empty")
+                            .setStyle("SECONDARY"),
+                        new MessageButton()
+                            .setCustomId("custom")
+                            .setLabel("Custom")
+                            .setEmoji(getEmojiByName("TICKETS.OTHER", "id"))
+                            .setStyle("PRIMARY")
+                    ])
+                ]});
+                let i;
+                try {
+                    i = await m.awaitMessageComponent({time: 300000});
+                } catch(e) { break }
+                if (i.component.customId === "template") {
+                    i.deferUpdate()
+                    await interaction.channel.send({embeds: [new EmojiEmbed()
+                        .setTitle(verifyMessages[parseInt(i.values[0])].label)
+                        .setDescription(verifyMessages[parseInt(i.values[0])].description)
+                        .setStatus("Success")
+                        .setEmoji("CONTROL.BLOCKTICK")
+                    ], components: [new MessageActionRow().addComponents([new MessageButton()
+                        .setLabel("Verify")
+                        .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
+                        .setStyle("SUCCESS")
+                        .setCustomId("verifybutton")
+                    ])]});
+                    break
+                } else if (i.component.customId === "blank") {
+                    i.deferUpdate()
+                    await interaction.channel.send({components: [new MessageActionRow().addComponents([new MessageButton()
+                        .setLabel("Verify")
+                        .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
+                        .setStyle("SUCCESS")
+                        .setCustomId("verifybutton")
+                    ])]});
+                    break
+                } else if (i.component.customId === "custom") {
+                    await i.showModal(new Discord.Modal().setCustomId("modal").setTitle(`Enter embed details`).addComponents(
+                        new MessageActionRow<TextInputComponent>().addComponents(new TextInputComponent()
+                            .setCustomId("title")
+                            .setLabel("Title")
+                            .setMaxLength(256)
+                            .setRequired(true)
+                            .setStyle("SHORT")
+                        ),
+                        new MessageActionRow<TextInputComponent>().addComponents(new TextInputComponent()
+                            .setCustomId("description")
+                            .setLabel("Description")
+                            .setMaxLength(4000)
+                            .setRequired(true)
+                            .setStyle("PARAGRAPH")
+                        )
+                    ))
+                    await interaction.editReply({
+                        embeds: [new EmojiEmbed()
+                            .setTitle("Verify Button")
+                            .setDescription("Modal opened. If you can't see it, click back and try again.")
+                            .setStatus("Success")
+                            .setEmoji("GUILD.TICKET.OPEN")
+                        ], components: [new MessageActionRow().addComponents([new MessageButton()
+                            .setLabel("Back")
+                            .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
+                            .setStyle("PRIMARY")
+                            .setCustomId("back")
+                        ])]
+                    });
+                    let out;
+                    try {
+                        out = await modalInteractionCollector(m, (m) => m.channel.id === interaction.channel.id, (m) => m.customId === "modify")
+                    } catch (e) { break }
+                    if (out.fields) {
+                        let title = out.fields.getTextInputValue("title");
+                        let description = out.fields.getTextInputValue("description");
+                        await interaction.channel.send({embeds: [new EmojiEmbed()
+                            .setTitle(title)
+                            .setDescription(description)
+                            .setStatus("Success")
+                            .setEmoji("CONTROL.BLOCKTICK")
+                        ], components: [new MessageActionRow().addComponents([new MessageButton()
+                            .setLabel("Verify")
+                            .setEmoji(getEmojiByName("CONTROL.TICK", "id"))
+                            .setStyle("SUCCESS")
+                            .setCustomId("verifybutton")
+                        ])]});
+                        break
+                    } else { continue }
+                }
+            }
         } else {
-            break
+            i.deferUpdate()
+            break;
         }
     }
-    await interaction.editReply({embeds: [new EmojiEmbed()
-        .setTitle("Verify Role")
-        .setDescription(role ? `Your verify role is currently set to <@&${role}}>` : `You have not set a verify role`)
-        .setStatus("Success")
-        .setEmoji("GUILD.ROLE.CREATE")
-        .setFooter({text: "Message closed"})
-    ], components: [new MessageActionRow().addComponents([new MessageButton()
-        .setCustomId("clear")
-        .setLabel("Clear")
-        .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
-        .setStyle("SECONDARY")
-        .setDisabled(true),
-    ])]});
+    await interaction.editReply({embeds: [m.embeds[0].setFooter({text: "Message closed"})], components: []});
 }
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
diff --git a/src/commands/settings/welcome.ts b/src/commands/settings/welcome.ts
new file mode 100644
index 0000000..1a107ed
--- /dev/null
+++ b/src/commands/settings/welcome.ts
@@ -0,0 +1,209 @@
+import { LoadingEmbed } from './../../utils/defaultEmbeds.js';
+import Discord, { CommandInteraction, MessageActionRow, MessageButton, MessageSelectMenu } from "discord.js";
+import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
+import { WrappedCheck } from "jshaiku";
+import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import client from '../../utils/client.js';
+import confirmationMessage from '../../utils/confirmationMessage.js';
+import generateKeyValueList from '../../utils/generateKeyValueList.js';
+import { ChannelType } from 'discord-api-types';
+import getEmojiByName from '../../utils/getEmojiByName.js';
+
+const command = (builder: SlashCommandSubcommandBuilder) =>
+    builder
+    .setName("welcome")
+    .setDescription("Messages and roles sent or given when someone joins the server")
+    .addStringOption(option => option.setName("message").setDescription("The message to send when someone joins the server").setAutocomplete(true))
+    .addRoleOption(option => option.setName("role").setDescription("The role given when someone joins the server"))
+    .addRoleOption(option => option.setName("ping").setDescription("The role pinged when someone joins the server"))
+    .addChannelOption(option => option.setName("channel").setDescription("The channel the welcome message should be sent to").addChannelTypes([
+        ChannelType.GuildText, ChannelType.GuildNews
+    ]))
+
+const callback = async (interaction: CommandInteraction): Promise<any> => {
+    const { renderRole, renderChannel, log, NucleusColors, entry, renderUser } = client.logger
+    await interaction.reply({embeds: LoadingEmbed, fetchReply: true, ephemeral: true});
+    let m;
+    if (interaction.options.getRole("role") || interaction.options.getChannel("channel") || interaction.options.getString("message")) {
+        let role;
+        let ping;
+        let message = interaction.options.getString("message");
+        try {
+            role = interaction.options.getRole("role")
+            ping = interaction.options.getRole("ping")
+        } catch {
+            return await interaction.editReply({embeds: [new EmojiEmbed()
+                .setEmoji("GUILD.ROLES.DELETE")
+                .setTitle("Welcome Events")
+                .setDescription("The role you provided is not a valid role")
+                .setStatus("Danger")
+            ]})
+        }
+        let channel;
+        try {
+            channel = interaction.options.getChannel("channel")
+        } catch {
+            return await interaction.editReply({embeds: [new EmojiEmbed()
+                .setEmoji("GUILD.ROLES.DELETE")
+                .setTitle("Welcome Events")
+                .setDescription("The channel you provided is not a valid channel")
+                .setStatus("Danger")
+            ]})
+        }
+        role = role as Discord.Role
+        ping = ping as Discord.Role
+        channel = channel as Discord.TextChannel
+        let options = {}
+        if (role) options["role"] = renderRole(role)
+        if (ping) options["ping"] = renderRole(ping)
+        if (channel) options["channel"] = renderChannel(channel)
+        if (message) options["message"] = "\n> " + message
+        let confirmation = await new confirmationMessage(interaction)
+            .setEmoji("GUILD.ROLES.EDIT")
+            .setTitle("Welcome Events")
+            .setDescription(generateKeyValueList(options))
+            .setColor("Warning")
+            .setInverted(true)
+        .send(true)
+        if (confirmation.cancelled) return
+        if (confirmation.success) {
+            try {
+                let toChange = {}
+                if (role) toChange["welcome.role"] = role.id
+                if (ping) toChange["welcome.ping"] = ping.id
+                if (channel) toChange["welcome.channel"] = channel.id
+                if (message) toChange["welcome.message"] = message
+                await client.database.guilds.write(interaction.guild.id, toChange);
+                let list = {
+                    memberId: entry(interaction.user.id, `\`${interaction.user.id}\``),
+                    changedBy: entry(interaction.user.id, renderUser(interaction.user))
+                }
+                if (role) list["role"] = entry(role.id, renderRole(role))
+                if (ping) list["ping"] = entry(ping.id, renderRole(ping))
+                if (channel) list["channel"] = entry(channel.id, renderChannel(channel.id))
+                if (message) list["message"] = entry(message, `\`${message}\``)
+                try {
+                    let data = {
+                        meta:{
+                            type: 'welcomeSettingsUpdated',
+                            displayName: 'Welcome Settings Changed',
+                            calculateType: 'nucleusSettingsUpdated',
+                            color: NucleusColors.green,
+                            emoji: "CONTROL.BLOCKTICK",
+                            timestamp: new Date().getTime()
+                        },
+                        list: list,
+                        hidden: {
+                            guild: interaction.guild.id
+                        }
+                    }
+                    log(data);
+                } catch {}
+            } catch (e) {
+                console.log(e)
+                return interaction.editReply({embeds: [new EmojiEmbed()
+                    .setTitle("Welcome Events")
+                    .setDescription(`Something went wrong while updating welcome settings`)
+                    .setStatus("Danger")
+                    .setEmoji("GUILD.ROLES.DELETE")
+                ], components: []});
+            }
+        } else {
+            return interaction.editReply({embeds: [new EmojiEmbed()
+                .setTitle("Welcome Events")
+                .setDescription(`No changes were made`)
+                .setStatus("Success")
+                .setEmoji("GUILD.ROLES.CREATE")
+            ], components: []});
+        }
+    }
+    let lastClicked = null
+    while (true) {
+        let config = await client.database.guilds.read(interaction.guild.id)
+        m = await interaction.editReply({embeds: [new EmojiEmbed()
+            .setTitle("Welcome Events")
+            .setDescription(
+                `**Message:** ${config.welcome.message ? `\n> ${config.welcome.message}` : "*None set*"}\n` +
+                `**Role:** ${config.welcome.role ? renderRole(await interaction.guild.roles.fetch(config.welcome.role)) : "*None set*"}\n` +
+                `**Ping:** ${config.welcome.ping ? renderRole(await interaction.guild.roles.fetch(config.welcome.ping)) : "*None set*"}\n` +
+                `**Channel:** ${config.welcome.channel ? (config.welcome.channel == "dm" ? "DM" : renderChannel(await interaction.guild.channels.fetch(config.welcome.channel))) : "*None set*"}`
+            )
+            .setStatus("Success")
+            .setEmoji("CHANNEL.TEXT.CREATE")
+        ], components: [
+            new MessageActionRow().addComponents([
+                new MessageButton()
+                    .setLabel(lastClicked == "clear-message" ? "Click again to confirm" : "Clear Message")
+                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+                    .setCustomId("clear-message")
+                    .setDisabled(!config.welcome.message)
+                    .setStyle("DANGER"),
+                new MessageButton()
+                    .setLabel(lastClicked == "clear-role" ? "Click again to confirm" : "Clear Role")
+                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+                    .setCustomId("clear-role")
+                    .setDisabled(!config.welcome.role)
+                    .setStyle("DANGER"),
+                new MessageButton()
+                    .setLabel(lastClicked == "clear-ping" ? "Click again to confirm" : "Clear Ping")
+                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+                    .setCustomId("clear-ping")
+                    .setDisabled(!config.welcome.ping)
+                    .setStyle("DANGER"),
+                new MessageButton()
+                    .setLabel(lastClicked == "clear-channel" ? "Click again to confirm" : "Clear Channel")
+                    .setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
+                    .setCustomId("clear-channel")
+                    .setDisabled(!config.welcome.channel)
+                    .setStyle("DANGER"),
+                new MessageButton()
+                    .setLabel("Set Channel to DM")
+                    .setCustomId("set-channel-dm")
+                    .setDisabled(config.welcome.channel == "dm")
+                    .setStyle("SECONDARY")
+            ])
+        ]})
+        let i;
+        try {
+            i = await m.awaitMessageComponent({ time: 300000 });
+        } catch (e) {
+            break
+        }
+        i.deferUpdate()
+        if (i.customId == "clear-message") {
+            if (lastClicked == "clear-message") {
+                await client.database.guilds.write(interaction.guild.id, {"welcome.message": null});
+                lastClicked = null
+            } else { lastClicked = "clear-message" }
+        } else if (i.customId == "clear-role") {
+            if (lastClicked == "clear-role") {
+                await client.database.guilds.write(interaction.guild.id, {"welcome.role": null});
+                lastClicked = null
+            } else { lastClicked = "clear-role" }
+        } else if (i.customId == "clear-ping") {
+            if (lastClicked == "clear-ping") {
+                await client.database.guilds.write(interaction.guild.id, {"welcome.ping": null});
+                lastClicked = null
+            } else { lastClicked = "clear-ping" }
+        } else if (i.customId == "clear-channel") {
+            if (lastClicked == "clear-channel") {
+                await client.database.guilds.write(interaction.guild.id, {"welcome.channel": null});
+                lastClicked = null
+            } else { lastClicked = "clear-channel" }
+        } else if (i.customId == "set-channel-dm") {
+            await client.database.guilds.write(interaction.guild.id, {"welcome.channel": "dm"});
+            lastClicked = null
+        }
+    }
+    await interaction.editReply({embeds: [m.embeds[0].setFooter({text: "Message closed"})], components: []});
+}
+
+const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
+    let member = (interaction.member as Discord.GuildMember)
+    if (!member.permissions.has("MANAGE_GUILD")) throw "You must have the *Manage Server* permission to use this command"
+    return true;
+}
+
+export { command };
+export { callback };
+export { check };
\ No newline at end of file
diff --git a/src/commands/tag.ts b/src/commands/tag.ts
index 03b13c6..70d5a75 100644
--- a/src/commands/tag.ts
+++ b/src/commands/tag.ts
@@ -9,7 +9,7 @@
     .setDescription("Get and manage the servers tags")
     .addStringOption(o => o.setName("tag").setDescription("The tag to get").setAutocomplete(true).setRequired(true))
 
-const callback = async (interaction: CommandInteraction) => {
+const callback = async (interaction: CommandInteraction): Promise<any> => {
     const config = await client.database.guilds.read(interaction.guild.id)
     const tags = config.getKey("tags")
     const tag = tags[interaction.options.getString("tag")]
diff --git a/src/commands/tags/create.ts b/src/commands/tags/create.ts
index b0a278d..4615def 100644
--- a/src/commands/tags/create.ts
+++ b/src/commands/tags/create.ts
@@ -79,7 +79,7 @@
 
 const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => {
     let member = (interaction.member as Discord.GuildMember)
-    if (!member.permissions.has("MANAGE_MESSAGES")) throw "You must have the *Manage Server* permission to use this command"
+    if (!member.permissions.has("MANAGE_MESSAGES")) throw "You must have the *Manage Messages* permission to use this command"
     return true;
 }
 
diff --git a/src/config/default.json b/src/config/default.json
index 84a4de0..3976812 100644
--- a/src/config/default.json
+++ b/src/config/default.json
@@ -29,7 +29,8 @@
     },
     "welcome": {
         "enabled": false,
-        "welcomeRole": null,
+        "role": null,
+        "ping": null,
         "channel": null,
         "message": null
     },
diff --git a/src/config/format.js b/src/config/format.js
index 16519b5..bdd3fdb 100644
--- a/src/config/format.js
+++ b/src/config/format.js
@@ -61,7 +61,7 @@
             if (walkthrough) {
                 switch (key) {
                     case "enableDevelopment": {
-                        json[key] = (await getInput("\x1b[36mEnable development mode? \x1b[0m(\x1b[32mY\x1b[0m/\x1b[31mn\x1b[0m) > ") || "Y").toLowerCase() === "y"; break;
+                        json[key] = (await getInput("\x1b[36mEnable development mode? This redisters commands in a single server making it easier to test\x1b[0m(\x1b[32mY\x1b[0m/\x1b[31mn\x1b[0m) > ") || "Y").toLowerCase() === "y"; break;
                     } case "owners": {
                         let chosen = "!";
                         let toWrite = []
@@ -81,7 +81,12 @@
     if (walkthrough && !json.mongoUrl) json.mongoUrl = "mongodb://127.0.0.1:27017"
     if (!json.mongoUrl.endsWith("/")) json.mongoUrl += "/";
     if (!json.baseUrl.endsWith("/")) json.baseUrl += "/";
-    let hosts = fs.readFileSync('/etc/hosts', 'utf8').toString().split("\n");
+    let hosts;
+    try {
+        hosts = fs.readFileSync('/etc/hosts', 'utf8').toString().split("\n");
+    } catch (e) {
+        return console.log("\x1b[31m⚠ No /etc/hosts found. Please ensure the file exists and is readable. (Windows is not supported, Mac and Linux users should not experience this error)")
+    }
     let localhost = hosts.find(line => line.split(" ")[1] === "localhost");
     if (localhost) { localhost = localhost.split(" ")[0]; }
     else { localhost = "127.0.0.1" }
diff --git a/src/events/commandError.ts b/src/events/commandError.ts
index 6d3672e..8edb480 100644
--- a/src/events/commandError.ts
+++ b/src/events/commandError.ts
@@ -5,7 +5,7 @@
 export async function callback(client, interaction, error) {
     if (interaction.replied || interaction.deferred) {
         await interaction.followUp({embeds: [new EmojiEmbed()
-            .setTitle("Something went")
+            .setTitle("Something went wrong")
             .setDescription(error.message ?? error.toString())
             .setStatus("Danger")
             .setEmoji("CONTROL.BLOCKCROSS")
diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts
index eaf4fea..a4bb8c3 100644
--- a/src/events/interactionCreate.ts
+++ b/src/events/interactionCreate.ts
@@ -16,8 +16,16 @@
     return fuse.slice(0, 25).map(option => ({name: option.item, value: option.item}))
 }
 
-const validReplacements = ["serverName", "memberCount", "memberCount:bots", "memberCount:humans"]
 function generateStatsChannelAutocomplete(typed) {
+    const validReplacements = ["serverName", "memberCount", "memberCount:bots", "memberCount:humans"]
+    let autocompletions = []
+    const beforeLastOpenBracket = typed.match(/(.*){[^{}]{0,15}$/)
+    if (beforeLastOpenBracket !== null) { for (let replacement of validReplacements) { autocompletions.push(`${beforeLastOpenBracket[1]} {${replacement}}`) } }
+    else { for (let replacement of validReplacements) { autocompletions.push(`${typed} {${replacement}}`) } }
+    return getAutocomplete(typed, autocompletions)
+}
+function generateWelcomeMessageAutocomplete(typed) {
+    const validReplacements = ["serverName", "memberCount", "memberCount:bots", "memberCount:humans", "member:mention", "member:name"]
     let autocompletions = []
     const beforeLastOpenBracket = typed.match(/(.*){[^{}]{0,15}$/)
     if (beforeLastOpenBracket !== null) { for (let replacement of validReplacements) { autocompletions.push(`${beforeLastOpenBracket[1]} {${replacement}}`) } }
@@ -39,6 +47,7 @@
         switch (`${interaction.commandName} ${interaction.options.getSubcommandGroup(false)} ${interaction.options.getSubcommand(false)}`) {
             case `tag null null`: { return interaction.respond(getAutocomplete(interaction.options.getString("tag"), (await tagAutocomplete(interaction)))) }
             case `settings null stats`: { return interaction.respond(generateStatsChannelAutocomplete(interaction.options.getString("name"))) }
+            case `settings null welcome`: { return interaction.respond(generateWelcomeMessageAutocomplete(interaction.options.getString("message"))) }
         }
     }
 }
diff --git a/src/reflex/verify.ts b/src/reflex/verify.ts
index b95d5db..bc7aa4f 100644
--- a/src/reflex/verify.ts
+++ b/src/reflex/verify.ts
@@ -17,6 +17,7 @@
     if ((!config.verify.enabled ) || (!config.verify.role)) return interaction.editReply({embeds: [new EmojiEmbed()
         .setTitle("Verify")
         .setDescription(`Verify is not enabled on this server`)
+        .setFooter({text: interaction.member.permissions.has("MANAGE_GUILD") ? "You can enable it by running /settings verify" : ""})
         .setStatus("Danger")
         .setEmoji("CONTROL.BLOCKCROSS")
     ], ephemeral: true, fetchReply: true});
diff --git a/src/reflex/welcome.ts b/src/reflex/welcome.ts
index 5b80fbd..9be71d5 100644
--- a/src/reflex/welcome.ts
+++ b/src/reflex/welcome.ts
@@ -1,41 +1,39 @@
-import log from '../utils/log.js'
 import convertCurlyBracketString from '../utils/convertCurlyBracketString.js'
 import client from '../utils/client.js';
+import EmojiEmbed from '../utils/generateEmojiEmbed.js';
 
 export async function callback(_, member) {
     if (member.bot) return
     let config = await client.database.guilds.read(member.guild.id);
     if (!config.welcome.enabled) return
 
-    if (!config.welcome.verificationRequired.role) {
-        if (config.welcome.welcomeRole) {
-            try {
-                await member.roles.add(config.welcome.welcomeRole)
-            } catch (err) {
-                console.error(err)
-            }
-        }
-    }
-
-    if (!config.welcome.verificationRequired.message && config.welcome.channel) {
+    if (config.welcome.channel) {
         let string = config.welcome.message
         if (string) {
             string = await convertCurlyBracketString(string, member.id, member.displayName, member.guild.name, member.guild.members)
-
             if (config.welcome.channel === 'dm') {
                 try {
-                    await member.send(string)
-                } catch (err) {
-                    console.error(err)
-                }
+                    await member.send({
+                        embeds: [new EmojiEmbed()
+                            .setDescription(string)
+                            .setStatus('Success')
+                        ]
+                    })
+                } catch {}
             } else {
-                let channel = await member.client.channels.fetch(config.welcome.channel)
+                let channel = await member.guild.channels.fetch(config.welcome.channel)
                 if (channel.guild.id !== member.guild.id) return
                 if (!channel) return
                 try {
-                    await channel.send(string)
+                    await channel.send({
+                        embeds: [new EmojiEmbed()
+                            .setDescription(string)
+                            .setStatus('Success')
+                        ],
+                        content: (config.welcome.ping ? `<@${config.welcome.ping}>` : '') + `<@${member.id}>`
+                    })
                 } catch (err) {
-                    console.error(err)
+                    console.error(err) // SEN
                 }
             }
         }
diff --git a/src/utils/database.ts b/src/utils/database.ts
index 2f04198..8a5ca6f 100644
--- a/src/utils/database.ts
+++ b/src/utils/database.ts
@@ -49,7 +49,6 @@
         let out = {$set: {}, $unset: {}}
         if (set) out["$set"] = set;
         if (unset.length) out["$unset"] = uo;
-        console.log(out)
         await this.guilds.updateOne({ id: guild }, out, { upsert: true });
     }
 
@@ -211,7 +210,8 @@
             message: boolean,
             role: string | null
         },
-        welcomeRole: string | null,
+        role: string | null,
+        ping: string | null,
         channel: string | null,
         message: string | null,
     }
diff --git a/src/utils/defaultEmbeds.ts b/src/utils/defaultEmbeds.ts
index 0f226da..e799307 100644
--- a/src/utils/defaultEmbeds.ts
+++ b/src/utils/defaultEmbeds.ts
@@ -4,5 +4,6 @@
     .setTitle("Loading")
     .setDescription("One moment...")
     .setStatus("Danger")
-    .setEmoji("NUCLEUS.LOADING")]
+    .setEmoji("NUCLEUS.LOADING")
+]
 
diff --git a/src/utils/log.ts b/src/utils/log.ts
index 22da837..0c6aa89 100644
--- a/src/utils/log.ts
+++ b/src/utils/log.ts
@@ -29,7 +29,7 @@
     entry(value, displayValue) {
         return { value: value, displayValue: displayValue }
     }
-    renderChannel(channel: Discord.GuildChannel | Discord.ThreadChannel | Discord.NewsChannel) {
+    renderChannel(channel: Discord.GuildChannel | Discord.ThreadChannel) {
         return `${channel.name} [<#${channel.id}>]`;
     }
     renderRole(role: Discord.Role) {
diff --git a/tsconfig.json b/tsconfig.json
index e38a3bc..0b5e28f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,6 +11,6 @@
         "moduleResolution": "node",
         "skipLibCheck": true
     },
-    "include": ["src/**/*", "isntaller.js"],
+    "include": ["src/**/*", "installer.js"],
     "exclude": []
 }
\ No newline at end of file