blob: af3098988b9c905fc587827740873e4707f8363c [file] [log] [blame]
Skyler Greyc634e2b2022-08-06 17:50:48 +01001import { HistorySchema } from "../../utils/database.js";
Skyler Grey75ea9172022-08-06 10:22:23 +01002import Discord, {
3 CommandInteraction,
4 GuildMember,
5 MessageActionRow,
6 MessageButton,
7 TextInputComponent
8} from "discord.js";
pineafan73a7c4a2022-07-24 10:38:04 +01009import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
pineafan4edb7762022-06-26 19:21:04 +010010import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
11import getEmojiByName from "../../utils/getEmojiByName.js";
12import client from "../../utils/client.js";
pineafan63fc5e22022-08-04 22:04:10 +010013import { modalInteractionCollector } from "../../utils/dualCollector.js";
14import pageIndicator from "../../utils/createPageIndicator.js";
pineafan4edb7762022-06-26 19:21:04 +010015
16const command = (builder: SlashCommandSubcommandBuilder) =>
17 builder
pineafan63fc5e22022-08-04 22:04:10 +010018 .setName("info")
19 .setDescription("Shows moderator information about a user")
Skyler Grey75ea9172022-08-06 10:22:23 +010020 .addUserOption((option) =>
Skyler Grey11236ba2022-08-08 21:13:33 +010021 option.setName("user").setDescription("The user to get information about").setRequired(true)
Skyler Grey75ea9172022-08-06 10:22:23 +010022 );
pineafan4edb7762022-06-26 19:21:04 +010023
pineafan4edb7762022-06-26 19:21:04 +010024const types = {
Skyler Grey75ea9172022-08-06 10:22:23 +010025 warn: { emoji: "PUNISH.WARN.YELLOW", text: "Warned" },
26 mute: { emoji: "PUNISH.MUTE.YELLOW", text: "Muted" },
27 unmute: { emoji: "PUNISH.MUTE.GREEN", text: "Unmuted" },
28 join: { emoji: "MEMBER.JOIN", text: "Joined" },
29 leave: { emoji: "MEMBER.LEAVE", text: "Left" },
30 kick: { emoji: "MEMBER.KICK", text: "Kicked" },
31 softban: { emoji: "PUNISH.SOFTBAN", text: "Softbanned" },
32 ban: { emoji: "MEMBER.BAN", text: "Banned" },
33 unban: { emoji: "MEMBER.UNBAN", text: "Unbanned" },
34 purge: { emoji: "PUNISH.CLEARHISTORY", text: "Messages cleared" },
35 nickname: { emoji: "PUNISH.NICKNAME.YELLOW", text: "Nickname changed" }
pineafan63fc5e22022-08-04 22:04:10 +010036};
pineafan4edb7762022-06-26 19:21:04 +010037
38function historyToString(history: HistorySchema) {
Skyler Grey11236ba2022-08-08 21:13:33 +010039 let s = `${getEmojiByName(types[history.type].emoji)} ${history.amount ? history.amount + " " : ""}${
40 types[history.type].text
41 } on <t:${Math.round(history.occurredAt.getTime() / 1000)}:F>`;
Skyler Grey75ea9172022-08-06 10:22:23 +010042 if (history.moderator) {
43 s += ` by <@${history.moderator}>`;
44 }
45 if (history.reason) {
46 s += `\n**Reason:**\n> ${history.reason}`;
47 }
48 if (history.before) {
49 s += `\n**Before:**\n> ${history.before}`;
50 }
51 if (history.after) {
52 s += `\n**After:**\n> ${history.after}`;
53 }
pineafan4edb7762022-06-26 19:21:04 +010054 return s + "\n";
55}
56
pineafan4edb7762022-06-26 19:21:04 +010057class TimelineSection {
58 name: string;
Skyler Grey75ea9172022-08-06 10:22:23 +010059 content: { data: HistorySchema; rendered: string }[] = [];
pineafan4edb7762022-06-26 19:21:04 +010060
Skyler Grey75ea9172022-08-06 10:22:23 +010061 addContent = (content: { data: HistorySchema; rendered: string }) => {
62 this.content.push(content);
63 return this;
64 };
65 contentLength = () => {
66 return this.content.reduce((acc, cur) => acc + cur.rendered.length, 0);
67 };
pineafan4edb7762022-06-26 19:21:04 +010068 generateName = () => {
Skyler Grey11236ba2022-08-08 21:13:33 +010069 const first = Math.round(this.content[0].data.occurredAt.getTime() / 1000);
70 const last = Math.round(this.content[this.content.length - 1].data.occurredAt.getTime() / 1000);
Skyler Grey75ea9172022-08-06 10:22:23 +010071 if (first === last) {
72 return (this.name = `<t:${first}:F>`);
73 }
74 return (this.name = `<t:${first}:F> - <t:${last}:F>`);
pineafan63fc5e22022-08-04 22:04:10 +010075 };
pineafan4edb7762022-06-26 19:21:04 +010076}
77
Skyler Grey75ea9172022-08-06 10:22:23 +010078const monthNames = [
79 "January",
80 "February",
81 "March",
82 "April",
83 "May",
84 "June",
85 "July",
86 "August",
87 "September",
88 "October",
89 "November",
90 "December"
91];
pineafan4edb7762022-06-26 19:21:04 +010092
93async function showHistory(member, interaction: CommandInteraction) {
94 let currentYear = new Date().getFullYear();
95 let pageIndex = null;
96 let m, history, current;
97 let refresh = true;
98 let filteredTypes = [];
99 let openFilterPane = false;
100 while (true) {
101 if (refresh) {
Skyler Grey11236ba2022-08-08 21:13:33 +0100102 history = await client.database.history.read(member.guild.id, member.id, currentYear);
103 history = history.sort((a, b) => b.occurredAt.getTime() - a.occurredAt.getTime()).reverse();
pineafan4edb7762022-06-26 19:21:04 +0100104 if (openFilterPane) {
pineafan63fc5e22022-08-04 22:04:10 +0100105 let tempFilteredTypes = filteredTypes;
Skyler Grey75ea9172022-08-06 10:22:23 +0100106 if (filteredTypes.length === 0) {
107 tempFilteredTypes = Object.keys(types);
108 }
Skyler Grey11236ba2022-08-08 21:13:33 +0100109 history = history.filter((h) => tempFilteredTypes.includes(h.type));
pineafan63fc5e22022-08-04 22:04:10 +0100110 }
pineafan4edb7762022-06-26 19:21:04 +0100111 refresh = false;
112 }
pineafan63fc5e22022-08-04 22:04:10 +0100113 const groups: TimelineSection[] = [];
pineafan4edb7762022-06-26 19:21:04 +0100114 if (history.length > 0) {
pineafan63fc5e22022-08-04 22:04:10 +0100115 current = new TimelineSection();
Skyler Grey75ea9172022-08-06 10:22:23 +0100116 history.forEach((event) => {
Skyler Grey11236ba2022-08-08 21:13:33 +0100117 if (current.contentLength() + historyToString(event).length > 2000 || current.content.length === 5) {
pineafan4edb7762022-06-26 19:21:04 +0100118 groups.push(current);
119 current.generateName();
120 current = new TimelineSection();
121 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100122 current.addContent({
123 data: event,
124 rendered: historyToString(event)
125 });
pineafan4edb7762022-06-26 19:21:04 +0100126 });
127 current.generateName();
128 groups.push(current);
Skyler Grey75ea9172022-08-06 10:22:23 +0100129 if (pageIndex === null) {
130 pageIndex = groups.length - 1;
131 }
pineafan4edb7762022-06-26 19:21:04 +0100132 }
pineafan63fc5e22022-08-04 22:04:10 +0100133 const components = (
Skyler Grey75ea9172022-08-06 10:22:23 +0100134 openFilterPane
135 ? [
136 new MessageActionRow().addComponents([
137 new Discord.MessageSelectMenu()
138 .setOptions(
139 Object.entries(types).map(([key, value]) => ({
140 label: value.text,
141 value: key,
142 default: filteredTypes.includes(key),
Skyler Grey11236ba2022-08-08 21:13:33 +0100143 emoji: client.emojis.resolve(getEmojiByName(value.emoji, "id"))
Skyler Grey75ea9172022-08-06 10:22:23 +0100144 }))
145 )
146 .setMinValues(1)
147 .setMaxValues(Object.keys(types).length)
148 .setCustomId("filter")
149 .setPlaceholder("Select at least one event")
150 ])
151 ]
152 : []
153 ).concat([
pineafan4edb7762022-06-26 19:21:04 +0100154 new MessageActionRow().addComponents([
155 new MessageButton()
156 .setCustomId("prevYear")
157 .setLabel((currentYear - 1).toString())
158 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
159 .setStyle("SECONDARY"),
Skyler Grey11236ba2022-08-08 21:13:33 +0100160 new MessageButton().setCustomId("prevPage").setLabel("Previous page").setStyle("PRIMARY"),
161 new MessageButton().setCustomId("today").setLabel("Today").setStyle("PRIMARY"),
pineafan4edb7762022-06-26 19:21:04 +0100162 new MessageButton()
163 .setCustomId("nextPage")
164 .setLabel("Next page")
165 .setStyle("PRIMARY")
Skyler Grey11236ba2022-08-08 21:13:33 +0100166 .setDisabled(pageIndex >= groups.length - 1 && currentYear === new Date().getFullYear()),
pineafan4edb7762022-06-26 19:21:04 +0100167 new MessageButton()
168 .setCustomId("nextYear")
169 .setLabel((currentYear + 1).toString())
170 .setEmoji(getEmojiByName("CONTROL.RIGHT", "id"))
171 .setStyle("SECONDARY")
pineafan63fc5e22022-08-04 22:04:10 +0100172 .setDisabled(currentYear === new Date().getFullYear())
Skyler Grey75ea9172022-08-06 10:22:23 +0100173 ]),
174 new MessageActionRow().addComponents([
pineafan4edb7762022-06-26 19:21:04 +0100175 new MessageButton()
176 .setLabel("Mod notes")
177 .setCustomId("modNotes")
178 .setStyle("PRIMARY")
179 .setEmoji(getEmojiByName("ICONS.EDIT", "id")),
180 new MessageButton()
181 .setLabel("Filter")
182 .setCustomId("openFilter")
183 .setStyle(openFilterPane ? "SUCCESS" : "PRIMARY")
184 .setEmoji(getEmojiByName("ICONS.FILTER", "id"))
185 ])
pineafan63fc5e22022-08-04 22:04:10 +0100186 ]);
Skyler Grey75ea9172022-08-06 10:22:23 +0100187 const end =
188 "\n\nJanuary " +
189 currentYear.toString() +
Skyler Grey11236ba2022-08-08 21:13:33 +0100190 pageIndicator(Math.max(groups.length, 1), groups.length === 0 ? 1 : pageIndex) +
191 (currentYear === new Date().getFullYear() ? monthNames[new Date().getMonth()] : "December") +
Skyler Grey75ea9172022-08-06 10:22:23 +0100192 " " +
193 currentYear.toString();
pineafan4edb7762022-06-26 19:21:04 +0100194 if (groups.length > 0) {
pineafan63fc5e22022-08-04 22:04:10 +0100195 const toRender = groups[Math.min(pageIndex, groups.length - 1)];
Skyler Grey75ea9172022-08-06 10:22:23 +0100196 m = await interaction.editReply({
197 embeds: [
198 new EmojiEmbed()
199 .setEmoji("MEMBER.JOIN")
Skyler Grey11236ba2022-08-08 21:13:33 +0100200 .setTitle("Moderation history for " + member.user.username)
Skyler Grey75ea9172022-08-06 10:22:23 +0100201 .setDescription(
Skyler Grey11236ba2022-08-08 21:13:33 +0100202 `**${toRender.name}**\n\n` + toRender.content.map((c) => c.rendered).join("\n") + end
Skyler Grey75ea9172022-08-06 10:22:23 +0100203 )
204 .setStatus("Success")
205 .setFooter({
Skyler Grey11236ba2022-08-08 21:13:33 +0100206 text: openFilterPane && filteredTypes.length ? "Filters are currently enabled" : ""
Skyler Grey75ea9172022-08-06 10:22:23 +0100207 })
208 ],
209 components: components
210 });
pineafan4edb7762022-06-26 19:21:04 +0100211 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100212 m = await interaction.editReply({
213 embeds: [
214 new EmojiEmbed()
215 .setEmoji("MEMBER.JOIN")
Skyler Grey11236ba2022-08-08 21:13:33 +0100216 .setTitle("Moderation history for " + member.user.username)
217 .setDescription(`**${currentYear}**\n\n*No events*` + "\n\n" + end)
Skyler Grey75ea9172022-08-06 10:22:23 +0100218 .setStatus("Success")
219 .setFooter({
Skyler Grey11236ba2022-08-08 21:13:33 +0100220 text: openFilterPane && filteredTypes.length ? "Filters are currently enabled" : ""
Skyler Grey75ea9172022-08-06 10:22:23 +0100221 })
222 ],
223 components: components
224 });
pineafan4edb7762022-06-26 19:21:04 +0100225 }
226 let i;
227 try {
228 i = await m.awaitMessageComponent({ time: 300000 });
229 } catch (e) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100230 interaction.editReply({
231 embeds: [
232 new EmojiEmbed()
233 .setEmoji("MEMBER.JOIN")
Skyler Grey11236ba2022-08-08 21:13:33 +0100234 .setTitle("Moderation history for " + member.user.username)
Skyler Grey75ea9172022-08-06 10:22:23 +0100235 .setDescription(m.embeds[0].description)
236 .setStatus("Danger")
237 .setFooter({ text: "Message timed out" })
238 ]
239 });
pineafan63fc5e22022-08-04 22:04:10 +0100240 return 0;
pineafan4edb7762022-06-26 19:21:04 +0100241 }
pineafan63fc5e22022-08-04 22:04:10 +0100242 i.deferUpdate();
pineafan4edb7762022-06-26 19:21:04 +0100243 if (i.customId === "filter") {
244 filteredTypes = i.values;
245 pageIndex = null;
246 refresh = true;
247 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100248 if (i.customId === "prevYear") {
249 currentYear--;
250 pageIndex = null;
251 refresh = true;
252 }
253 if (i.customId === "nextYear") {
254 currentYear++;
255 pageIndex = null;
256 refresh = true;
257 }
pineafan4edb7762022-06-26 19:21:04 +0100258 if (i.customId === "prevPage") {
259 pageIndex--;
Skyler Grey75ea9172022-08-06 10:22:23 +0100260 if (pageIndex < 0) {
261 pageIndex = null;
262 currentYear--;
263 refresh = true;
264 }
pineafan4edb7762022-06-26 19:21:04 +0100265 }
266 if (i.customId === "nextPage") {
267 pageIndex++;
Skyler Grey75ea9172022-08-06 10:22:23 +0100268 if (pageIndex >= groups.length) {
269 pageIndex = 0;
270 currentYear++;
271 refresh = true;
272 }
pineafan4edb7762022-06-26 19:21:04 +0100273 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100274 if (i.customId === "today") {
275 currentYear = new Date().getFullYear();
276 pageIndex = null;
277 refresh = true;
278 }
279 if (i.customId === "modNotes") {
280 return 1;
281 }
282 if (i.customId === "openFilter") {
283 openFilterPane = !openFilterPane;
284 refresh = true;
285 }
pineafan4edb7762022-06-26 19:21:04 +0100286 }
287}
288
Skyler Grey11236ba2022-08-08 21:13:33 +0100289const callback = async (interaction: CommandInteraction): Promise<void | unknown> => {
pineafan4edb7762022-06-26 19:21:04 +0100290 let m;
Skyler Grey75ea9172022-08-06 10:22:23 +0100291 const member = interaction.options.getMember("user") as Discord.GuildMember;
292 await interaction.reply({
Skyler Grey11236ba2022-08-08 21:13:33 +0100293 embeds: [new EmojiEmbed().setEmoji("NUCLEUS.LOADING").setTitle("Downloading Data").setStatus("Danger")],
Skyler Grey75ea9172022-08-06 10:22:23 +0100294 ephemeral: true,
295 fetchReply: true
296 });
pineafan4edb7762022-06-26 19:21:04 +0100297 let note;
298 let firstLoad = true;
299 while (true) {
300 note = await client.database.notes.read(member.guild.id, member.id);
Skyler Grey75ea9172022-08-06 10:22:23 +0100301 if (firstLoad && !note) {
302 await showHistory(member, interaction);
303 }
pineafan4edb7762022-06-26 19:21:04 +0100304 firstLoad = false;
Skyler Grey75ea9172022-08-06 10:22:23 +0100305 m = await interaction.editReply({
306 embeds: [
307 new EmojiEmbed()
308 .setEmoji("MEMBER.JOIN")
309 .setTitle("Mod notes for " + member.user.username)
310 .setDescription(note ? note : "*No note set*")
311 .setStatus("Success")
312 ],
313 components: [
314 new MessageActionRow().addComponents([
315 new MessageButton()
316 .setLabel(`${note ? "Modify" : "Create"} note`)
317 .setStyle("PRIMARY")
318 .setCustomId("modify")
319 .setEmoji(getEmojiByName("ICONS.EDIT", "id")),
320 new MessageButton()
321 .setLabel("View moderation history")
322 .setStyle("PRIMARY")
323 .setCustomId("history")
324 .setEmoji(getEmojiByName("ICONS.HISTORY", "id"))
325 ])
326 ]
327 });
pineafan4edb7762022-06-26 19:21:04 +0100328 let i;
329 try {
330 i = await m.awaitMessageComponent({ time: 300000 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100331 } catch (e) {
332 return;
333 }
pineafan4edb7762022-06-26 19:21:04 +0100334 if (i.customId === "modify") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100335 await i.showModal(
336 new Discord.Modal()
337 .setCustomId("modal")
338 .setTitle("Editing moderator note")
339 .addComponents(
340 new MessageActionRow<TextInputComponent>().addComponents(
341 new TextInputComponent()
342 .setCustomId("note")
343 .setLabel("Note")
344 .setMaxLength(4000)
345 .setRequired(false)
346 .setStyle("PARAGRAPH")
347 .setValue(note ? note : "")
348 )
349 )
350 );
pineafan4edb7762022-06-26 19:21:04 +0100351 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100352 embeds: [
353 new EmojiEmbed()
354 .setTitle("Mod notes for " + member.user.username)
Skyler Grey11236ba2022-08-08 21:13:33 +0100355 .setDescription("Modal opened. If you can't see it, click back and try again.")
Skyler Grey75ea9172022-08-06 10:22:23 +0100356 .setStatus("Success")
357 .setEmoji("GUILD.TICKET.OPEN")
358 ],
359 components: [
360 new MessageActionRow().addComponents([
361 new MessageButton()
362 .setLabel("Back")
363 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
364 .setStyle("PRIMARY")
365 .setCustomId("back")
366 ])
367 ]
pineafan4edb7762022-06-26 19:21:04 +0100368 });
369 let out;
370 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100371 out = await modalInteractionCollector(
372 m,
373 (m) => m.channel.id === interaction.channel.id,
374 (m) => m.customId === "modify"
375 );
376 } catch (e) {
377 continue;
378 }
pineafan4edb7762022-06-26 19:21:04 +0100379 if (out.fields) {
pineafan63fc5e22022-08-04 22:04:10 +0100380 const toAdd = out.fields.getTextInputValue("note") || null;
Skyler Grey11236ba2022-08-08 21:13:33 +0100381 await client.database.notes.create(member.guild.id, member.id, toAdd);
Skyler Grey75ea9172022-08-06 10:22:23 +0100382 } else {
383 continue;
384 }
pineafan4edb7762022-06-26 19:21:04 +0100385 } else if (i.customId === "history") {
386 i.deferUpdate();
Skyler Grey75ea9172022-08-06 10:22:23 +0100387 if (!(await showHistory(member, interaction))) return;
pineafan4edb7762022-06-26 19:21:04 +0100388 }
389 }
pineafan63fc5e22022-08-04 22:04:10 +0100390};
pineafan4edb7762022-06-26 19:21:04 +0100391
pineafanbd02b4a2022-08-05 22:01:38 +0100392const check = (interaction: CommandInteraction) => {
Skyler Grey75ea9172022-08-06 10:22:23 +0100393 const member = interaction.member as GuildMember;
Skyler Grey11236ba2022-08-08 21:13:33 +0100394 if (!member.permissions.has("MODERATE_MEMBERS")) throw "You do not have the *Moderate Members* permission";
pineafan63fc5e22022-08-04 22:04:10 +0100395 return true;
396};
pineafan4edb7762022-06-26 19:21:04 +0100397
398export { command };
399export { callback };
Skyler Grey75ea9172022-08-06 10:22:23 +0100400export { check };