Merge pull request #10 from r-Overwatch2/release/1.0.0-beta.3
Release/1.0.0 beta.3
This commit was merged in pull request #10.
	This commit is contained in:
		
							
								
								
									
										20
									
								
								Changelog.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								Changelog.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					# Changelog
 | 
				
			||||||
 | 
					This file is used to list changes made to this software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## V1.0.0 [`unreleased`]
 | 
				
			||||||
 | 
					_Current development version: **1.0.0-beta.3**_
 | 
				
			||||||
 | 
					### Features
 | 
				
			||||||
 | 
					- /info
 | 
				
			||||||
 | 
					- /logchannel
 | 
				
			||||||
 | 
					- /blocklist get
 | 
				
			||||||
 | 
					- /blocklist add
 | 
				
			||||||
 | 
					- /blocklist remove
 | 
				
			||||||
 | 
					- /preservesettings
 | 
				
			||||||
 | 
					- /showblocklist
 | 
				
			||||||
 | 
					- /showsettings
 | 
				
			||||||
 | 
					- Data will be deleted by default when the bot leaves the server
 | 
				
			||||||
 | 
					- Server admins can change the behaviour of the bot when it leaves the server to keep the data persistent
 | 
				
			||||||
 | 
					- Scans for blocked words in channel names and renames channels to "CENSORED" if found
 | 
				
			||||||
 | 
					- When settings are changed or channels are censored, notifications to a logchannel can be enabled
 | 
				
			||||||
 | 
					- CLI to change settings and get information during runtime by attaching to the apps docker container
 | 
				
			||||||
 | 
					- API for automated uptime checks to prevent the bot from going offline unnoticed
 | 
				
			||||||
							
								
								
									
										20
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								README.md
									
									
									
									
									
								
							@@ -2,12 +2,12 @@
 | 
				
			|||||||
A Discord bot that checks Discord channel names for banned words and prevents renaming of them
 | 
					A Discord bot that checks Discord channel names for banned words and prevents renaming of them
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Commands
 | 
					## Commands
 | 
				
			||||||
### /logchanel [channel?]
 | 
					### /logchanel [channel?] `Permission: MANAGE_GUILD`
 | 
				
			||||||
Sets the channel where the bot will log if a channel meets the banned word criteria. If channel is omitted, the log channel will be disabled.
 | 
					Sets the channel where the bot will log if a channel meets the banned word criteria. If channel is omitted, the log channel will be disabled.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### /blocklist
 | 
					### /blocklist `Permission: MANAGE_GUILD`
 | 
				
			||||||
#### /blocklist get
 | 
					#### /blocklist get
 | 
				
			||||||
Gets the global and server specific banned word list and returns it
 | 
					Returns the global and server specific banned word list and returns it (Same behaviour as /showblocklist)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### /blocklist add [word]
 | 
					#### /blocklist add [word]
 | 
				
			||||||
Adds the word to the server specific blocklist
 | 
					Adds the word to the server specific blocklist
 | 
				
			||||||
@@ -15,9 +15,21 @@ Adds the word to the server specific blocklist
 | 
				
			|||||||
#### /blocklist remove [word]
 | 
					#### /blocklist remove [word]
 | 
				
			||||||
Removes the word from the server specific blocklist
 | 
					Removes the word from the server specific blocklist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### /info
 | 
					### /info `Permission: EVERYONE`
 | 
				
			||||||
Returns general information about the bot and the servers stats
 | 
					Returns general information about the bot and the servers stats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### /preservesettings `Permission: ADMINISTRATOR`
 | 
				
			||||||
 | 
					Changes the behaviour when the bot leaves the server.
 | 
				
			||||||
 | 
					Options are:
 | 
				
			||||||
 | 
					- Keep settings persistent even if the bot leaves
 | 
				
			||||||
 | 
					- Delete setting when the bot leaves the server [default]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### /showblocklist `Permission: MANAGE_CHANNELS`
 | 
				
			||||||
 | 
					Returns the global and server specific banned word list and returns it
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### /showsettings `Permission: MANAGE_GUILD`
 | 
				
			||||||
 | 
					Returns the current settings for the server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Environment variables
 | 
					## Environment variables
 | 
				
			||||||
| Name             | Description                                                                     | Required | Example             |
 | 
					| Name             | Description                                                                     | Required | Example             |
 | 
				
			||||||
