* Change rules.txt to rules.json (#31)

* Migrate to yarn

* Add role configs to config template

* Install packges and setup typescript

* Migrate entry point

* Migrate about command

* Migrate ban command

* Migrate clear command

* Migrate kick command

* Migrate mute command

* Migrate poll command

* Migrate bunny command

* Update required roles checker

* Migrate role command

* Migrate unmute command

* Migrate warn command

* Migrate eval command

* Migrate help command

* Migrate rules command

* Migrate events to typescript

* Update about command to use the PublicEmbed class

* Update ErrorMessage to ChannelNotFound

* Update messageDelete event to ignore bots

* Feature/74 merge vylbot core (#80)

* Merge VylBot-Core

* Update commands to new system

* Fix issue where events would not load

* Feature/12 create tests (#102)

* Fix tests

* Update coverage

* Remove unrequired mock files

* Add about command test

* Update about tests

* Ban command tests

* eval command tests

* Start help command tests

* Add help command tests

* Add kick command tests

* Mute command tests

* Poll command tests

* Add role command tests

Signed-off-by: Ethan Lane <ethan@vylpes.com>

* Add rules command tests

* Add unmute command tests

* Add warn command tests

* Add MemberEvents tests

* Add GuildMemberUpdate tests

Signed-off-by: Ethan Lane <ethan@vylpes.com>

* Add MessageEvents tests

* Add StringTools test

Signed-off-by: Ethan Lane <ethan@vylpes.com>

* Add embed tests

Signed-off-by: Ethan Lane <ethan@vylpes.com>

* Add GitHub Actions

Signed-off-by: Ethan Lane <ethan@vylpes.com>

* Move to tslint

Signed-off-by: Ethan Lane <ethan@vylpes.com>

* Remove tslint

Signed-off-by: Ethan Lane <ethan@vylpes.com>

* Remove linting script

Signed-off-by: Ethan Lane <ethan@vylpes.com>

* Update rules with blog website and event spoilers rule" (#106)

Signed-off-by: Ethan Lane <ethan@vylpes.com>

* Containerise bot (#107)

* Add moderator names to audit reason (#108)

* Feature/48 database (#114)

* Add database and default values

* Add ability to save a setting to the database

* Get commands and events to use database

* Setup and config command

* Update commands to check roles per server

* Different rules per server

Signed-off-by: Ethan Lane <ethan@vylpes.com>

* Different prefix per server

Signed-off-by: Ethan Lane <ethan@vylpes.com>

* Add verification system

Signed-off-by: Ethan Lane <ethan@vylpes.com>

* Disabled commands per server

* Add devmode for default prefix

* Update embeds

* Fix broken tests

* Feature/66 add different commands per server (#122)

* Add ability for server exclusive commands

* Add MankBot server-exclusive commands

* Add lobby entity to database

* Add documentation

* Add setup command for lobby (#123)

* Update bot to discord.js v13 (#125)

* Update bot to discord.js v13

* Remove debug code

* 110 commandshelp about command errors which causes command to not run (#126)

* Change onMessage to onMessageCreate

* Fix help command

* Add override for bot owner and server owner (#135)

* Change help command so exclusive commands can only be seen for the server they're assigned to (#136)

* Change parsing to not crash if invalid (#142)

* 137 role command cannot read properties of undefined (#141)

* Fix issue with bot crashing

* Fix server prefix not showing

* Add easy way to configure role command

* Move help text to its own directory

* Make role config command to use role id

* Get lobby command to use IDs instead of names (#144)

Co-authored-by: Vylpes <getgravitysoftware@gmail.com>
This commit is contained in:
Vylpes 2022-04-24 14:46:37 +01:00 committed by GitHub
parent 1168898e57
commit 04a4a6204c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
118 changed files with 13966 additions and 4027 deletions

View file

@ -0,0 +1,25 @@
import { ICommandContext } from "../../contracts/ICommandContext";
import PublicEmbed from "../../helpers/embeds/PublicEmbed";
import SettingsHelper from "../../helpers/SettingsHelper";
import { Command } from "../../type/command";
export default class Entry extends Command {
constructor() {
super();
super.Category = "Moderation";
super.Roles = [
"moderator"
];
}
public override async execute(context: ICommandContext) {
if (!context.message.guild) return;
const rulesChannelId = await SettingsHelper.GetSetting("channels.rules", context.message.guild.id) || "rules";
const embedInfo = new PublicEmbed(context, "", `Welcome to the server! Please make sure to read the rules in the <#${rulesChannelId}> channel and type the code found there in here to proceed to the main part of the server.`);
embedInfo.SendToCurrentChannel();
}
}

View file

@ -0,0 +1,143 @@
import { TextChannel } from "discord.js";
import { ICommandContext } from "../../contracts/ICommandContext";
import { Command } from "../../type/command";
import { default as eLobby } from "../../entity/501231711271780357/Lobby";
import SettingsHelper from "../../helpers/SettingsHelper";
import PublicEmbed from "../../helpers/embeds/PublicEmbed";
import { readFileSync } from "fs";
import ErrorEmbed from "../../helpers/embeds/ErrorEmbed";
import BaseEntity from "../../contracts/BaseEntity";
export default class Lobby extends Command {
constructor() {
super();
super.Category = "General";
}
public override async execute(context: ICommandContext) {
if (!context.message.guild) return;
switch (context.args[0]) {
case "config":
await this.UseConfig(context);
break;
default:
await this.UseDefault(context);
}
}
// =======
// Default
// =======
private async UseDefault(context: ICommandContext) {
const channel = context.message.channel as TextChannel;
const channelId = channel.id;
const lobby = await eLobby.FetchOneByChannelId(channelId);
if (!lobby) {
this.SendDisabled(context);
return;
}
const timeNow = Date.now();
const timeLength = lobby.Cooldown * 60 * 1000; // x minutes in ms
const timeAgo = timeNow - timeLength;
// If it was less than x minutes ago
if (lobby.LastUsed.getTime() > timeAgo) {
this.SendOnCooldown(context, timeLength, new Date(timeNow), lobby.LastUsed);
return;
}
await this.RequestLobby(context, lobby);
}
private async RequestLobby(context: ICommandContext, lobby: eLobby) {
lobby.MarkAsUsed();
await lobby.Save(eLobby, lobby);
context.message.channel.send(`${context.message.author} would like to organise a lobby of **${lobby.Name}**! <@&${lobby.RoleId}>`);
}
private SendOnCooldown(context: ICommandContext, timeLength: number, timeNow: Date, timeUsed: Date) {
const timeLeft = Math.ceil((timeLength - (timeNow.getTime() - timeUsed.getTime())) / 1000 / 60);
context.message.reply(`Requesting a lobby for this game is on cooldown! Please try again in **${timeLeft} minutes**.`);
}
private SendDisabled(context: ICommandContext) {
context.message.reply("This channel hasn't been setup for lobbies.");
}
// ======
// Config
// ======
private async UseConfig(context: ICommandContext) {
const moderatorRole = await SettingsHelper.GetSetting("role.moderator", context.message.guild!.id);
if (!context.message.member?.roles.cache.find(x => x.name == moderatorRole)) {
const errorEmbed = new ErrorEmbed(context, "Sorry, you must be a moderator to be able to configure this command");
errorEmbed.SendToCurrentChannel();
return;
}
switch (context.args[1]) {
case "add":
await this.AddLobbyConfig(context);
break;
case "remove":
await this.RemoveLobbyConfig(context);
break;
case "help":
default:
this.SendConfigHelp(context);
}
}
private SendConfigHelp(context: ICommandContext) {
const helpText = readFileSync(`${process.cwd()}/data/usage/lobby.txt`).toString();
const embed = new PublicEmbed(context, "Configure Lobby Command", helpText);
embed.SendToCurrentChannel();
}
private async AddLobbyConfig(context: ICommandContext) {
const channel = context.message.guild!.channels.cache.find(x => x.id == context.args[2]);
const role = context.message.guild!.roles.cache.find(x => x.id == context.args[3]);
const cooldown = Number(context.args[4]) || 30;
const gameName = context.args.splice(5).join(" ");
if (!channel || !role) {
this.SendConfigHelp(context);
return;
}
const entity = new eLobby(channel.id, role.id, cooldown, gameName);
await entity.Save(eLobby, entity);
const embed = new PublicEmbed(context, "", `Added \`${channel.name}\` as a new lobby channel with a cooldown of \`${cooldown} minutes\` and will ping \`${role.name}\` on use`);
embed.SendToCurrentChannel();
}
private async RemoveLobbyConfig(context: ICommandContext) {
const channel = context.message.guild!.channels.cache.find(x => x.id == context.args[2]);
if (!channel) {
this.SendConfigHelp(context);
return;
}
const entity = await eLobby.FetchOneByChannelId(channel.id);
if (entity) {
await BaseEntity.Remove<eLobby>(eLobby, entity);
}
const embed = new PublicEmbed(context, "", `Removed \`${channel.name}\` from the list of lobby channels`);
embed.SendToCurrentChannel();
}
}

25
src/commands/about.ts Normal file
View file

@ -0,0 +1,25 @@
import { ICommandContext } from "../contracts/ICommandContext";
import ICommandReturnContext from "../contracts/ICommandReturnContext";
import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
export default class About extends Command {
constructor() {
super();
super.Category = "General";
}
public override execute(context: ICommandContext): ICommandReturnContext {
const embed = new PublicEmbed(context, "About", "")
.addField("Version", process.env.BOT_VER!)
.addField("Author", process.env.BOT_AUTHOR!)
.addField("Date", process.env.BOT_DATE!);
embed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [embed]
};
}
}

80
src/commands/ban.ts Normal file
View file

@ -0,0 +1,80 @@
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import ErrorMessages from "../constants/ErrorMessages";
import LogEmbed from "../helpers/embeds/LogEmbed";
import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
import { ICommandContext } from "../contracts/ICommandContext";
import ICommandReturnContext from "../contracts/ICommandReturnContext";
export default class Ban extends Command {
constructor() {
super();
super.Category = "Moderation";
super.Roles = [
"moderator"
];
}
public override async execute(context: ICommandContext): Promise<ICommandReturnContext> {
const targetUser = context.message.mentions.users.first();
if (!targetUser) {
const embed = new ErrorEmbed(context, "User does not exist");
embed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [embed],
};
}
const targetMember = context.message.guild?.members.cache.find(x => x.user.id == targetUser.id);
if (!targetMember) {
const embed = new ErrorEmbed(context, "User is not in this server");
embed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [embed],
};
}
const reasonArgs = context.args;
reasonArgs.splice(0, 1)
const reason = reasonArgs.join(" ");
if (!context.message.guild?.available) {
return {
commandContext: context,
embeds: [],
};
}
if (!targetMember.bannable) {
const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions);
embed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [embed],
};
}
const logEmbed = new LogEmbed(context, "Member Banned");
logEmbed.AddUser("User", targetUser, true);
logEmbed.AddUser("Moderator", context.message.author);
logEmbed.AddReason(reason);
const publicEmbed = new PublicEmbed(context, "", `${targetUser} has been banned`);
await targetMember.ban({ reason: `Moderator: ${context.message.author.tag}, Reason: ${reason || "*none*"}` });
await logEmbed.SendToModLogsChannel();
publicEmbed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [logEmbed, publicEmbed],
};
}
}

50
src/commands/clear.ts Normal file
View file

@ -0,0 +1,50 @@
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import { TextChannel } from "discord.js";
import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
import { ICommandContext } from "../contracts/ICommandContext";
import ICommandReturnContext from "../contracts/ICommandReturnContext";
export default class Clear extends Command {
constructor() {
super();
super.Category = "Moderation";
super.Roles = [
"moderator"
];
}
public override async execute(context: ICommandContext): Promise<ICommandReturnContext> {
if (context.args.length == 0) {
const errorEmbed = new ErrorEmbed(context, "Please specify an amount between 1 and 100");
errorEmbed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [errorEmbed]
};
}
const totalToClear = Number.parseInt(context.args[0]);
if (!totalToClear || totalToClear <= 0 || totalToClear > 100) {
const errorEmbed = new ErrorEmbed(context, "Please specify an amount between 1 and 100");
errorEmbed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [errorEmbed]
};
}
await (context.message.channel as TextChannel).bulkDelete(totalToClear);
const embed = new PublicEmbed(context, "", `${totalToClear} message(s) were removed`);
embed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [embed]
};
}
}

94
src/commands/code.ts Normal file
View file

@ -0,0 +1,94 @@
import { CommandResponse } from "../constants/CommandResponse";
import { ICommandContext } from "../contracts/ICommandContext";
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import PublicEmbed from "../helpers/embeds/PublicEmbed";
import SettingsHelper from "../helpers/SettingsHelper";
import StringTools from "../helpers/StringTools";
import { Command } from "../type/command";
export default class Code extends Command {
constructor() {
super();
super.Category = "Moderation";
super.Roles = [
"moderator"
];
}
public override async precheckAsync(context: ICommandContext): Promise<CommandResponse> {
if (!context.message.guild){
return CommandResponse.NotInServer;
}
const isEnabled = await SettingsHelper.GetSetting("verification.enabled", context.message.guild?.id);
if (!isEnabled) {
return CommandResponse.FeatureDisabled;
}
if (isEnabled.toLocaleLowerCase() != 'true') {
return CommandResponse.FeatureDisabled;
}
return CommandResponse.Ok;
}
public override async execute(context: ICommandContext) {
const action = context.args[0];
switch (action) {
case "randomise":
await this.Randomise(context);
break;
case "embed":
await this.SendEmbed(context);
break;
default:
await this.SendUsage(context);
}
}
private async SendUsage(context: ICommandContext) {
const description = [
"USAGE: <randomise|embed>",
"",
"randomise: Sets the server's entry code to a random code",
"embed: Sends an embed with the server's entry code"
].join("\n");
const embed = new PublicEmbed(context, "", description);
embed.SendToCurrentChannel();
}
private async Randomise(context: ICommandContext) {
if (!context.message.guild) {
return;
}
const randomCode = StringTools.RandomString(5);
await SettingsHelper.SetSetting("verification.code", context.message.guild.id, randomCode);
const embed = new PublicEmbed(context, "Code", `Entry code has been set to \`${randomCode}\``);
embed.SendToCurrentChannel();
}
private async SendEmbed(context: ICommandContext) {
if (!context.message.guild) {
return;
}
const code = await SettingsHelper.GetSetting("verification.code", context.message.guild.id);
if (!code || code == "") {
const errorEmbed = new ErrorEmbed(context, "There is no code for this server setup.");
errorEmbed.SendToCurrentChannel();
return;
}
const embed = new PublicEmbed(context, "Entry Code", code!);
embed.SendToCurrentChannel();
}
}

136
src/commands/config.ts Normal file
View file

@ -0,0 +1,136 @@
import { Guild } from "discord.js";
import { readFileSync } from "fs";
import { CommandResponse } from "../constants/CommandResponse";
import DefaultValues from "../constants/DefaultValues";
import { ICommandContext } from "../contracts/ICommandContext";
import ICommandReturnContext from "../contracts/ICommandReturnContext";
import Server from "../entity/Server";
import Setting from "../entity/Setting";
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
export default class Config extends Command {
constructor() {
super();
super.Category = "Administration";
super.Roles = [
"administrator"
]
}
public override async precheckAsync(context: ICommandContext): Promise<CommandResponse> {
if (!context.message.guild) {
return CommandResponse.ServerNotSetup;
}
const server = await Server.FetchOneById<Server>(Server, context.message.guild?.id, [
"Settings",
]);
if (!server) {
return CommandResponse.ServerNotSetup;
}
return CommandResponse.Ok;
}
public override async execute(context: ICommandContext) {
if (!context.message.guild) {
return;
}
const server = await Server.FetchOneById<Server>(Server, context.message.guild?.id, [
"Settings",
]);
if (!server) {
return;
}
const key = context.args[0];
const action = context.args[1];
const value = context.args.splice(2).join(" ");
if (!key) {
this.SendHelpText(context);
} else if (!action) {
this.GetValue(context, server, key);
} else {
switch(action) {
case 'reset':
this.ResetValue(context, server, key);
break;
case 'set':
if (!value) {
const errorEmbed = new ErrorEmbed(context, "Value is required when setting");
errorEmbed.SendToCurrentChannel();
return;
}
this.SetValue(context, server, key, value);
break;
default:
const errorEmbed = new ErrorEmbed(context, "Action must be either set or reset");
errorEmbed.SendToCurrentChannel();
return;
}
}
}
private async SendHelpText(context: ICommandContext) {
const description = readFileSync(`${process.cwd()}/data/usage/config.txt`).toString();
const embed = new PublicEmbed(context, "Config", description);
embed.SendToCurrentChannel();
}
private async GetValue(context: ICommandContext, server: Server, key: string) {
const setting = server.Settings.filter(x => x.Key == key)[0];
if (setting) {
const embed = new PublicEmbed(context, "", `${key}: ${setting.Value}`);
embed.SendToCurrentChannel();
} else {
const embed = new PublicEmbed(context, "", `${key}: ${DefaultValues.GetValue(key)} <DEFAULT>`);
embed.SendToCurrentChannel();
}
}
private async ResetValue(context: ICommandContext, server: Server, key: string) {
const setting = server.Settings.filter(x => x.Key == key)[0];
if (!setting) {
const embed = new PublicEmbed(context, "", "Setting has been reset");
embed.SendToCurrentChannel();
return;
}
await Setting.Remove(Setting, setting);
const embed = new PublicEmbed(context, "", "Setting has been reset");
embed.SendToCurrentChannel();
}
private async SetValue(context: ICommandContext, server: Server, key: string, value: string) {
const setting = server.Settings.filter(x => x.Key == key)[0];
if (setting) {
setting.UpdateBasicDetails(key, value);
await setting.Save(Setting, setting);
} else {
const newSetting = new Setting(key, value);
await newSetting.Save(Setting, newSetting);
server.AddSettingToServer(newSetting);
await server.Save(Server, server);
}
const embed = new PublicEmbed(context, "", "Setting has been set");
embed.SendToCurrentChannel();
}
}

93
src/commands/disable.ts Normal file
View file

@ -0,0 +1,93 @@
import { ICommandContext } from "../contracts/ICommandContext";
import PublicEmbed from "../helpers/embeds/PublicEmbed";
import SettingsHelper from "../helpers/SettingsHelper";
import { Command } from "../type/command";
export default class Disable extends Command {
constructor() {
super();
super.Category = "Moderation";
super.Roles = [
"moderator"
];
}
public override async execute(context: ICommandContext) {
const action = context.args[0];
switch (action) {
case "add":
await this.Add(context);
break;
case "remove":
await this.Remove(context);
break;
default:
await this.SendUsage(context);
}
}
private async SendUsage(context: ICommandContext) {
const description = [
"USAGE: <add|remove> <name>",
"",
"add: Adds the command name to the server's disabled command string",
"remove: Removes the command name from the server's disabled command string",
"name: The name of the command to enable/disable"
].join("\n");
const embed = new PublicEmbed(context, "", description);
embed.SendToCurrentChannel();
}
private async Add(context: ICommandContext) {
if (!context.message.guild) {
return;
}
const commandName = context.args[1];
if (!commandName) {
this.SendUsage(context);
return;
}
const disabledCommandsString = await SettingsHelper.GetSetting("commands.disabled", context.message.guild.id);
const disabledCommands = disabledCommandsString != "" ? disabledCommandsString?.split(",") : [];
disabledCommands?.push(commandName);
await SettingsHelper.SetSetting("commands.disabled", context.message.guild.id, disabledCommands!.join(","));
const embed = new PublicEmbed(context, "", `Disabled command: ${commandName}`);
embed.SendToCurrentChannel();
}
private async Remove(context: ICommandContext) {
if (!context.message.guild) {
return;
}
const commandName = context.args[1];
if (!commandName) {
this.SendUsage(context);
return;
}
const disabledCommandsString = await SettingsHelper.GetSetting("commands.disabled", context.message.guild.id);
const disabledCommands = disabledCommandsString != "" ? disabledCommandsString?.split(",") : [];
const disabledCommandsInstance = disabledCommands?.findIndex(x => x == commandName);
if (disabledCommandsInstance! > -1) {
disabledCommands?.splice(disabledCommandsInstance!, 1);
}
await SettingsHelper.SetSetting("commands.disabled", context.message.guild.id, disabledCommands!.join(","));
const embed = new PublicEmbed(context, "", `Enabled command: ${commandName}`);
embed.SendToCurrentChannel();
}
}

70
src/commands/help.ts Normal file
View file

@ -0,0 +1,70 @@
import { existsSync, readdirSync } from "fs";
import { CoreClient } from "../client/client";
import { ICommandContext } from "../contracts/ICommandContext";
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import PublicEmbed from "../helpers/embeds/PublicEmbed";
import StringTools from "../helpers/StringTools";
import { Command } from "../type/command";
export interface ICommandData {
Exists: boolean;
Name?: string;
Category?: string;
Roles?: string[];
}
export default class Help extends Command {
constructor() {
super();
super.Category = "General";
}
public override execute(context: ICommandContext) {
if (context.args.length == 0) {
this.SendAll(context);
} else {
this.SendSingle(context);
}
}
public SendAll(context: ICommandContext) {
const allCommands = CoreClient.commandItems
.filter(x => !x.ServerId || x.ServerId == context.message.guild?.id);
const cateogries = [...new Set(allCommands.map(x => x.Command.Category))];
const embed = new PublicEmbed(context, "Commands", "");
cateogries.forEach(category => {
let filtered = allCommands.filter(x => x.Command.Category == category);
embed.addField(StringTools.Capitalise(category || "Uncategorised"), StringTools.CapitaliseArray(filtered.flatMap(x => x.Name)).join(", "));
});
embed.SendToCurrentChannel();
}
public SendSingle(context: ICommandContext) {
const command = CoreClient.commandItems.find(x => x.Name == context.args[0] && !x.ServerId);
const exclusiveCommand = CoreClient.commandItems.find(x => x.Name == context.args[0] && x.ServerId == context.message.guild?.id);
if (exclusiveCommand) {
const embed = new PublicEmbed(context, StringTools.Capitalise(exclusiveCommand.Name), "");
embed.addField("Category", StringTools.Capitalise(exclusiveCommand.Command.Category || "Uncategorised"));
embed.addField("Required Roles", StringTools.Capitalise(exclusiveCommand.Command.Roles.join(", ")) || "Everyone");
embed.SendToCurrentChannel();
} else if (command) {
const embed = new PublicEmbed(context, StringTools.Capitalise(command.Name), "");
embed.addField("Category", StringTools.Capitalise(command.Command.Category || "Uncategorised"));
embed.addField("Required Roles", StringTools.Capitalise(command.Command.Roles.join(", ")) || "Everyone");
embed.SendToCurrentChannel();
} else {
const errorEmbed = new ErrorEmbed(context, "Command does not exist");
errorEmbed.SendToCurrentChannel();
return;
}
}
}

83
src/commands/kick.ts Normal file
View file

@ -0,0 +1,83 @@
import ErrorMessages from "../constants/ErrorMessages";
import { ICommandContext } from "../contracts/ICommandContext";
import ICommandReturnContext from "../contracts/ICommandReturnContext";
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import LogEmbed from "../helpers/embeds/LogEmbed";
import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
export default class Kick extends Command {
constructor() {
super();
super.Category = "Moderation";
super.Roles = [
"moderator"
];
}
public override async execute(context: ICommandContext): Promise<ICommandReturnContext> {
const targetUser = context.message.mentions.users.first();
if (!targetUser) {
const embed = new ErrorEmbed(context, "User does not exist");
embed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [embed]
};
}
const targetMember = context.message.guild?.members.cache.find(x => x.user.id == targetUser.id);
if (!targetMember) {
const embed = new ErrorEmbed(context, "User is not in this server");
embed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [embed]
};
}
const reasonArgs = context.args;
reasonArgs.splice(0, 1)
const reason = reasonArgs.join(" ");
if (!context.message.guild?.available) {
return {
commandContext: context,
embeds: []
};
}
if (!targetMember.kickable) {
const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions);
embed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [embed]
};
}
const logEmbed = new LogEmbed(context, "Member Kicked");
logEmbed.AddUser("User", targetUser, true);
logEmbed.AddUser("Moderator", context.message.author);
logEmbed.AddReason(reason);
const publicEmbed = new PublicEmbed(context, "", `${targetUser} has been kicked`);
await targetMember.kick(`Moderator: ${context.message.author.tag}, Reason: ${reason || "*none*"}`);
await logEmbed.SendToModLogsChannel();
publicEmbed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [logEmbed, publicEmbed]
};
}
}

