Compare commits

...

3 commits

Author SHA1 Message Date
a89b9246a3 Attempt to create dms to the user a moderation action was taken on
All checks were successful
Test / build (push) Successful in 7s
2024-08-17 16:54:55 +01:00
674a4ac614 Fix typo in moon sql script
All checks were successful
Deploy To Stage / build (push) Successful in 6s
Deploy To Stage / deploy (push) Successful in 15s
2024-08-17 16:54:11 +01:00
e2ec042fcd Add list moons command (#449)
Some checks failed
Deploy To Stage / build (push) Successful in 6s
Deploy To Stage / deploy (push) Failing after 16s
- Add concept of moons to the database
- Add the list moons command to view a user's moons
- This command is paginated using buttons

#195

Reviewed-on: #449
Reviewed-by: VylpesTester <tester@vylpes.com>
Co-authored-by: Ethan Lane <ethan@vylpes.com>
Co-committed-by: Ethan Lane <ethan@vylpes.com>
2024-08-17 16:47:14 +01:00
14 changed files with 321 additions and 51 deletions

View file

@ -7,7 +7,7 @@
# any secret values.
BOT_TOKEN=
BOT_VER=3.2.1
BOT_VER=3.3.0
BOT_AUTHOR=Vylpes
BOT_OWNERID=147392775707426816
BOT_CLIENTID=682942374040961060
@ -24,3 +24,5 @@ DB_AUTH_USER=dev
DB_AUTH_PASS=dev
DB_SYNC=true
DB_LOGGING=true
DB_ROOT_HOST=0.0.0.0
DB_DATA_LOCATION=./.temp/database

View file

@ -0,0 +1,10 @@
CREATE TABLE `moon` (
`Id` varchar(255) NOT NULL,
`WhenCreated` datetime NOT NULL,
`WhenUpdated` datetime NOT NULL,
`MoonNumber` int NOT NULL,
`UserId` varchar(255) NOT NULL,
`Description` varchar(255) NOT NULL,
`WhenArchived` datetime NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

14
src/buttonEvents/moons.ts Normal file
View file

@ -0,0 +1,14 @@
import {ButtonInteraction} from "discord.js";
import {ButtonEvent} from "../type/buttonEvent";
import List from "./moons/list";
export default class Moons extends ButtonEvent {
public override async execute(interaction: ButtonInteraction): Promise<void> {
const action = interaction.customId.split(" ")[1];
switch (action) {
case "list":
await List(interaction);
}
}
}

View file

@ -0,0 +1,53 @@
import {ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, EmbedBuilder} from "discord.js";
import Moon from "../../database/entities/Moon";
import EmbedColours from "../../constants/EmbedColours";
export default async function List(interaction: ButtonInteraction) {
if (!interaction.guild) return;
const userId = interaction.customId.split(" ")[2];
const page = interaction.customId.split(" ")[3];
if (!userId || !page) return;
const pageNumber = Number(page);
const member = interaction.guild.members.cache.find(x => x.user.id == userId);
const pageLength = 10;
const moons = await Moon.FetchPaginatedMoonsByUserId(userId, pageLength, pageNumber);
if (!moons || moons[0].length == 0) {
await interaction.reply(`${member?.user.username ?? "This user"} does not have any moons or page is invalid.`);
return;
}
const totalPages = Math.ceil(moons[1] / pageLength);
const description = moons[0].flatMap(x => `${x.MoonNumber}. ${x.Description.slice(0, 15)}`);
const embed = new EmbedBuilder()
.setTitle(`${member?.user.username}'s Moons`)
.setColor(EmbedColours.Ok)
.setDescription(description.join("\n"))
.setFooter({ text: `Page ${page + 1} of ${totalPages} · ${moons[1]} moons` });
const row = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId(`moons list ${userId} ${pageNumber - 1}`)
.setLabel("Previous")
.setStyle(ButtonStyle.Primary)
.setDisabled(pageNumber == 0),
new ButtonBuilder()
.setCustomId(`moons list ${userId} ${pageNumber + 1}`)
.setLabel("Next")
.setStyle(ButtonStyle.Primary)
.setDisabled(pageNumber + 1 == totalPages));
await interaction.update({
embeds: [ embed ],
components: [ row ],
});
}

View file

@ -0,0 +1,35 @@
import { Command } from "../../type/command";
import { CommandInteraction, SlashCommandBuilder } from "discord.js";
import ListMoons from "./moons/list";
export default class Moons extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
.setName("moons")
.setDescription("View and create moons")
.addSubcommand(subcommand =>
subcommand
.setName('list')
.setDescription('List moons you have obtained')
.addUserOption(option =>
option
.setName("user")
.setDescription("The user to view (Defaults to yourself)"))
.addNumberOption(option =>
option
.setName("page")
.setDescription("The page to start with")));
}
public override async execute(interaction: CommandInteraction) {
if (!interaction.isChatInputCommand()) return;
switch (interaction.options.getSubcommand()) {
case "list":
await ListMoons(interaction);
break;
}
}
}

View file

