Skip to content

Commit e038b77

Browse files
Merge pull request #165 from Pdzly/feature/modmail-modnotes
2 parents dfc52f2 + 43046e4 commit e038b77

7 files changed

Lines changed: 1221 additions & 185 deletions

File tree

src/modules/modmail/modmail.command.ts

Lines changed: 166 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,179 @@
1+
import type { MessageActionRowComponentBuilder } from "@discordjs/builders";
12
import {
2-
type ActionRowBuilder,
3+
ActionRowBuilder,
34
ApplicationCommandOptionType,
45
ApplicationCommandType,
5-
type ButtonBuilder,
6+
ButtonBuilder,
7+
ButtonStyle,
68
type EmbedBuilder,
79
PermissionFlagsBits,
810
} from "discord.js";
911
import type { Command, ExecutableSubcommand } from "djs-slash-helper";
1012
import { config } from "../../Config.js";
1113
import { logger } from "../../logging.js";
14+
import { ModMailNote } from "../../store/models/ModMailNote.js";
1215
import { getMemberFromInteraction } from "../../util/member.js";
1316
import { safelyFetchUser } from "../../util/users.js";
1417
import {
18+
archiveModmailTicket,
1519
closeModMailTicketByModMail,
1620
createModMailDetails,
21+
createModMailNoteEmbed,
1722
getActiveModMailByChannel,
1823
getActiveModMailByUser,
24+
MODMAIL_DELETE_NOTE_ID,
25+
MODMAIL_EDIT_NOTE_ID,
26+
showModmailNotes,
27+
validateModmailPermissions,
1928
} from "./modmail.js";
2029

30+
const ArchiveSubCommand: ExecutableSubcommand = {
31+
type: ApplicationCommandOptionType.Subcommand,
32+
name: "archive",
33+
description: "Archive and close the current modmail thread.",
34+
async handle(interaction) {
35+
await interaction.deferReply({ flags: ["Ephemeral"] });
36+
37+
try {
38+
// Validate permissions
39+
if (!(await validateModmailPermissions(interaction))) {
40+
return;
41+
}
42+
43+
// Use the reusable archive function
44+
await archiveModmailTicket(
45+
interaction,
46+
interaction.channelId,
47+
interaction.client,
48+
);
49+
} catch (error) {
50+
logger.error(`Error creating modmail archive:`, error);
51+
await interaction.followUp({
52+
content: "An error occurred while creating the archive.",
53+
flags: ["Ephemeral"],
54+
});
55+
}
56+
},
57+
};
58+
59+
const NoteSubCommand: ExecutableSubcommand = {
60+
type: ApplicationCommandOptionType.Subcommand,
61+
name: "note",
62+
description: "Add a note for moderators about this ticket.",
63+
options: [
64+
{
65+
type: ApplicationCommandOptionType.String,
66+
name: "content",
67+
description: "The note content",
68+
required: true,
69+
},
70+
],
71+
async handle(interaction) {
72+
await interaction.deferReply({
73+
flags: ["Ephemeral"],
74+
});
75+
76+
if (!interaction.inGuild()) {
77+
await interaction.followUp({
78+
content: "This command can only be used in a guild",
79+
flags: ["Ephemeral"],
80+
});
81+
return;
82+
}
83+
84+
// Check if user has moderator permissions
85+
const member = await getMemberFromInteraction(interaction);
86+
if (!member?.permissions.has(PermissionFlagsBits.ManageMessages)) {
87+
await interaction.followUp({
88+
content: "You don't have permission to add notes",
89+
flags: ["Ephemeral"],
90+
});
91+
return;
92+
}
93+
94+
if (!interaction.channel?.isThread()) {
95+
await interaction.followUp({
96+
content: "This command can only be used in a modmail thread",
97+
flags: ["Ephemeral"],
98+
});
99+
return;
100+
}
101+
102+
const modMail = await getActiveModMailByChannel(
103+
BigInt(interaction.channelId),
104+
);
105+
106+
if (!modMail) {
107+
await interaction.followUp({
108+
content: "This command can only be used in a modmail thread",
109+
flags: ["Ephemeral"],
110+
});
111+
return;
112+
}
113+
114+
const content = interaction.options.getString("content", true);
115+
116+
const deleteButton = new ButtonBuilder()
117+
.setStyle(ButtonStyle.Danger)
118+
.setLabel("Delete Note")
119+
.setCustomId(MODMAIL_DELETE_NOTE_ID);
120+
121+
const editButton = new ButtonBuilder()
122+
.setStyle(ButtonStyle.Secondary)
123+
.setLabel("Edit Note")
124+
.setCustomId(MODMAIL_EDIT_NOTE_ID);
125+
126+
const row =
127+
new ActionRowBuilder<MessageActionRowComponentBuilder>().addComponents(
128+
editButton,
129+
deleteButton,
130+
);
131+
132+
const createdModMailNote = await ModMailNote.create({
133+
modMailTicketId: modMail.id,
134+
authorId: BigInt(interaction.user.id),
135+
content: content,
136+
});
137+
138+
const noteEmbed = await createModMailNoteEmbed(
139+
interaction.client,
140+
createdModMailNote,
141+
);
142+
143+
// Send the note to the channel
144+
await interaction.channel.send({
145+
embeds: [noteEmbed],
146+
components: [row],
147+
});
148+
149+
await interaction.followUp({
150+
content: "✅ Note added successfully",
151+
flags: ["Ephemeral"],
152+
});
153+
},
154+
};
155+
156+
const ListNotesSubCommand: ExecutableSubcommand = {
157+
type: ApplicationCommandOptionType.Subcommand,
158+
name: "listnotes",
159+
description: "List all notes for this ticket.",
160+
async handle(interaction) {
161+
await interaction.deferReply({
162+
flags: ["Ephemeral"],
163+
});
164+
165+
try {
166+
await showModmailNotes(interaction);
167+
} catch (error) {
168+
logger.error("Failed to show notes:", error);
169+
await interaction.followUp({
170+
content: "An error occurred while showing notes.",
171+
flags: ["Ephemeral"],
172+
});
173+
}
174+
},
175+
};
176+
21177
const DetailsSubCommand: ExecutableSubcommand = {
22178
type: ApplicationCommandOptionType.Subcommand,
23179
name: "details",
@@ -316,6 +472,13 @@ export const ModmailCommand: Command<ApplicationCommandType.ChatInput> = {
316472
name: "ticket",
317473
description: "Manage Tickets",
318474
type: ApplicationCommandType.ChatInput,
319-
options: [DetailsSubCommand, AssignSubCommand, CloseSubCommand],
475+
options: [
476+
DetailsSubCommand,
477+
AssignSubCommand,
478+
CloseSubCommand,
479+
NoteSubCommand,
480+
ListNotesSubCommand,
481+
ArchiveSubCommand,
482+
],
320483
handle() {},
321484
};

0 commit comments

Comments
 (0)