96
src/commands/mute.ts Normal file
View file

@ -0,0 +1,96 @@
import ErrorMessages from "../constants/ErrorMessages";
import { ICommandContext } from "../contracts/ICommandContext";
import ICommandReturnContext from "../contracts/ICommandReturnContext";
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import LogEmbed from "../helpers/embeds/LogEmbed";
import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
export default class Mute extends Command {
constructor() {
super();
super.Category = "Moderation";
super.Roles = [
"moderator"
];
}
public override async execute(context: ICommandContext): Promise<ICommandReturnContext> {
const targetUser = context.message.mentions.users.first();
if (!targetUser) {
const embed = new ErrorEmbed(context, "User does not exist");
embed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [embed]
};
}
const targetMember = context.message.guild?.members.cache.find(x => x.user.id == targetUser.id);
if (!targetMember) {
const embed = new ErrorEmbed(context, "User is not in this server");
embed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [embed]
};
}
const reasonArgs = context.args;
reasonArgs.splice(0, 1);
const reason = reasonArgs.join(" ");
if (!context.message.guild?.available) {
return {
commandContext: context,
embeds: []
};
}
if (!targetMember.manageable) {
const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions);
embed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [embed]
};
}
const logEmbed = new LogEmbed(context, "Member Muted");
logEmbed.AddUser("User", targetUser, true)
logEmbed.AddUser("Moderator", context.message.author);
logEmbed.AddReason(reason);
const publicEmbed = new PublicEmbed(context, "", `${targetUser} has been muted`);
publicEmbed.AddReason(reason);
const mutedRole = context.message.guild.roles.cache.find(role => role.name == process.env.ROLES_MUTED);
if (!mutedRole) {
const embed = new ErrorEmbed(context, ErrorMessages.RoleNotFound);
embed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [embed]
};
}
await targetMember.roles.add(mutedRole, `Moderator: ${context.message.author.tag}, Reason: ${reason || "*none*"}`);
await logEmbed.SendToModLogsChannel();
publicEmbed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [logEmbed, publicEmbed]
};
}
}

