blob: 86a4c4a2c357421f7edea090f7922b39c7a30970 [file] [log] [blame]
pineafan63fc5e22022-08-04 22:04:10 +01001import { HistorySchema } from "../../utils/database";
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) =>
21 option
22 .setName("user")
23 .setDescription("The user to get information about")
24 .setRequired(true)
25 );
pineafan4edb7762022-06-26 19:21:04 +010026
pineafan4edb7762022-06-26 19:21:04 +010027const types = {
Skyler Grey75ea9172022-08-06 10:22:23 +010028 warn: { emoji: "PUNISH.WARN.YELLOW", text: "Warned" },
29 mute: { emoji: "PUNISH.MUTE.YELLOW", text: "Muted" },
30 unmute: { emoji: "PUNISH.MUTE.GREEN", text: "Unmuted" },
31 join: { emoji: "MEMBER.JOIN", text: "Joined" },
32 leave: { emoji: "MEMBER.LEAVE", text: "Left" },
33 kick: { emoji: "MEMBER.KICK", text: "Kicked" },
34 softban: { emoji: "PUNISH.SOFTBAN", text: "Softbanned" },
35 ban: { emoji: "MEMBER.BAN", text: "Banned" },
36 unban: { emoji: "MEMBER.UNBAN", text: "Unbanned" },
37 purge: { emoji: "PUNISH.CLEARHISTORY", text: "Messages cleared" },
38 nickname: { emoji: "PUNISH.NICKNAME.YELLOW", text: "Nickname changed" }
pineafan63fc5e22022-08-04 22:04:10 +010039};
pineafan4edb7762022-06-26 19:21:04 +010040
41function historyToString(history: HistorySchema) {
42 let s = `${getEmojiByName(types[history.type].emoji)} ${
Skyler Grey75ea9172022-08-06 10:22:23 +010043 history.amount ? history.amount + " " : ""
44 }${types[history.type].text} on <t:${Math.round(
45 history.occurredAt.getTime() / 1000
46 )}:F>`;
47 if (history.moderator) {
48 s += ` by <@${history.moderator}>`;
49 }
50 if (history.reason) {
51 s += `\n**Reason:**\n> ${history.reason}`;
52 }
53 if (history.before) {
54 s += `\n**Before:**\n> ${history.before}`;
55 }
56 if (history.after) {
57 s += `\n**After:**\n> ${history.after}`;
58 }
pineafan4edb7762022-06-26 19:21:04 +010059 return s + "\n";
60}
61
pineafan4edb7762022-06-26 19:21:04 +010062class TimelineSection {
63 name: string;
Skyler Grey75ea9172022-08-06 10:22:23 +010064 content: { data: HistorySchema; rendered: string }[] = [];
pineafan4edb7762022-06-26 19:21:04 +010065
Skyler Grey75ea9172022-08-06 10:22:23 +010066 addContent = (content: { data: HistorySchema; rendered: string }) => {
67 this.content.push(content);
68 return this;
69 };
70 contentLength = () => {
71 return this.content.reduce((acc, cur) => acc + cur.rendered.length, 0);
72 };
pineafan4edb7762022-06-26 19:21:04 +010073 generateName = () => {
Skyler Grey75ea9172022-08-06 10:22:23 +010074 const first = Math.round(
75 this.content[0].data.occurredAt.getTime() / 1000
76 );
77 const last = Math.round(
78 this.content[this.content.length - 1].data.occurredAt.getTime() /
79 1000
80 );
81 if (first === last) {
82 return (this.name = `<t:${first}:F>`);
83 }
84 return (this.name = `<t:${first}:F> - <t:${last}:F>`);
pineafan63fc5e22022-08-04 22:04:10 +010085 };
pineafan4edb7762022-06-26 19:21:04 +010086}
87
Skyler Grey75ea9172022-08-06 10:22:23 +010088const monthNames = [
89 "January",
90 "February",
91 "March",
92 "April",
93 "May",
94 "June",
95 "July",
96 "August",
97 "September",
98 "October",
99 "November",
100 "December"
101];
pineafan4edb7762022-06-26 19:21:04 +0100102
103async function showHistory(member, interaction: CommandInteraction) {
104 let currentYear = new Date().getFullYear();
105 let pageIndex = null;
106 let m, history, current;
107 let refresh = true;
108 let filteredTypes = [];
109 let openFilterPane = false;
110 while (true) {
111 if (refresh) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100112 history = await client.database.history.read(
113 member.guild.id,
114 member.id,
115 currentYear
116 );
117 history = history
118 .sort((a, b) => b.occurredAt.getTime() - a.occurredAt.getTime())
119 .reverse();
pineafan4edb7762022-06-26 19:21:04 +0100120 if (openFilterPane) {
pineafan63fc5e22022-08-04 22:04:10 +0100121 let tempFilteredTypes = filteredTypes;
Skyler Grey75ea9172022-08-06 10:22:23 +0100122 if (filteredTypes.length === 0) {
123 tempFilteredTypes = Object.keys(types);
124 }
125 history = history.filter((h) =>
126 tempFilteredTypes.includes(h.type)
127 );
pineafan63fc5e22022-08-04 22:04:10 +0100128 }
pineafan4edb7762022-06-26 19:21:04 +0100129 refresh = false;
130 }
pineafan63fc5e22022-08-04 22:04:10 +0100131 const groups: TimelineSection[] = [];
pineafan4edb7762022-06-26 19:21:04 +0100132 if (history.length > 0) {
pineafan63fc5e22022-08-04 22:04:10 +0100133 current = new TimelineSection();
Skyler Grey75ea9172022-08-06 10:22:23 +0100134 history.forEach((event) => {
135 if (
136 current.contentLength() + historyToString(event).length >
137 2000 ||
138 current.content.length === 5
139 ) {
pineafan4edb7762022-06-26 19:21:04 +0100140 groups.push(current);
141 current.generateName();
142 current = new TimelineSection();
143 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100144 current.addContent({
145 data: event,
146 rendered: historyToString(event)
147 });
pineafan4edb7762022-06-26 19:21:04 +0100148 });
149 current.generateName();
150 groups.push(current);
Skyler Grey75ea9172022-08-06 10:22:23 +0100151 if (pageIndex === null) {
152 pageIndex = groups.length - 1;
153 }
pineafan4edb7762022-06-26 19:21:04 +0100154 }
pineafan63fc5e22022-08-04 22:04:10 +0100155 const components = (
Skyler Grey75ea9172022-08-06 10:22:23 +0100156 openFilterPane
157 ? [
158 new MessageActionRow().addComponents([
159 new Discord.MessageSelectMenu()
160 .setOptions(
161 Object.entries(types).map(([key, value]) => ({
162 label: value.text,
163 value: key,
164 default: filteredTypes.includes(key),
165 emoji: client.emojis.resolve(
166 getEmojiByName(value.emoji, "id")
167 )
168 }))
169 )
170 .setMinValues(1)
171 .setMaxValues(Object.keys(types).length)
172 .setCustomId("filter")
173 .setPlaceholder("Select at least one event")
174 ])
175 ]
176 : []
177 ).concat([
pineafan4edb7762022-06-26 19:21:04 +0100178 new MessageActionRow().addComponents([
179 new MessageButton()
180 .setCustomId("prevYear")
181 .setLabel((currentYear - 1).toString())
182 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
183 .setStyle("SECONDARY"),
184 new MessageButton()
185 .setCustomId("prevPage")
186 .setLabel("Previous page")
187 .setStyle("PRIMARY"),
188 new MessageButton()
189 .setCustomId("today")
190 .setLabel("Today")
191 .setStyle("PRIMARY"),
192 new MessageButton()
193 .setCustomId("nextPage")
194 .setLabel("Next page")
195 .setStyle("PRIMARY")
Skyler Grey75ea9172022-08-06 10:22:23 +0100196 .setDisabled(
197 pageIndex >= groups.length - 1 &&
198 currentYear === new Date().getFullYear()
199 ),
pineafan4edb7762022-06-26 19:21:04 +0100200 new MessageButton()
201 .setCustomId("nextYear")
202 .setLabel((currentYear + 1).toString())
203 .setEmoji(getEmojiByName("CONTROL.RIGHT", "id"))
204 .setStyle("SECONDARY")
pineafan63fc5e22022-08-04 22:04:10 +0100205 .setDisabled(currentYear === new Date().getFullYear())
Skyler Grey75ea9172022-08-06 10:22:23 +0100206 ]),
207 new MessageActionRow().addComponents([
pineafan4edb7762022-06-26 19:21:04 +0100208 new MessageButton()
209 .setLabel("Mod notes")
210 .setCustomId("modNotes")
211 .setStyle("PRIMARY")
212 .setEmoji(getEmojiByName("ICONS.EDIT", "id")),
213 new MessageButton()
214 .setLabel("Filter")
215 .setCustomId("openFilter")
216 .setStyle(openFilterPane ? "SUCCESS" : "PRIMARY")
217 .setEmoji(getEmojiByName("ICONS.FILTER", "id"))
218 ])
pineafan63fc5e22022-08-04 22:04:10 +0100219 ]);
Skyler Grey75ea9172022-08-06 10:22:23 +0100220 const end =
221 "\n\nJanuary " +
222 currentYear.toString() +
223 pageIndicator(
224 Math.max(groups.length, 1),
225 groups.length === 0 ? 1 : pageIndex
226 ) +
227 (currentYear === new Date().getFullYear()
228 ? monthNames[new Date().getMonth()]
229 : "December") +
230 " " +
231 currentYear.toString();
pineafan4edb7762022-06-26 19:21:04 +0100232 if (groups.length > 0) {
pineafan63fc5e22022-08-04 22:04:10 +0100233 const toRender = groups[Math.min(pageIndex, groups.length - 1)];
Skyler Grey75ea9172022-08-06 10:22:23 +0100234 m = await interaction.editReply({
235 embeds: [
236 new EmojiEmbed()
237 .setEmoji("MEMBER.JOIN")
238 .setTitle(
239 "Moderation history for " + member.user.username
240 )
241 .setDescription(
242 `**${toRender.name}**\n\n` +
243 toRender.content
244 .map((c) => c.rendered)
245 .join("\n") +
246 end
247 )
248 .setStatus("Success")
249 .setFooter({
250 text:
251 openFilterPane && filteredTypes.length
252 ? "Filters are currently enabled"
253 : ""
254 })
255 ],
256 components: components
257 });
pineafan4edb7762022-06-26 19:21:04 +0100258 } else {
Skyler Grey75ea9172022-08-06 10:22:23 +0100259 m = await interaction.editReply({
260 embeds: [
261 new EmojiEmbed()
262 .setEmoji("MEMBER.JOIN")
263 .setTitle(
264 "Moderation history for " + member.user.username
265 )
266 .setDescription(
267 `**${currentYear}**\n\n*No events*` + "\n\n" + end
268 )
269 .setStatus("Success")
270 .setFooter({
271 text:
272 openFilterPane && filteredTypes.length
273 ? "Filters are currently enabled"
274 : ""
275 })
276 ],
277 components: components
278 });
pineafan4edb7762022-06-26 19:21:04 +0100279 }
280 let i;
281 try {
282 i = await m.awaitMessageComponent({ time: 300000 });
283 } catch (e) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100284 interaction.editReply({
285 embeds: [
286 new EmojiEmbed()
287 .setEmoji("MEMBER.JOIN")
288 .setTitle(
289 "Moderation history for " + member.user.username
290 )
291 .setDescription(m.embeds[0].description)
292 .setStatus("Danger")
293 .setFooter({ text: "Message timed out" })
294 ]
295 });
pineafan63fc5e22022-08-04 22:04:10 +0100296 return 0;
pineafan4edb7762022-06-26 19:21:04 +0100297 }
pineafan63fc5e22022-08-04 22:04:10 +0100298 i.deferUpdate();
pineafan4edb7762022-06-26 19:21:04 +0100299 if (i.customId === "filter") {
300 filteredTypes = i.values;
301 pageIndex = null;
302 refresh = true;
303 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100304 if (i.customId === "prevYear") {
305 currentYear--;
306 pageIndex = null;
307 refresh = true;
308 }
309 if (i.customId === "nextYear") {
310 currentYear++;
311 pageIndex = null;
312 refresh = true;
313 }
pineafan4edb7762022-06-26 19:21:04 +0100314 if (i.customId === "prevPage") {
315 pageIndex--;
Skyler Grey75ea9172022-08-06 10:22:23 +0100316 if (pageIndex < 0) {
317 pageIndex = null;
318 currentYear--;
319 refresh = true;
320 }
pineafan4edb7762022-06-26 19:21:04 +0100321 }
322 if (i.customId === "nextPage") {
323 pageIndex++;
Skyler Grey75ea9172022-08-06 10:22:23 +0100324 if (pageIndex >= groups.length) {
325 pageIndex = 0;
326 currentYear++;
327 refresh = true;
328 }
pineafan4edb7762022-06-26 19:21:04 +0100329 }
Skyler Grey75ea9172022-08-06 10:22:23 +0100330 if (i.customId === "today") {
331 currentYear = new Date().getFullYear();
332 pageIndex = null;
333 refresh = true;
334 }
335 if (i.customId === "modNotes") {
336 return 1;
337 }
338 if (i.customId === "openFilter") {
339 openFilterPane = !openFilterPane;
340 refresh = true;
341 }
pineafan4edb7762022-06-26 19:21:04 +0100342 }
343}
344
Skyler Grey75ea9172022-08-06 10:22:23 +0100345const callback = async (
346 interaction: CommandInteraction
347): Promise<void | unknown> => {
pineafan4edb7762022-06-26 19:21:04 +0100348 let m;
Skyler Grey75ea9172022-08-06 10:22:23 +0100349 const member = interaction.options.getMember("user") as Discord.GuildMember;
350 await interaction.reply({
351 embeds: [
352 new EmojiEmbed()
353 .setEmoji("NUCLEUS.LOADING")
354 .setTitle("Downloading Data")
355 .setStatus("Danger")
356 ],
357 ephemeral: true,
358 fetchReply: true
359 });
pineafan4edb7762022-06-26 19:21:04 +0100360 let note;
361 let firstLoad = true;
362 while (true) {
363 note = await client.database.notes.read(member.guild.id, member.id);
Skyler Grey75ea9172022-08-06 10:22:23 +0100364 if (firstLoad && !note) {
365 await showHistory(member, interaction);
366 }
pineafan4edb7762022-06-26 19:21:04 +0100367 firstLoad = false;
Skyler Grey75ea9172022-08-06 10:22:23 +0100368 m = await interaction.editReply({
369 embeds: [
370 new EmojiEmbed()
371 .setEmoji("MEMBER.JOIN")
372 .setTitle("Mod notes for " + member.user.username)
373 .setDescription(note ? note : "*No note set*")
374 .setStatus("Success")
375 ],
376 components: [
377 new MessageActionRow().addComponents([
378 new MessageButton()
379 .setLabel(`${note ? "Modify" : "Create"} note`)
380 .setStyle("PRIMARY")
381 .setCustomId("modify")
382 .setEmoji(getEmojiByName("ICONS.EDIT", "id")),
383 new MessageButton()
384 .setLabel("View moderation history")
385 .setStyle("PRIMARY")
386 .setCustomId("history")
387 .setEmoji(getEmojiByName("ICONS.HISTORY", "id"))
388 ])
389 ]
390 });
pineafan4edb7762022-06-26 19:21:04 +0100391 let i;
392 try {
393 i = await m.awaitMessageComponent({ time: 300000 });
Skyler Grey75ea9172022-08-06 10:22:23 +0100394 } catch (e) {
395 return;
396 }
pineafan4edb7762022-06-26 19:21:04 +0100397 if (i.customId === "modify") {
Skyler Grey75ea9172022-08-06 10:22:23 +0100398 await i.showModal(
399 new Discord.Modal()
400 .setCustomId("modal")
401 .setTitle("Editing moderator note")
402 .addComponents(
403 new MessageActionRow<TextInputComponent>().addComponents(
404 new TextInputComponent()
405 .setCustomId("note")
406 .setLabel("Note")
407 .setMaxLength(4000)
408 .setRequired(false)
409 .setStyle("PARAGRAPH")
410 .setValue(note ? note : "")
411 )
412 )
413 );
pineafan4edb7762022-06-26 19:21:04 +0100414 await interaction.editReply({
Skyler Grey75ea9172022-08-06 10:22:23 +0100415 embeds: [
416 new EmojiEmbed()
417 .setTitle("Mod notes for " + member.user.username)
418 .setDescription(
419 "Modal opened. If you can't see it, click back and try again."
420 )
421 .setStatus("Success")
422 .setEmoji("GUILD.TICKET.OPEN")
423 ],
424 components: [
425 new MessageActionRow().addComponents([
426 new MessageButton()
427 .setLabel("Back")
428 .setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
429 .setStyle("PRIMARY")
430 .setCustomId("back")
431 ])
432 ]
pineafan4edb7762022-06-26 19:21:04 +0100433 });
434 let out;
435 try {
Skyler Grey75ea9172022-08-06 10:22:23 +0100436 out = await modalInteractionCollector(
437 m,
438 (m) => m.channel.id === interaction.channel.id,
439 (m) => m.customId === "modify"
440 );
441 } catch (e) {
442 continue;
443 }
pineafan4edb7762022-06-26 19:21:04 +0100444 if (out.fields) {
pineafan63fc5e22022-08-04 22:04:10 +0100445 const toAdd = out.fields.getTextInputValue("note") || null;
Skyler Grey75ea9172022-08-06 10:22:23 +0100446 await client.database.notes.create(
447 member.guild.id,
448 member.id,
449 toAdd
450 );
451 } else {
452 continue;
453 }
pineafan4edb7762022-06-26 19:21:04 +0100454 } else if (i.customId === "history") {
455 i.deferUpdate();
Skyler Grey75ea9172022-08-06 10:22:23 +0100456 if (!(await showHistory(member, interaction))) return;
pineafan4edb7762022-06-26 19:21:04 +0100457 }
458 }
pineafan63fc5e22022-08-04 22:04:10 +0100459};
pineafan4edb7762022-06-26 19:21:04 +0100460
pineafanbd02b4a2022-08-05 22:01:38 +0100461const check = (interaction: CommandInteraction) => {
Skyler Grey75ea9172022-08-06 10:22:23 +0100462 const member = interaction.member as GuildMember;
463 if (!member.permissions.has("MODERATE_MEMBERS"))
464 throw "You do not have the *Moderate Members* permission";
pineafan63fc5e22022-08-04 22:04:10 +0100465 return true;
466};
pineafan4edb7762022-06-26 19:21:04 +0100467
468export { command };
469export { callback };
Skyler Grey75ea9172022-08-06 10:22:23 +0100470export { check };