@ -0,0 +1,45 @@
import {ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedBuilder} from "discord.js";
import Moon from "../../../database/entities/Moon";
import EmbedColours from "../../../constants/EmbedColours";
export default async function ListMoons(interaction: CommandInteraction) {
const user = interaction.options.get("user")?.user ?? interaction.user;
const page = interaction.options.get("page")?.value as number ?? 0;
const pageLength = 10;
const moons = await Moon.FetchPaginatedMoonsByUserId(user.id, pageLength, page);
if (!moons || moons[0].length == 0) {
await interaction.reply(`${user.username} does not have any moons or page is invalid.`);
return;
}
const totalPages = Math.ceil(moons[1] / pageLength);
const description = moons[0].flatMap(x => `${x.MoonNumber}. ${x.Description.slice(0, 15)}`);
const embed = new EmbedBuilder()
.setTitle(`${user.username}'s Moons`)
.setColor(EmbedColours.Ok)
.setDescription(description.join("\n"))
.setFooter({ text: `Page ${page + 1} of ${totalPages} · ${moons[1]} moons` });
const row = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId(`moons list ${user.id} ${page - 1}`)
.setLabel("Previous")
.setStyle(ButtonStyle.Primary)
.setDisabled(page == 0),
new ButtonBuilder()
.setCustomId(`moons list ${user.id} ${page + 1}`)
.setLabel("Next")
.setStyle(ButtonStyle.Primary)
.setDisabled(page + 1 == totalPages));
await interaction.reply({
embeds: [ embed ],
components: [ row ],
});
}

View file

@ -62,7 +62,6 @@ export default class Ban extends Command {
}
await member.ban();
await interaction.reply(`\`${targetUser.user.tag}\` has been banned.`);
const channelName = await SettingsHelper.GetSetting('channels.logs.mod', interaction.guildId);
@ -74,7 +73,29 @@ export default class Ban extends Command {
await channel.send({ embeds: [ logEmbed ]});
}
const dmEmbed = new EmbedBuilder()
.setColor(EmbedColours.Ok)
.setTitle(interaction.guild.name)
.setDescription("You have been banned by a moderator.")
.addFields([
{
name: "Reason",
value: reason,
},
]);
let replyText = "Successfully banned user.";
try {
const dmChannel = await targetUser.user!.createDM();
await dmChannel.send({ embeds: [ dmEmbed ] });
} catch {
replyText += " *Note: I was unable to DM the user the reason.*";
}
const audit = new Audit(targetUser.user.id, AuditType.Ban, reason, interaction.user.id, interaction.guildId);
await audit.Save(Audit, audit);
await interaction.reply(replyText);
}
}

View file

@ -62,7 +62,6 @@ export default class Kick extends Command {
}
await member.kick();
await interaction.reply(`\`${targetUser.user.tag}\` has been kicked.`);
const channelName = await SettingsHelper.GetSetting('channels.logs.mod', interaction.guildId);
@ -74,7 +73,29 @@ export default class Kick extends Command {
await channel.send({ embeds: [ logEmbed ]});
}
const dmEmbed = new EmbedBuilder()
.setColor(EmbedColours.Ok)
.setTitle(interaction.guild.name)
.setDescription("You have been kicked from the server.")
.addFields([
{
name: "Reason",
value: reason,
},
]);
let replyText = "Successfully kicked user.";
try {
const dmChannel = await targetUser.user!.createDM();
await dmChannel.send({ embeds: [ dmEmbed ] });
} catch {
replyText += " *Note: I was unable to DM the user the reason.*";
}
const audit = new Audit(targetUser.user.id, AuditType.Kick, reason, interaction.user.id, interaction.guildId);
await audit.Save(Audit, audit);
await interaction.reply(replyText);
}
}

View file

@ -104,54 +104,30 @@ export default class Timeout extends Command {
await channel.send({ embeds: [ logEmbed ]});
}
const dmEmbed = new EmbedBuilder()
.setColor(EmbedColours.Ok)
.setTitle(interaction.guild.name)
.setDescription("You have been given a warning by a moderator.")
.addFields([
{
name: "Reason",
value: reason || "*none*",
},
]);
let replyText = "Successfully warned user.";
try {
const dmChannel = await targetUser.user!.createDM();
await dmChannel.send({ embeds: [ dmEmbed ] });
} catch {
replyText += " *Note: I was unable to DM the user the reason.*";
}
// Create Audit
const audit = new Audit(targetUser.user.id, AuditType.Timeout, reason || "*none*", interaction.user.id, interaction.guildId);
await audit.Save(Audit, audit);
// DM User, if possible
const resultEmbed = new EmbedBuilder()
.setColor(EmbedColours.Ok)
.setDescription(`<@${targetUser.user.id}> has been timed out`);
const dmEmbed = new EmbedBuilder()
.setColor(EmbedColours.Ok)
.setDescription(`You have been timed out in ${interaction.guild.name}`)
.addFields([
{
name: "Reason",
value: reason || "*none*"
},
{
name: "Length",
value: timeLength.GetLengthShort(),
},
{
name: "Until",
value: timeLength.GetDateFromNow().toString(),
},
]);
try {
const dmChannel = await targetUser.user.createDM();
await dmChannel.send({ embeds: [ dmEmbed ]});
resultEmbed.addFields([
{
name: "DM Sent",
value: "true",
},
]);
} catch {
resultEmbed.addFields([
{
name: "DM Sent",
value: "false",
},
]);
}
// Success Reply
await interaction.reply({ embeds: [ resultEmbed ]});
await interaction.reply(replyText);
}
}