67
src/commands/poll.ts Normal file
View file

@ -0,0 +1,67 @@
import { ICommandContext } from "../contracts/ICommandContext";
import ICommandReturnContext from "../contracts/ICommandReturnContext";
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
export default class Poll extends Command {
constructor() {
super();
super.Category = "General";
}
public override async execute(context: ICommandContext): Promise<ICommandReturnContext> {
const argsJoined = context.args.join(" ");
const argsSplit = argsJoined.split(";");
if (argsSplit.length < 3 || argsSplit.length > 10) {
const errorEmbed = new ErrorEmbed(context, "Usage: <title>;<option 1>;<option 2>... (separate options with semicolons), maximum of 9 options");
errorEmbed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [errorEmbed]
};
}
const title = argsSplit[0];
const arrayOfNumbers = [
':one:',
':two:',
':three:',
':four:',
':five:',
':six:',
':seven:',
':eight:',
':nine:'
];
const reactionEmojis = ["1⃣", "2⃣", "3⃣", "4⃣", "5⃣", "6⃣", "7⃣", "8⃣", "9⃣"];
const description = arrayOfNumbers.splice(0, argsSplit.length - 1);
description.forEach((value, index) => {
description[index] = `${value} ${argsSplit[index + 1]}`;
});
const embed = new PublicEmbed(context, title, description.join("\n"));
const message = await context.message.channel.send({ embeds: [ embed ]});
description.forEach(async (value, index) => {
await message.react(reactionEmojis[index]);
});
if (context.message.deletable) {
await context.message.delete();
}
return {
commandContext: context,
embeds: [embed]
};
}
}