| :--------------- | :------------------------------------------------------------------------------ | :------: | :------------------ |
 | 
					| :--------------- | :------------------------------------------------------------------------------ | :------: | :------------------ |
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "eu.astrogd.white-leopard",
 | 
					  "name": "eu.astrogd.white-leopard",
 | 
				
			||||||
  "version": "1.0.0-beta.2",
 | 
					  "version": "1.0.0-beta.3",
 | 
				
			||||||
  "description": "A Discord bot that checks channel names for blacklisted words and reverts the changes if necessary",
 | 
					  "description": "A Discord bot that checks channel names for blacklisted words and reverts the changes if necessary",
 | 
				
			||||||
  "main": "build/index.js",
 | 
					  "main": "build/index.js",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,7 @@ export default async function execute(args: string[]) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            console.log(`Guild ${args[1]}:
 | 
					            console.log(`Guild ${args[1]}:
 | 
				
			||||||
    - Premium: ${isPremium ? `ACTIVE for ${moment(settings.isPremiumUntil).fromNow(true)}` : "INACTIVE"}
 | 
					    - Premium: ${isPremium ? `ACTIVE for ${moment(settings.isPremiumUntil).fromNow(true)}` : "INACTIVE"}
 | 
				
			||||||
 | 
					    - Preserve Settings: ${settings.preserveDataOnGuildLeave ? "ENABLED" : "DISABLED"}
 | 
				
			||||||
    - Logchannel: ${settings.notificationChannelID ? `ENABLED (${settings.notificationChannelID})` : "DISABLED"}
 | 
					    - Logchannel: ${settings.notificationChannelID ? `ENABLED (${settings.notificationChannelID})` : "DISABLED"}
 | 
				
			||||||
    - blocked Words: ${wordCount}`);
 | 
					    - blocked Words: ${wordCount}`);
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
@@ -124,6 +125,21 @@ export default async function execute(args: string[]) {
 | 
				
			|||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case "delete": {
 | 
				
			||||||
 | 
					            if (!args[1]) return printHelp();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await database.getRepository(GuildSetting).delete({
 | 
				
			||||||
 | 
					                id: args[1]
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await database.getRepository(Badword).delete({
 | 
				
			||||||
 | 
					                guildID: args[1]
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            console.log(`Deleted all data for guild ${args[1]}`);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        default: {
 | 
					        default: {
 | 
				
			||||||
            printHelp();
 | 
					            printHelp();
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
@@ -137,7 +153,8 @@ function printHelp() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
guild info [GUILDID]
 | 
					guild info [GUILDID]
 | 
				
			||||||
guild setPremium [GUILDID] [YYYY-MM-DD or NULL]
 | 
					guild setPremium [GUILDID] [YYYY-MM-DD or NULL]
 | 
				
			||||||
guild words [get|add|remove|clear]`);
 | 
					guild words [get|add|remove|clear]
 | 
				
			||||||
 | 
					guild delete [GUILDID]`);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function printWordHelp() {
 | 
					function printWordHelp() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import { runCleanup } from "../service";
 | 
				
			||||||
import client from "./index";
 | 
					import client from "./index";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const token = process.env["TOKEN"];
 | 
					const token = process.env["TOKEN"];
 | 
				
			||||||
@@ -7,4 +8,6 @@ client.login(token);
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
client.on("ready", () => {
 | 
					client.on("ready", () => {
 | 
				
			||||||
    console.log(`Connected to Discord API. Bot account is ${client.user?.tag} (${client.user?.id})`);
 | 
					    console.log(`Connected to Discord API. Bot account is ${client.user?.tag} (${client.user?.id})`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    runCleanup();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -5,6 +5,7 @@ import { getGuildSetting, isPremiumActive } from "../tools/data";
 | 
				
			|||||||
import getDefaultEmbed, { getFailedEmbed, getSuccessEmbed } from "../tools/defaultEmbeds";
 | 
					import getDefaultEmbed, { getFailedEmbed, getSuccessEmbed } from "../tools/defaultEmbeds";
 | 
				
			||||||
import { getGuildChannel } from "../tools/discord";
 | 
					import { getGuildChannel } from "../tools/discord";
 | 
				
			||||||
import { Color, Emoji } from "../tools/design";
 | 
					import { Color, Emoji } from "../tools/design";
 | 
				
			||||||
 | 
					import { execute as showBlocklistRunner } from "./showblocklist";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const builder = new SlashCommandBuilder();
 | 
					const builder = new SlashCommandBuilder();
 | 
				
			||||||
builder.setName("blocklist");
 | 
					builder.setName("blocklist");
 | 
				
			||||||
@@ -51,24 +52,7 @@ async function execute(interaction: ChatInputCommandInteraction): Promise<void>
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    switch (interaction.options.getSubcommand(true)) {
 | 
					    switch (interaction.options.getSubcommand(true)) {
 | 
				
			||||||
        case "get": {
 | 
					        case "get": {
 | 
				
			||||||
            const guildBadWords = await database.getRepository(Badword).find({
 | 
					            await showBlocklistRunner(interaction);
 | 
				
			||||||
                select: {
 | 
					 | 
				
			||||||
                    value: true
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                where: {
 | 
					 | 
				
			||||||
                    guildID: interaction.guildId
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            const globalBadWords = await database.getRepository(Badword).find({
 | 
					 | 
				
			||||||
                where: {
 | 
					 | 
				
			||||||
                    guildID: IsNull()
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            interaction.reply({
 | 
					 | 
				
			||||||
                content: `\`\`\`Global bad word list\`\`\`\n||${globalBadWords.map((word) => word.value).reduce((prev, next) => prev + ", " + next, "").slice(2)} ||\n\`\`\`Local server bad word list (${guildBadWords.length}/${isPremium ? 100 : 10})\`\`\`\n||${guildBadWords.map((word) => word.value).reduce((prev, next) => prev + ", " + next, "").slice(2)} ||`,
 | 
					 | 
				
			||||||
                ephemeral: true
 | 
					 | 
				
			||||||
            }).catch();
 | 
					 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,19 @@
 | 
				
			|||||||
import * as notification from "./notification";
 | 
					import * as notification from "./logchannel";
 | 
				
			||||||
import * as blocklist from "./blocklist";
 | 
					import * as blocklist from "./blocklist";
 | 
				
			||||||
import * as info from "./info";
 | 
					import * as info from "./info";
 | 
				
			||||||
 | 
					import * as preserveSettings from "./preserveSettings";
 | 
				
			||||||
 | 
					import * as showBlocklist from "./showblocklist";
 | 
				
			||||||
 | 
					import * as showSettings from "./showSettings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const array = [notification.builder.toJSON(), blocklist.builder.toJSON(), info.builder.toJSON()];
 | 
					const commands = [];
 | 
				
			||||||
 | 
					commands.push(notification);
 | 
				
			||||||
 | 
					commands.push(blocklist);
 | 
				
			||||||
 | 
					commands.push(info);
 | 
				
			||||||
 | 
					commands.push(preserveSettings);
 | 
				
			||||||
 | 
					commands.push(showBlocklist);
 | 
				
			||||||
 | 
					commands.push(showSettings);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const array = commands.map((command) => command.builder.toJSON());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export {
 | 
					export {
 | 
				
			||||||
    array
 | 
					    array
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,10 @@
 | 
				
			|||||||
import { ChatInputCommandInteraction, Collection, Events, SlashCommandBuilder } from "discord.js";
 | 
					import { ChatInputCommandInteraction, Collection, Events, SlashCommandBuilder } from "discord.js";
 | 
				
			||||||
import * as notification from "./notification";
 | 
					import * as notification from "./logchannel";
 | 
				
			||||||
import * as blocklist from "./blocklist";
 | 
					import * as blocklist from "./blocklist";
 | 
				
			||||||
import * as info from "./info";
 | 
					import * as info from "./info";
 | 
				
			||||||
 | 
					import * as preserveSettings from "./preserveSettings";
 | 
				
			||||||
 | 
					import * as showBlocklist from "./showblocklist";
 | 
				
			||||||
 | 
					import * as showSettings from "./showSettings";
 | 
				
			||||||
import client from "../client";
 | 
					import client from "../client";
 | 
				
			||||||
import getDefaultEmbed from "../tools/defaultEmbeds";
 | 
					import getDefaultEmbed from "../tools/defaultEmbeds";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -9,6 +12,9 @@ const commands = new Collection<string, { builder: SlashCommandBuilder, execute:
 | 
				
			|||||||
commands.set(notification.builder.name, notification);
 | 
					commands.set(notification.builder.name, notification);
 | 
				
			||||||
commands.set(blocklist.builder.name, blocklist);
 | 
					commands.set(blocklist.builder.name, blocklist);
 | 
				
			||||||
commands.set(info.builder.name, info);
 | 
					commands.set(info.builder.name, info);
 | 
				
			||||||
 | 
					commands.set(preserveSettings.builder.name, preserveSettings);
 | 
				
			||||||
 | 
					commands.set(showBlocklist.builder.name, showBlocklist);
 | 
				
			||||||
 | 
					commands.set(showSettings.builder.name, showSettings);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
client.on(Events.InteractionCreate, async (interaction) => {
 | 
					client.on(Events.InteractionCreate, async (interaction) => {
 | 
				
			||||||
    if (!interaction.isChatInputCommand()) return;
 | 
					    if (!interaction.isChatInputCommand()) return;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@ const builder = new SlashCommandBuilder();
 | 
				
			|||||||
builder.setName("logchannel");
 | 
					builder.setName("logchannel");
 | 
				
			||||||
builder.setDescription("Configures the log channel");
 | 
					builder.setDescription("Configures the log channel");
 | 
				
			||||||
builder.setDMPermission(false);
 | 
					builder.setDMPermission(false);
 | 
				
			||||||
builder.setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels);
 | 
					builder.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild);
 | 
				
			||||||
builder.addChannelOption((option) => {
 | 
					builder.addChannelOption((option) => {
 | 
				
			||||||
    option.addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement);
 | 
					    option.addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement);
 | 
				
			||||||
    option.setName("channel");
 | 
					    option.setName("channel");
 | 
				
			||||||
							
								
								
									
										66
									
								
								src/commands/preserveSettings.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/commands/preserveSettings.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					import { SlashCommandBuilder, ChatInputCommandInteraction, PermissionFlagsBits } from "discord.js";
 | 
				
			||||||
 | 
					import { database, GuildSetting } from "../data";
 | 
				
			||||||
 | 
					import { getGuildSetting } from "../tools/data";
 | 
				
			||||||
 | 
					import getDefaultEmbed, { getSuccessEmbed } from "../tools/defaultEmbeds";
 | 
				
			||||||
 | 
					import { Emoji } from "../tools/design";
 | 
				
			||||||
 | 
					import { getGuildChannel } from "../tools/discord";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const builder = new SlashCommandBuilder();
 | 
				
			||||||
 | 
					builder.setName("preservesettings");
 | 
				
			||||||
 | 
					builder.setDescription("Sets if the bot should save the server settings and blocklist if it leaves the server or delete it");
 | 
				
			||||||
 | 
					builder.addStringOption((option) => {
 | 
				
			||||||
 | 
					    option.addChoices({
 | 
				
			||||||
 | 
					        name: "Keep data when bot leaves the server",
 | 
				
			||||||
 | 
					        value: "keep"
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        name: "Delete data when bot leaves the server",
 | 
				
			||||||
 | 
					        value: "delete"
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    option.setName("behaviour");
 | 
				
			||||||
 | 
					    option.setDescription("How the bot behaves when leaving the server");
 | 
				
			||||||
 | 
					    option.setRequired(true);
 | 
				
			||||||
 | 
					    return option;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					builder.setDMPermission(false);
 | 
				
			||||||
 | 
					builder.setDefaultMemberPermissions(PermissionFlagsBits.Administrator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function execute(interaction: ChatInputCommandInteraction): Promise<void> {
 | 
				
			||||||
 | 
					    if (!interaction.inGuild()) throw new Error("Command was executed outside guild context");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const settings = await getGuildSetting(interaction.guildId);
 | 
				
			||||||
 | 
					    const option = interaction.options.getString("behaviour", true).toLowerCase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (option !== "keep" && option !== "delete") throw new TypeError(`option "behaviour" expected to be of type "keep" | "delete" but was "${option}"`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    settings.preserveDataOnGuildLeave = option === "keep";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await database.getRepository(GuildSetting).save(settings);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const embed = getSuccessEmbed();
 | 
				
			||||||
 | 
					    embed.setDescription(`Preserve settings on server leave is now ${settings.preserveDataOnGuildLeave ? "ENABLED" : "DISABLED"}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    interaction.reply({
 | 
				
			||||||
 | 
					        embeds: [embed],
 | 
				
			||||||
 | 
					        ephemeral: true
 | 
				
			||||||
 | 
					    }).catch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!settings.notificationChannelID) return;
 | 
				
			||||||
 | 
					    const logChannel = await getGuildChannel(interaction.guildId, settings.notificationChannelID);
 | 
				
			||||||
 | 
					    if (!logChannel || !logChannel.isTextBased()) return;
 | 
				
			||||||
 | 
					    const logEmbed = getDefaultEmbed();
 | 
				
			||||||
 | 
					    logEmbed.setTitle(`${Emoji.SETTINGS} Settings changed`);
 | 
				
			||||||
 | 
					    logEmbed.setDescription(`Preserve settings on server leave is now ${settings.preserveDataOnGuildLeave ? "ENABLED" : "DISABLED"}`);
 | 
				
			||||||
 | 
					    logEmbed.addFields({
 | 
				
			||||||
 | 
					        name: "This action was performed by",
 | 
				
			||||||
 | 
					        value: `${interaction.user.tag} (${interaction.user.id})`
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logChannel.send({
 | 
				
			||||||
 | 
					        embeds: [logEmbed]
 | 
				
			||||||
 | 
					    }).catch();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					    builder,
 | 
				
			||||||
 | 
					    execute
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										61
									
								
								src/commands/showSettings.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/commands/showSettings.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					import { ChatInputCommandInteraction, PermissionFlagsBits, SlashCommandBuilder } from "discord.js";
 | 
				
			||||||
 | 
					import { Badword, database } from "../data";
 | 
				
			||||||
 | 
					import { getGuildSetting, isPremiumActive } from "../tools/data";
 | 
				
			||||||
 | 
					import getDefaultEmbed from "../tools/defaultEmbeds";
 | 
				
			||||||
 | 
					import moment from "moment";
 | 
				
			||||||
 | 
					import { Color, Emoji } from "../tools/design";
 | 
				
			||||||
 | 
					import { getGuildChannel } from "../tools/discord";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const builder = new SlashCommandBuilder();
 | 
				
			||||||
 | 
					builder.setName("showsettings");
 | 
				
			||||||
 | 
					builder.setDescription("Show the current settings of this guild");
 | 
				
			||||||
 | 
					builder.setDMPermission(false);
 | 
				
			||||||
 | 
					builder.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function execute(interaction: ChatInputCommandInteraction): Promise<void> {
 | 
				
			||||||
 | 
					    if (!interaction.inGuild()) throw new Error("Interaction was performed outside guild context");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const settings = await getGuildSetting(interaction.guildId);
 | 
				
			||||||
 | 
					    const isPremium = await isPremiumActive(settings.isPremiumUntil);
 | 
				
			||||||
 | 
					    const wordCount = await database.getRepository(Badword).count({
 | 
				
			||||||
 | 
					        where: {
 | 
				
			||||||
 | 
					            guildID: interaction.guildId
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const logChannel = settings.notificationChannelID ? await getGuildChannel(interaction.guildId, settings.notificationChannelID) : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const embed = getDefaultEmbed();
 | 
				
			||||||
 | 
					    embed.setTitle(`Settings from guild ${interaction.guild?.name || ""} (${interaction.guildId})`);
 | 
				
			||||||
 | 
					    embed.setDescription(isPremium ? `${Emoji.PREMIUM} your subscription ends in ${moment(settings.isPremiumUntil).fromNow(true)}` : `Consider Premium status to get an increased blocklist`);
 | 
				
			||||||
 | 
					    embed.setColor(isPremium ? Color.PREMIUM_ORANGE : Color.INFORMING_BLUE);
 | 
				
			||||||
 | 
					    embed.addFields({
 | 
				
			||||||
 | 
					        name: "Premium",
 | 
				
			||||||
 | 
					        value: isPremium ? `${Emoji.PREMIUM} active` : `${Emoji.SWITCH_OFF} inactive`,
 | 
				
			||||||
 | 
					        inline: true
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        name: "Logchannel",
 | 
				
			||||||
 | 
					        value: logChannel && logChannel.isTextBased() ? `<#${logChannel.id}>` : "Not configured",
 | 
				
			||||||
 | 
					        inline: true
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        name: "Words in Blocklist",
 | 
				
			||||||
 | 
					        value: `${wordCount}/${isPremium ? "100" : "10"}`,
 | 
				
			||||||
 | 
					        inline: true
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        name: `Preserve data on server leave is ${settings.preserveDataOnGuildLeave ? "active" : "inactive"}`,
 | 
				
			||||||
 | 
					        value: settings.preserveDataOnGuildLeave ? "Your settings will be saved even if the bot gets kicked" : "Your settings will be deleted as soon as the bot leaves this server"
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        name: `${Emoji.WAVING} Found a bug? Want to request a feature? Say hello?`,
 | 
				
			||||||
 | 
					        value: "Join the support server at https://go.astrogd.eu/discord"
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    interaction.reply({
 | 
				
			||||||
 | 
					        embeds: [embed],
 | 
				
			||||||
 | 
					        ephemeral: true
 | 
				
			||||||
 | 
					    }).catch();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					    builder,
 | 
				
			||||||
 | 
					    execute
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										41
									
								
								src/commands/showblocklist.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/commands/showblocklist.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					import { ChatInputCommandInteraction, PermissionFlagsBits, SlashCommandBuilder } from "discord.js";
 | 
				
			||||||
 | 
					import { IsNull } from "typeorm";
 | 
				
			||||||
 | 
					import { Badword, database } from "../data";
 | 
				
			||||||
 | 
					import { getGuildSetting, isPremiumActive } from "../tools/data";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const builder = new SlashCommandBuilder();
 | 
				
			||||||
 | 
					builder.setName("showblocklist");
 | 
				
			||||||
 | 
					builder.setDescription("Shows the blocklist of this server");
 | 
				
			||||||
 | 
					builder.setDMPermission(false);
 | 
				
			||||||
 | 
					builder.setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function execute(interaction: ChatInputCommandInteraction): Promise<void> {
 | 
				
			||||||
 | 
					    if (!interaction.inGuild()) throw new Error("Command was executed outside guild context");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const settings = await getGuildSetting(interaction.guildId);
 | 
				
			||||||
 | 
					    const isPremium = isPremiumActive(settings.isPremiumUntil);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const guildBadWords = await database.getRepository(Badword).find({
 | 
				
			||||||
 | 
					        select: {
 | 
				
			||||||
 | 
					            value: true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        where: {
 | 
				
			||||||
 | 
					            guildID: interaction.guildId
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    const globalBadWords = await database.getRepository(Badword).find({
 | 
				
			||||||
 | 
					        where: {
 | 
				
			||||||
 | 
					            guildID: IsNull()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    interaction.reply({
 | 
				
			||||||
 | 
					        content: `\`\`\`Global bad word list\`\`\`\n||${globalBadWords.map((word) => word.value).reduce((prev, next) => prev + ", " + next, "").slice(2)} ||\n\`\`\`Local server bad word list (${guildBadWords.length}/${isPremium ? 100 : 10})\`\`\`\n||${guildBadWords.map((word) => word.value).reduce((prev, next) => prev + ", " + next, "").slice(2)} ||`,
 | 
				
			||||||
 | 
					        ephemeral: true
 | 
				
			||||||
 | 
					    }).catch();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					    builder,
 | 
				
			||||||
 | 
					    execute
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								src/data/migrations/1669392941776-data.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/data/migrations/1669392941776-data.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					import { MigrationInterface, QueryRunner } from "typeorm";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class data1669392941776 implements MigrationInterface {
 | 
				
			||||||
 | 
					    name = 'data1669392941776'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async up(queryRunner: QueryRunner): Promise<void> {
 | 
				
			||||||
 | 
					        await queryRunner.query(`
 | 
				
			||||||
 | 
					            ALTER TABLE "guild_setting"
 | 
				
			||||||
 | 
					            ADD "preserveDataOnGuildLeave" boolean NOT NULL DEFAULT false
 | 
				
			||||||
 | 
					        `);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async down(queryRunner: QueryRunner): Promise<void> {
 | 
				
			||||||
 | 
					        await queryRunner.query(`
 | 
				
			||||||
 | 
					            ALTER TABLE "guild_setting" DROP COLUMN "preserveDataOnGuildLeave"
 | 
				
			||||||
 | 
					        `);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -10,4 +10,7 @@ export class GuildSetting {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Column("timestamp", { nullable: true, default: null })
 | 
					    @Column("timestamp", { nullable: true, default: null })
 | 
				
			||||||
    isPremiumUntil!: Date | null;
 | 
					    isPremiumUntil!: Date | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Column("boolean", { default: false })
 | 
				
			||||||
 | 
					    preserveDataOnGuildLeave!: boolean
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										17
									
								
								src/events/guildDelete.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/events/guildDelete.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					import client from "../client";
 | 
				
			||||||
 | 
					import { Events } from "discord.js";
 | 
				
			||||||
 | 
					import { getGuildSetting } from "../tools/data";
 | 
				
			||||||
 | 
					import { Badword, database, GuildSetting } from "../data";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					client.on(Events.GuildDelete, async (guild) => {
 | 
				
			||||||
 | 
					    const settings = await getGuildSetting(guild.id);
 | 
				
			||||||
 | 
					    if (settings.preserveDataOnGuildLeave) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await database.getRepository(GuildSetting).delete({
 | 
				
			||||||
 | 
					        id: guild.id
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await database.getRepository(Badword).delete({
 | 
				
			||||||
 | 
					        guildID: guild.id
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -1 +1,2 @@
 | 
				
			|||||||
import "./channelUpdate";
 | 
					import "./channelUpdate";
 | 
				
			||||||
 | 
					import "./guildDelete";
 | 
				
			||||||
							
								
								
									
										39
									
								
								src/service/cleanup.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/service/cleanup.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					import { Badword, database, GuildSetting } from "../data";
 | 
				
			||||||
 | 
					import client from "../client";
 | 
				
			||||||
 | 
					import { DiscordAPIError } from "discord.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default async function execute(): Promise<void> {
 | 
				
			||||||
 | 
					    const guilds = await database.getRepository(GuildSetting).find({
 | 
				
			||||||
 | 
					        where: {
 | 
				
			||||||
 | 
					            preserveDataOnGuildLeave: false
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const guild of guilds) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            await client.guilds.fetch(guild.id);
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            if (!(error instanceof DiscordAPIError)) {
 | 
				
			||||||
 | 
					                console.error(`service.cleanup failed: ${error}`);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            } 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const id = guild.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 5001 = Missing access
 | 
				
			||||||
 | 
					            if (error.code.toString() !== "50001") {
 | 
				
			||||||
 | 
					                console.warn(`Guild ${guild.id} is unavailable but not because of error 5001:\n${error}`);
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await database.getRepository(Badword).delete({
 | 
				
			||||||
 | 
					                guildID: guild.id
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await database.getRepository(GuildSetting).remove(guild);
 | 
				
			||||||
 | 
					            console.log(`Removed data for guild ${id}`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log("Cleanup completed");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1 +1,6 @@
 | 
				
			|||||||
import "./uptime";
 | 
					import "./uptime";
 | 
				
			||||||
 | 
					import runCleanup from "./cleanup";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					    runCleanup
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -12,6 +12,7 @@ export async function getGuildSetting(guildID: string): Promise<GuildSetting> {
 | 
				
			|||||||
        guildSetting.id = guildID;
 | 
					        guildSetting.id = guildID;
 | 
				
			||||||
        guildSetting.isPremiumUntil = null;
 | 
					        guildSetting.isPremiumUntil = null;
 | 
				
			||||||
        guildSetting.notificationChannelID = null;
 | 
					        guildSetting.notificationChannelID = null;
 | 
				
			||||||
 | 
					        guildSetting.preserveDataOnGuildLeave = false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return guildSetting;
 | 
					    return guildSetting;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ import { Color, Emoji } from "./design";
 | 
				
			|||||||
export default function getDefaultEmbed(): EmbedBuilder {
 | 
					export default function getDefaultEmbed(): EmbedBuilder {
 | 
				
			||||||
    const embed = new EmbedBuilder();
 | 
					    const embed = new EmbedBuilder();
 | 
				
			||||||
    embed.setFooter({
 | 
					    embed.setFooter({
 | 
				
			||||||
        text: `Channel filter V${pack.version} by AstroGD®`,
 | 
					        text: `Channel filter V${pack.version}`,
 | 
				
			||||||
        iconURL: client.user?.avatarURL() || undefined,
 | 
					        iconURL: client.user?.avatarURL() || undefined,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    embed.setTimestamp(new Date());
 | 
					    embed.setTimestamp(new Date());
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user