View file

@ -63,9 +63,29 @@ export default class Warn extends Command {
await channel.send({ embeds: [ logEmbed ]});
}
const dmEmbed = new EmbedBuilder()
.setColor(EmbedColours.Ok)
.setTitle(interaction.guild.name)
.setDescription("You have been given a warning by a moderator.")
.addFields([
{
name: "Reason",
value: reason,
},
]);
let replyText = "Successfully warned user.";
try {
const dmChannel = await targetUser.user!.createDM();
await dmChannel.send({ embeds: [ dmEmbed ] });
} catch {
replyText += " *Note: I was unable to DM the user the reason.*";
}
const audit = new Audit(targetUser.user.id, AuditType.Warn, reason, interaction.user.id, interaction.guildId);
await audit.Save(Audit, audit);
await interaction.reply('Successfully warned user.');
await interaction.reply(replyText);
}
}

View file

@ -0,0 +1,49 @@
import { Column, Entity, IsNull } from "typeorm";
import BaseEntity from "../../contracts/BaseEntity";
import AppDataSource from "../dataSources/appDataSource";
@Entity()
export default class Moon extends BaseEntity {
constructor(moonNumber: number, description: string, userId: string) {
super();
this.MoonNumber = moonNumber;
this.Description = description;
this.UserId = userId;
}
@Column()
MoonNumber: number;
@Column()
Description: string;
@Column({ nullable: true })
WhenArchived?: Date;
@Column()
UserId: string;
public static async FetchMoonsByUserId(userId: string): Promise<Moon[] | null> {
const repository = AppDataSource.getRepository(Moon);
const all = await repository.find({ where: { UserId: userId } });
return all;
}
public static async FetchPaginatedMoonsByUserId(userId: string, pageLength: number, page: number): Promise<[ Moon[], number ]> {
const rangeStart = page * pageLength;
const repository = AppDataSource.getRepository(Moon);
const moons = await repository.findAndCount({
where: { UserId: userId, WhenArchived: IsNull() },
order: { MoonNumber: "ASC" },
skip: rangeStart,
take: pageLength,
});
return moons;
}
}

View file

@ -0,0 +1,15 @@
import { MigrationInterface, QueryRunner } from "typeorm"
import MigrationHelper from "../../../helpers/MigrationHelper"
export class CreateMoon1719856023429 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
MigrationHelper.Up('1719856023429-CreateMoon', '3.3.0', [
"01-Moon",
], queryRunner);
}
public async down(queryRunner: QueryRunner): Promise<void> {
}
}

View file

@ -28,6 +28,9 @@ import AddLobby from "./commands/501231711271780357/Lobby/add";
import RemoveLobby from "./commands/501231711271780357/Lobby/remove";
import ListLobby from "./commands/501231711271780357/Lobby/list";
// Command Imports: Potato Talk
import Moons from "./commands/304276391837302787/moons";
// Event Imports
import GuildMemberAdd from "./events/MemberEvents/GuildMemberAdd";
import GuildMemberRemove from "./events/MemberEvents/GuildMemberRemove";
@ -38,6 +41,7 @@ import MessageCreate from "./events/MessageEvents/MessageCreate";
// Button Event Imports
import Verify from "./buttonEvents/verify";
import MoonsButtonEvent from "./buttonEvents/moons";
export default class Registry {
public static RegisterCommands() {
@ -68,12 +72,16 @@ export default class Registry {
CoreClient.RegisterCommand("listlobby", new ListLobby(), "501231711271780357");
CoreClient.RegisterCommand("entry", new Entry(), "501231711271780357");
// Exclusive Commands: Potato Talk
CoreClient.RegisterCommand("moons", new Moons(), "304276391837302787");
// Add Exclusive Commands to Test Server
CoreClient.RegisterCommand("lobby", new Lobby(), "442730357897429002");
CoreClient.RegisterCommand("addlobby", new AddLobby(), "442730357897429002");
CoreClient.RegisterCommand("removelobby", new RemoveLobby(), "442730357897429002");
CoreClient.RegisterCommand("listlobby", new ListLobby(), "442730357897429002");
CoreClient.RegisterCommand("entry", new Entry(), "442730357897429002");
CoreClient.RegisterCommand("moons", new Moons(), "442730357897429002");
}
public static RegisterEvents() {
@ -88,5 +96,6 @@ export default class Registry {
public static RegisterButtonEvents() {
CoreClient.RegisterButtonEvent("verify", new Verify());
CoreClient.RegisterButtonEvent("moons", new MoonsButtonEvent());
}
}