209
src/commands/role.ts Normal file
View file

@ -0,0 +1,209 @@
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Role as DiscordRole } from "discord.js";
import { Command } from "../type/command";
import { ICommandContext } from "../contracts/ICommandContext";
import ICommandReturnContext from "../contracts/ICommandReturnContext";
import SettingsHelper from "../helpers/SettingsHelper";
import { readFileSync } from "fs";
export default class Role extends Command {
constructor() {
super();
super.Category = "General";
}
public override async execute(context: ICommandContext) {
if (!context.message.guild) return;
switch (context.args[0]) {
case "config":
await this.UseConfig(context);
break;
default:
await this.UseDefault(context);
}
}
// =======
// Default
// =======
private async UseDefault(context: ICommandContext) {
const roles = await SettingsHelper.GetSetting("role.assignable", context.message.guild!.id);
if (!roles) {
const errorEmbed = new ErrorEmbed(context, "Unable to find any assignable roles");
errorEmbed.SendToCurrentChannel();
return;
}
const rolesArray = roles.split(",");
if (context.args.length == 0) {
await this.SendRolesList(context, rolesArray, context.message.guild!.id);
} else {
await this.ToggleRole(context, rolesArray);
}
}
public async SendRolesList(context: ICommandContext, roles: String[], serverId: string): Promise<ICommandReturnContext> {
const botPrefix = await SettingsHelper.GetServerPrefix(serverId);
const description = `Do ${botPrefix}role <role> to get the role!\n${roles.join('\n')}`;
const embed = new PublicEmbed(context, "Roles", description);
embed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [embed]
};
}
public async ToggleRole(context: ICommandContext, roles: String[]): Promise<ICommandReturnContext> {
const requestedRole = context.args.join(" ");
if (!roles.includes(requestedRole)) {
const errorEmbed = new ErrorEmbed(context, "This role isn't marked as assignable, to see a list of assignable roles, run this command without any parameters");
errorEmbed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [errorEmbed]
};
}
const assignRole = context.message.guild?.roles.cache.find(x => x.name == requestedRole);
if (!assignRole) {
const errorEmbed = new ErrorEmbed(context, "The current server doesn't have this role. Please contact the server's moderators");
errorEmbed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [errorEmbed]
};
}
const role = context.message.member?.roles.cache.find(x => x.name == requestedRole)
if (!role) {
await this.AddRole(context, assignRole);
} else {
await this.RemoveRole(context, assignRole);
}
return {
commandContext: context,
embeds: []
};
}
public async AddRole(context: ICommandContext, role: DiscordRole): Promise<ICommandReturnContext> {
await context.message.member?.roles.add(role, "Toggled with role command");
const embed = new PublicEmbed(context, "", `Gave role: \`${role.name}\``);
embed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [embed]
};
}
public async RemoveRole(context: ICommandContext, role: DiscordRole): Promise<ICommandReturnContext> {
await context.message.member?.roles.remove(role, "Toggled with role command");
const embed = new PublicEmbed(context, "", `Removed role: \`${role.name}\``);
embed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [embed]
};
}
// ======
// Config
// ======
private async UseConfig(context: ICommandContext) {
const moderatorRole = await SettingsHelper.GetSetting("role.moderator", context.message.guild!.id);
if (!context.message.member?.roles.cache.find(x => x.name == moderatorRole)) {
const errorEmbed = new ErrorEmbed(context, "Sorry, you must be a moderator to be able to configure this command");
errorEmbed.SendToCurrentChannel();
return;
}
switch (context.args[1]) {
case "add":
await this.AddRoleConfig(context);
break;
case "remove":
await this.RemoveRoleConfig(context);
break;
default:
this.SendConfigHelp(context);
}
}
private SendConfigHelp(context: ICommandContext) {
const helpText = readFileSync(`${process.cwd()}/data/usage/role.txt`).toString();
const embed = new PublicEmbed(context, "Configure Role Command", helpText);
embed.SendToCurrentChannel();
}
private async AddRoleConfig(context: ICommandContext) {
const role = context.message.guild!.roles.cache.find(x => x.id == context.args[2]);
if (!role) {
this.SendConfigHelp(context);
return;
}
let setting = await SettingsHelper.GetSetting("role.assignable", context.message.guild!.id) || "";
const settingArray = setting.split(",");
settingArray.push(role.name);
setting = settingArray.join(",");
await SettingsHelper.SetSetting("role.assignable", context.message.guild!.id, setting);
const embed = new PublicEmbed(context, "", `Added \`${role.name}\` as a new assignable role`);
embed.SendToCurrentChannel();
}
private async RemoveRoleConfig(context: ICommandContext) {
const role = context.message.guild!.roles.cache.find(x => x.id == context.args[2]);
if (!role) {
this.SendConfigHelp(context);
return;
}
let setting = await SettingsHelper.GetSetting("role.assignable", context.message.guild!.id);
if (!setting) return;
const settingArray = setting.split(",");
const index = settingArray.findIndex(x => x == role.name);
if (index == -1) return;
settingArray.splice(index, 1);
setting = settingArray.join(",");
await SettingsHelper.SetSetting("role.assignable", context.message.guild!.id, setting);
const embed = new PublicEmbed(context, "", `Removed \`${role.name}\` from the list of assignable roles`);
embed.SendToCurrentChannel();
}
}

