diff --git a/package.json b/package.json index b7cfc34..01955ab 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "email": "helpdesk@vylpes.com" }, "homepage": "https://github.com/Vylpes/vylbot-app", + "funding": "https://ko-fi.com/vylpes", "dependencies": { "@types/jest": "^27.0.3", "@types/uuid": "^8.3.4", diff --git a/src/commands/about.ts b/src/commands/about.ts index 1391da9..b688df6 100644 --- a/src/commands/about.ts +++ b/src/commands/about.ts @@ -1,4 +1,4 @@ -import { Emoji, MessageActionRow, MessageButton } from "discord.js"; +import { MessageActionRow, MessageButton } from "discord.js"; import { MessageButtonStyles } from "discord.js/typings/enums"; import { ICommandContext } from "../contracts/ICommandContext"; import PublicEmbed from "../helpers/embeds/PublicEmbed"; diff --git a/src/commands/audits.ts b/src/commands/audits.ts new file mode 100644 index 0000000..c53fe37 --- /dev/null +++ b/src/commands/audits.ts @@ -0,0 +1,144 @@ +import { ICommandContext } from "../contracts/ICommandContext"; +import Audit from "../entity/Audit"; +import AuditTools from "../helpers/AuditTools"; +import PublicEmbed from "../helpers/embeds/PublicEmbed"; +import { Command } from "../type/command"; +import SettingsHelper from "../helpers/SettingsHelper"; +import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; + +export default class Audits extends Command { + constructor() { + super(); + + super.Category = "Moderation"; + super.Roles = [ + "moderator" + ]; + } + + public override async execute(context: ICommandContext) { + if (!context.message.guild) return; + + switch (context.args[0]) { + case "user": + await this.SendAuditForUser(context); + break; + case "view": + await this.SendAudit(context); + break; + case "clear": + await this.ClearAudit(context); + break; + case "add": + await this.AddAudit(context); + break; + default: + await this.SendUsage(context); + } + } + + private async SendUsage(context: ICommandContext) { + const prefix = await SettingsHelper.GetServerPrefix(context.message.guild!.id); + + const description = [ + `\`${prefix}audits user \` - Send the audits for this user`, + `\`${prefix}audits view \` - Send information about an audit`, + `\`${prefix}audits clear \` - Clears an audit for a user by audit id`, + `\`${prefix}audits add [reason]\` - Manually add an audit for a user`, + ] + + const publicEmbed = new PublicEmbed(context, "Usage", description.join("\n")); + await publicEmbed.SendToCurrentChannel(); + } + + private async SendAuditForUser(context: ICommandContext) { + const userId = context.args[1]; + + const audits = await Audit.FetchAuditsByUserId(userId, context.message.guild!.id); + + if (!audits || audits.length == 0) { + const publicEmbed = new PublicEmbed(context, "", "There are no audits logged for this user."); + await publicEmbed.SendToCurrentChannel(); + + return; + } + + const publicEmbed = new PublicEmbed(context, "Audit Log", ""); + + for (let audit of audits) { + publicEmbed.addField(`${audit.AuditId} // ${AuditTools.TypeToFriendlyText(audit.AuditType)}`, audit.WhenCreated.toString()); + } + + await publicEmbed.SendToCurrentChannel(); + } + + private async SendAudit(context: ICommandContext) { + const auditId = context.args[1]; + + if (!auditId) { + await this.SendUsage(context); + return; + } + + const audit = await Audit.FetchAuditByAuditId(auditId.toUpperCase(), context.message.guild!.id); + + if (!audit) { + const errorEmbed = new ErrorEmbed(context, "This audit can not be found."); + await errorEmbed.SendToCurrentChannel(); + + return; + } + + const publicEmbed = new PublicEmbed(context, `Audit ${audit.AuditId.toUpperCase()}`, ""); + + publicEmbed.addField("Reason", audit.Reason || "*none*", true); + publicEmbed.addField("Type", AuditTools.TypeToFriendlyText(audit.AuditType), true); + publicEmbed.addField("Moderator", `<@${audit.ModeratorId}>`, true); + + await publicEmbed.SendToCurrentChannel(); + } + + private async ClearAudit(context: ICommandContext) { + const auditId = context.args[1]; + + if (!auditId) { + await this.SendUsage(context); + return; + } + + const audit = await Audit.FetchAuditByAuditId(auditId.toUpperCase(), context.message.guild!.id); + + if (!audit) { + const errorEmbed = new ErrorEmbed(context, "This audit can not be found."); + await errorEmbed.SendToCurrentChannel(); + + return; + } + + await Audit.Remove(Audit, audit); + + const publicEmbed = new PublicEmbed(context, "", "Audit cleared"); + await publicEmbed.SendToCurrentChannel(); + } + + private async AddAudit(context: ICommandContext) { + const userId = context.args[1]; + const typeString = context.args[2]; + const reason = context.args.splice(3) + .join(" "); + + if (!userId || !typeString) { + await this.SendUsage(context); + return; + } + + const type = AuditTools.StringToType(typeString); + + const audit = new Audit(userId, type, reason, context.message.author.id, context.message.guild!.id); + + await audit.Save(Audit, audit); + + const publicEmbed = new PublicEmbed(context, "", `Created new audit with ID \`${audit.AuditId}\``); + await publicEmbed.SendToCurrentChannel(); + } +} \ No newline at end of file diff --git a/src/commands/ban.ts b/src/commands/ban.ts index ad39f03..8d50132 100644 --- a/src/commands/ban.ts +++ b/src/commands/ban.ts @@ -5,6 +5,8 @@ import PublicEmbed from "../helpers/embeds/PublicEmbed"; import { Command } from "../type/command"; import { ICommandContext } from "../contracts/ICommandContext"; import ICommandReturnContext from "../contracts/ICommandReturnContext"; +import Audit from "../entity/Audit"; +import { AuditType } from "../constants/AuditType"; export default class Ban extends Command { constructor() { @@ -75,6 +77,12 @@ export default class Ban extends Command { await logEmbed.SendToModLogsChannel(); await publicEmbed.SendToCurrentChannel(); + if (context.message.guild) { + const audit = new Audit(targetUser.id, AuditType.Ban, reason, context.message.author.id, context.message.guild.id); + + await audit.Save(Audit, audit); + } + return { commandContext: context, embeds: [logEmbed, publicEmbed], diff --git a/src/commands/kick.ts b/src/commands/kick.ts index 81b1af9..c1b2572 100644 --- a/src/commands/kick.ts +++ b/src/commands/kick.ts @@ -1,6 +1,8 @@ +import { AuditType } from "../constants/AuditType"; import ErrorMessages from "../constants/ErrorMessages"; import { ICommandContext } from "../contracts/ICommandContext"; import ICommandReturnContext from "../contracts/ICommandReturnContext"; +import Audit from "../entity/Audit"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; import LogEmbed from "../helpers/embeds/LogEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed"; @@ -74,6 +76,12 @@ export default class Kick extends Command { await logEmbed.SendToModLogsChannel(); await publicEmbed.SendToCurrentChannel(); + + if (context.message.guild) { + const audit = new Audit(targetUser.id, AuditType.Kick, reason, context.message.author.id, context.message.guild.id); + + await audit.Save(Audit, audit); + } return { commandContext: context, diff --git a/src/commands/mute.ts b/src/commands/mute.ts index e5d43df..7a45b87 100644 --- a/src/commands/mute.ts +++ b/src/commands/mute.ts @@ -1,6 +1,8 @@ +import { AuditType } from "../constants/AuditType"; import ErrorMessages from "../constants/ErrorMessages"; import { ICommandContext } from "../contracts/ICommandContext"; import ICommandReturnContext from "../contracts/ICommandReturnContext"; +import Audit from "../entity/Audit"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; import LogEmbed from "../helpers/embeds/LogEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed"; @@ -87,6 +89,12 @@ export default class Mute extends Command { await logEmbed.SendToModLogsChannel(); await publicEmbed.SendToCurrentChannel(); + + if (context.message.guild) { + const audit = new Audit(targetUser.id, AuditType.Mute, reason, context.message.author.id, context.message.guild.id); + + await audit.Save(Audit, audit); + } return { commandContext: context, diff --git a/src/commands/warn.ts b/src/commands/warn.ts index a36e28e..00f9efe 100644 --- a/src/commands/warn.ts +++ b/src/commands/warn.ts @@ -1,5 +1,7 @@ +import { AuditType } from "../constants/AuditType"; import { ICommandContext } from "../contracts/ICommandContext"; import ICommandReturnContext from "../contracts/ICommandReturnContext"; +import Audit from "../entity/Audit"; import ErrorEmbed from "../helpers/embeds/ErrorEmbed"; import LogEmbed from "../helpers/embeds/LogEmbed"; import PublicEmbed from "../helpers/embeds/PublicEmbed"; @@ -62,6 +64,12 @@ export default class Warn extends Command { await logEmbed.SendToModLogsChannel(); await publicEmbed.SendToCurrentChannel(); + + if (context.message.guild) { + const audit = new Audit(user.id, AuditType.Warn, reason, context.message.author.id, context.message.guild.id); + + await audit.Save(Audit, audit); + } return { commandContext: context, diff --git a/src/constants/AuditType.ts b/src/constants/AuditType.ts new file mode 100644 index 0000000..4ad8df6 --- /dev/null +++ b/src/constants/AuditType.ts @@ -0,0 +1,7 @@ +export enum AuditType { + General, + Warn, + Mute, + Kick, + Ban, +} \ No newline at end of file diff --git a/src/entity/Audit.ts b/src/entity/Audit.ts new file mode 100644 index 0000000..0ec1ea7 --- /dev/null +++ b/src/entity/Audit.ts @@ -0,0 +1,56 @@ +import { Column, Entity, getConnection, ManyToOne } from "typeorm"; +import { AuditType } from "../constants/AuditType"; +import BaseEntity from "../contracts/BaseEntity"; +import StringTools from "../helpers/StringTools"; + +@Entity() +export default class Audit extends BaseEntity { + constructor(userId: string, auditType: AuditType, reason: string, moderatorId: string, serverId: string) { + super(); + + this.AuditId = StringTools.RandomString(5).toUpperCase(); + this.UserId = userId; + this.AuditType = auditType; + this.Reason = reason; + this.ModeratorId = moderatorId; + this.ServerId = serverId; + } + + @Column() + AuditId: string; + + @Column() + UserId: string; + + @Column() + AuditType: AuditType; + + @Column() + Reason: string; + + @Column() + ModeratorId: string; + + @Column() + ServerId: string; + + public static async FetchAuditsByUserId(userId: string, serverId: string): Promise { + const connection = getConnection(); + + const repository = connection.getRepository(Audit); + + const all = await repository.find({ UserId: userId, ServerId: serverId }); + + return all; + } + + public static async FetchAuditByAuditId(auditId: string, serverId: string): Promise { + const connection = getConnection(); + + const repository = connection.getRepository(Audit); + + const single = await repository.findOne({ AuditId: auditId, ServerId: serverId }); + + return single; + } +} \ No newline at end of file diff --git a/src/helpers/AuditTools.ts b/src/helpers/AuditTools.ts new file mode 100644 index 0000000..fa06c43 --- /dev/null +++ b/src/helpers/AuditTools.ts @@ -0,0 +1,37 @@ +import { AuditType } from "../constants/AuditType"; + +export default class AuditTools { + public static TypeToFriendlyText(auditType: AuditType): string { + switch (auditType) { + case AuditType.General: + return "General"; + case AuditType.Warn: + return "Warn"; + case AuditType.Mute: + return "Mute"; + case AuditType.Kick: + return "Kick"; + case AuditType.Ban: + return "Ban"; + default: + return "Other"; + } + } + + public static StringToType(str: string): AuditType { + switch (str.toLowerCase()) { + case "general": + return AuditType.General; + case "warn": + return AuditType.Warn; + case "mute": + return AuditType.Mute; + case "kick": + return AuditType.Kick; + case "ban": + return AuditType.Ban; + default: + return AuditType.General; + } + } +} \ No newline at end of file diff --git a/src/migration/1660754832945-CreateAudit.ts b/src/migration/1660754832945-CreateAudit.ts new file mode 100644 index 0000000..9dab091 --- /dev/null +++ b/src/migration/1660754832945-CreateAudit.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from "typeorm" + +export class CreateAudit1660754832945 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + queryRunner.query(`CREATE TABLE audit (Id varchar(255), WhenCreated datetime, WhenUpdated datetime, auditId varchar(255), userId varchar(255), auditType int, reason varchar(255), moderatorId varchar(255), serverId varchar(255), PRIMARY KEY (Id))`); + } + + public async down(queryRunner: QueryRunner): Promise { + queryRunner.query(`DROP TABLE audit`); + } + +} diff --git a/src/registry.ts b/src/registry.ts index 4fa68f7..c34c098 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -2,6 +2,7 @@ import { CoreClient } from "./client/client"; // Command Imports import About from "./commands/about"; +import Audits from "./commands/audits"; import Ban from "./commands/ban"; import Clear from "./commands/clear"; import Code from "./commands/code"; @@ -48,6 +49,7 @@ export default class Registry { CoreClient.RegisterCommand("warn", new Warn()); CoreClient.RegisterCommand("setup", new Setup()); CoreClient.RegisterCommand("say", new Say()); + CoreClient.RegisterCommand("audits", new Audits()); // Exclusive Commands: MankBot CoreClient.RegisterCommand("lobby", new Lobby(), "501231711271780357");