57
src/commands/rules.ts Normal file
View file

@ -0,0 +1,57 @@
import { existsSync, readFileSync } from "fs";
import { ICommandContext } from "../contracts/ICommandContext";
import ICommandReturnContext from "../contracts/ICommandReturnContext";
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
interface IRules {
title?: string;
description?: string[];
image?: string;
footer?: string;
}
export default class Rules extends Command {
constructor() {
super();
super.Category = "Admin";
super.Roles = [
"administrator"
];
}
public override execute(context: ICommandContext): ICommandReturnContext {
if (!existsSync(`${process.cwd()}/data/rules/${context.message.guild?.id}.json`)) {
const errorEmbed = new ErrorEmbed(context, "Rules file doesn't exist");
errorEmbed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [errorEmbed]
};
}
const rulesFile = readFileSync(`${process.cwd()}/data/rules/${context.message.guild?.id}.json`).toString();
const rules = JSON.parse(rulesFile) as IRules[];
const embeds: PublicEmbed[] = [];
rules.forEach(rule => {
const embed = new PublicEmbed(context, rule.title || "", rule.description?.join("\n") || "");
embed.setImage(rule.image || "");
embed.setFooter(rule.footer || "");
embeds.push(embed);
});
embeds.forEach(x => x.SendToCurrentChannel());
return {
commandContext: context,
embeds: embeds
};
}
}

36
src/commands/setup.ts Normal file
View file

@ -0,0 +1,36 @@
import { ICommandContext } from "../contracts/ICommandContext";
import Server from "../entity/Server";
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
export default class Setup extends Command {
constructor() {
super();
super.Category = "Administration";
super.Roles = [
"moderator"
]
}
public override async execute(context: ICommandContext) {
if (!context.message.guild) {
return;
}
const server = await Server.FetchOneById(Server, context.message.guild?.id);
if (server) {
const embed = new ErrorEmbed(context, "This server has already been setup, please configure using the config command");
embed.SendToCurrentChannel();
return;
}
const newServer = new Server(context.message.guild?.id);
await newServer.Save(Server, newServer);
const embed = new PublicEmbed(context, "Success", "Please configure using the config command");
embed.SendToCurrentChannel();
}
}

96
src/commands/unmute.ts Normal file
View file

@ -0,0 +1,96 @@
import ErrorMessages from "../constants/ErrorMessages";
import { ICommandContext } from "../contracts/ICommandContext";
import ICommandReturnContext from "../contracts/ICommandReturnContext";
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import LogEmbed from "../helpers/embeds/LogEmbed";
import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
export default class Unmute extends Command {
constructor() {
super();
super.Category = "Moderation";
super.Roles = [
"moderator"
];
}
public override async execute(context: ICommandContext): Promise<ICommandReturnContext> {
const targetUser = context.message.mentions.users.first();
if (!targetUser) {
const embed = new ErrorEmbed(context, "User does not exist");
embed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [embed]
};
}
const targetMember = context.message.guild?.members.cache.find(x => x.user.id == targetUser.id);
if (!targetMember) {
const embed = new ErrorEmbed(context, "User is not in this server");
embed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [embed]
};
}
const reasonArgs = context.args;
reasonArgs.splice(0, 1);
const reason = reasonArgs.join(" ");
if (!context.message.guild?.available) {
return {
commandContext: context,
embeds: []
};
}
if (!targetMember.manageable) {
const embed = new ErrorEmbed(context, ErrorMessages.InsufficientBotPermissions);
embed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [embed]
};
}
const logEmbed = new LogEmbed(context, "Member Unmuted");
logEmbed.AddUser("User", targetUser, true)
logEmbed.AddUser("Moderator", context.message.author);
logEmbed.AddReason(reason);
const publicEmbed = new PublicEmbed(context, "", `${targetUser} has been unmuted`);
publicEmbed.AddReason(reason);
const mutedRole = context.message.guild.roles.cache.find(role => role.name == process.env.ROLES_MUTED);
if (!mutedRole) {
const embed = new ErrorEmbed(context, ErrorMessages.RoleNotFound);
embed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [embed]
};
}
await targetMember.roles.remove(mutedRole, `Moderator: ${context.message.author.tag}, Reason: ${reason || "*none*"}`);
await logEmbed.SendToModLogsChannel();
publicEmbed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [logEmbed, publicEmbed]
};
}
}

71
src/commands/warn.ts Normal file
View file

@ -0,0 +1,71 @@
import { ICommandContext } from "../contracts/ICommandContext";
import ICommandReturnContext from "../contracts/ICommandReturnContext";
import ErrorEmbed from "../helpers/embeds/ErrorEmbed";
import LogEmbed from "../helpers/embeds/LogEmbed";
import PublicEmbed from "../helpers/embeds/PublicEmbed";
import { Command } from "../type/command";
export default class Warn extends Command {
constructor() {
super();
super.Category = "Moderation";
super.Roles = [
"moderator"
];
}
public override async execute(context: ICommandContext): Promise<ICommandReturnContext> {
const user = context.message.mentions.users.first();
if (!user) {
const errorEmbed = new ErrorEmbed(context, "User does not exist");
errorEmbed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [errorEmbed]
};
}
const member = context.message.guild?.members.cache.find(x => x.user.id == user.id);
if (!member) {
const errorEmbed = new ErrorEmbed(context, "User is not in this server");
errorEmbed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [errorEmbed]
};
}
const reasonArgs = context.args;
reasonArgs.splice(0, 1);
const reason = reasonArgs.join(" ");
if (!context.message.guild?.available) {
return {
commandContext: context,
embeds: []
};
}
const logEmbed = new LogEmbed(context, "Member Warned");
logEmbed.AddUser("User", user, true);
logEmbed.AddUser("Moderator", context.message.author);
logEmbed.AddReason(reason);
const publicEmbed = new PublicEmbed(context, "", `${user} has been warned`);
publicEmbed.AddReason(reason);
await logEmbed.SendToModLogsChannel();
publicEmbed.SendToCurrentChannel();
return {
commandContext: context,
embeds: [logEmbed, publicEmbed]
};
}
}