/[sudobot]/branches/5.x/src/services/LoggerService.ts
ViewVC logotype

Contents of /branches/5.x/src/services/LoggerService.ts

Parent Directory Parent Directory | Revision Log Revision Log


Revision 577 - (show annotations)
Mon Jul 29 18:52:37 2024 UTC (8 months ago) by rakinar2
File MIME type: application/typescript
File size: 42404 byte(s)
chore: add old version archive branches (2.x to 9.x-dev)
1 /**
2 * This file is part of SudoBot.
3 *
4 * Copyright (C) 2021-2023 OSN Developers.
5 *
6 * SudoBot is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * SudoBot is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with SudoBot. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 import { formatDistanceToNowStrict } from "date-fns";
21 import {
22 APIEmbedField,
23 ActionRowBuilder,
24 AttachmentBuilder,
25 BanOptions,
26 ButtonBuilder,
27 ButtonStyle,
28 ChannelType,
29 Collection,
30 ColorResolvable,
31 Colors,
32 EmbedBuilder,
33 EmbedData,
34 Guild,
35 GuildChannel,
36 GuildMember,
37 Message,
38 MessageCreateOptions,
39 MessagePayload,
40 MessageResolvable,
41 MessageType,
42 NonThreadGuildBasedChannel,
43 Role,
44 TextChannel,
45 User,
46 escapeMarkdown,
47 roleMention
48 } from "discord.js";
49 import Service from "../core/Service";
50 import { MessageRuleType } from "../types/MessageRuleSchema";
51 import { NotUndefined } from "../types/NotUndefined";
52 import { log, logError } from "../utils/logger";
53 import { isTextableChannel } from "../utils/utils";
54 import { GuildConfig } from "./ConfigManager";
55
56 export const name = "logger";
57
58 type LoggingChannelType = Exclude<keyof NotUndefined<GuildConfig["logging"]>, "enabled">;
59
60 export default class LoggerService extends Service {
61 private async send(guild: Guild, options: string | MessagePayload | MessageCreateOptions, channel?: LoggingChannelType) {
62 const channelId =
63 this.client.configManager.config[guild.id]?.logging?.[channel ?? "primary_channel"] ??
64 this.client.configManager.config[guild.id]?.logging?.primary_channel;
65 const enabled = this.client.configManager.config[guild.id]?.logging?.enabled;
66
67 if (!enabled || !channelId) return null;
68
69 try {
70 const channel = await guild.channels.fetch(channelId as string);
71
72 if (!channel || !isTextableChannel(channel)) return null;
73
74 return await channel.send(options);
75 } catch (e) {
76 logError(e);
77 return null;
78 }
79 }
80
81 private createLogEmbed({
82 options,
83 title,
84 user,
85 fields,
86 footerText,
87 timestamp,
88 moderator,
89 reason,
90 id,
91 color,
92 showUserId = true
93 }: CreateLogEmbedOptions) {
94 const embed = new EmbedBuilder({
95 title,
96 author: user
97 ? {
98 name: user.tag,
99 iconURL: user.displayAvatarURL()
100 }
101 : undefined,
102 fields: [
103 ...(reason !== undefined
104 ? [
105 {
106 name: "Reason",
107 value: `${reason ?? "*No reason provided*"}`
108 }
109 ]
110 : []),
111 ...(fields ?? []),
112 ...(moderator
113 ? [
114 {
115 name: "Responsible Moderator",
116 value: moderator.id === this.client.user?.id ? "System" : `${moderator.tag} (${moderator.id})`
117 }
118 ]
119 : []),
120 ...(id
121 ? [
122 {
123 name: "Infraction ID",
124 value: `${id}`
125 }
126 ]
127 : []),
128 ...(user && showUserId
129 ? [
130 {
131 name: "User ID",
132 value: user.id
133 }
134 ]
135 : [])
136 ],
137 footer: footerText
138 ? {
139 text: footerText
140 }
141 : undefined,
142 ...options
143 });
144
145 if (timestamp === undefined) embed.setTimestamp();
146 else if (timestamp) embed.setTimestamp(timestamp);
147
148 if (color) embed.setColor(color);
149
150 return embed;
151 }
152
153 private async sendLogEmbed(
154 guild: Guild,
155 options: CreateLogEmbedOptions,
156 extraOptions?: MessagePayload | MessageCreateOptions,
157 channel?: LoggingChannelType
158 ) {
159 return await this.send(
160 guild,
161 {
162 embeds: [this.createLogEmbed(options)],
163 ...((extraOptions as any) ?? {})
164 },
165 channel
166 );
167 }
168
169 /*
170 * Logging methods.
171 */
172
173 async logMessageRuleAction({
174 message,
175 embedOptions = {},
176 rule,
177 actions
178 }: {
179 message: Message;
180 actions: MessageRuleType["actions"];
181 embedOptions?: CreateLogEmbedOptions;
182 rule: MessageRuleType["type"];
183 }) {
184 log("Actions", actions);
185
186 await this.sendLogEmbed(message.guild!, {
187 color: Colors.Red,
188 user: message.author,
189 footerText: "AutoMod",
190 moderator: this.client.user!,
191 ...embedOptions,
192 fields: [
193 ...(embedOptions.fields ?? []),
194 {
195 name: "Rule",
196 value: `\`${rule}\``,
197 inline: true
198 },
199 {
200 name: "Actions taken",
201 value: `\`${actions.length === 0 ? "none" : actions.join("`, `")}\``,
202 inline: true
203 },
204 {
205 name: "Message",
206 value: `${message.url}`
207 }
208 ]
209 });
210 }
211
212 async logFileFilterDeletedMessage(
213 message: Message,
214 { contentType, hash, url }: { hash: string; url: string; name?: string; contentType?: string | null }
215 ) {
216 await this.sendLogEmbed(message.guild!, {
217 title: "Blocked file detected",
218 color: Colors.Red,
219 user: message.author,
220 fields: [
221 {
222 name: "File",
223 value:
224 `${name ? `[${escapeMarkdown(name)}](${url})` : `[Unnamed](${url})`}: \`${hash}\`` +
225 (contentType ? ` (\`${contentType}\`)` : "")
226 }
227 ],
228 footerText: "Deleted",
229 moderator: this.client.user!
230 });
231 }
232
233 async logMemberTimeout(
234 member: GuildMember,
235 { reason, id, moderator }: Omit<CommonUserActionOptions, "guild" | "id"> & { reason?: string; id?: string | number }
236 ) {
237 await this.sendLogEmbed(member.guild, {
238 title: "Member timed-out",
239 color: Colors.Red,
240 user: member.user,
241 fields: [
242 {
243 name: "Duration",
244 value: formatDistanceToNowStrict(member.communicationDisabledUntil!)
245 },
246 {
247 name: "User Information",
248 value: `Username: ${member.user.username}\nMention: ${member.user.toString()}\nID: ${member.user.id}`
249 }
250 ],
251 footerText: "Timed-out",
252 reason,
253 id: id?.toString(),
254 moderator
255 });
256 }
257
258 async logMemberTimeoutRemove(
259 member: GuildMember,
260 { reason, id, moderator }: Omit<CommonUserActionOptions, "guild" | "id"> & { reason?: string; id?: string | number }
261 ) {
262 await this.sendLogEmbed(member.guild, {
263 title: "Member timeout removed",
264 color: Colors.Green,
265 user: member.user,
266 fields: [
267 {
268 name: "User Information",
269 value: `Username: ${member.user.username}\nMention: ${member.user.toString()}\nID: ${member.user.id}`
270 }
271 ],
272 footerText: "Timed-out removed",
273 reason,
274 id: id?.toString(),
275 moderator
276 });
277 }
278
279 async logChannelCreate(channel: NonThreadGuildBasedChannel) {
280 await this.sendLogEmbed(channel.guild, {
281 title: "Channel Created",
282 color: Colors.Green,
283 fields: [
284 {
285 name: "Name",
286 value: channel.name
287 },
288 {
289 name: "ID",
290 value: channel.id
291 },
292 {
293 name: "Mention",
294 value: channel.toString()
295 },
296 {
297 name: "Type",
298 value: ChannelType[channel.type]
299 }
300 ],
301 footerText: "Created"
302 });
303 }
304
305 async logChannelDelete(channel: NonThreadGuildBasedChannel) {
306 await this.sendLogEmbed(channel.guild, {
307 title: "Channel Deleted",
308 color: Colors.Red,
309 fields: [
310 {
311 name: "Name",
312 value: channel.name
313 },
314 {
315 name: "ID",
316 value: channel.id
317 },
318 {
319 name: "Type",
320 value: ChannelType[channel.type]
321 }
322 ],
323 footerText: "Deleted"
324 });
325 }
326
327 async logChannelUpdate(oldChannel: NonThreadGuildBasedChannel, newChannel: NonThreadGuildBasedChannel) {
328 await this.sendLogEmbed(newChannel.guild, {
329 title: "Channel Updated",
330 color: Colors.Green,
331 fields: [
332 {
333 name: "Old Name",
334 value: oldChannel.name,
335 inline: true
336 },
337 {
338 name: "New Name",
339 value: newChannel.name,
340 inline: true
341 },
342 {
343 name: "ID",
344 value: newChannel.id
345 }
346 ],
347 footerText: "Updated"
348 });
349 }
350
351 async logRoleCreate(role: Role) {
352 const permissions = role.permissions.toArray();
353
354 await this.sendLogEmbed(role.guild, {
355 title: "Role Created",
356 color: Colors.Green,
357 fields: [
358 {
359 name: "Name",
360 value: role.name
361 },
362 {
363 name: "ID",
364 value: role.id
365 },
366 {
367 name: "Mention",
368 value: role.toString()
369 },
370 {
371 name: "Icon",
372 value: role.icon ? role.iconURL()! : "*None*"
373 },
374 {
375 name: "Permissions",
376 value: permissions.length === 0 ? "*Nothing*" : "`" + permissions.join("`, `") + "`"
377 }
378 ],
379 footerText: "Created"
380 });
381 }
382
383 async logRoleDelete(role: Role) {
384 const permissions = role.permissions.toArray();
385
386 await this.sendLogEmbed(role.guild, {
387 title: "Role Deleted",
388 color: Colors.Red,
389 fields: [
390 {
391 name: "Name",
392 value: role.name
393 },
394 {
395 name: "ID",
396 value: role.id
397 },
398 {
399 name: "Icon",
400 value: role.icon ? role.iconURL()! : "*None*"
401 },
402 {
403 name: "Permissions",
404 value: permissions.length === 0 ? "*Nothing*" : "`" + permissions.join("`, `") + "`"
405 }
406 ],
407 footerText: "Deleted"
408 });
409 }
410
411 async logRoleUpdate(oldRole: Role, newRole: Role) {
412 const newRolePermissions = newRole.permissions.toArray();
413 const oldRolePermissions = oldRole.permissions.toArray();
414 const addedPermissions = newRolePermissions.filter(permission => !oldRolePermissions.includes(permission));
415 const removedPermissions = oldRolePermissions.filter(permission => !newRolePermissions.includes(permission));
416
417 await this.sendLogEmbed(newRole.guild, {
418 title: "Role Updated",
419 color: Colors.Green,
420 fields: [
421 {
422 name: "Name",
423 value: `Old name: ${oldRole.name}\nNew name: ${newRole.name}`
424 },
425 {
426 name: "ID",
427 value: newRole.id
428 },
429 {
430 name: "Mention",
431 value: newRole.toString()
432 },
433 {
434 name: "Icon",
435 value: `Old icon: ${oldRole.icon ? oldRole.iconURL()! : "*None*"}\nNew icon: ${
436 newRole.icon ? newRole.iconURL()! : "*None*"
437 }`
438 },
439 {
440 name: "Added Permissions",
441 value: addedPermissions.length === 0 ? "*Nothing*" : "`" + addedPermissions.join("`, `") + "`",
442 inline: true
443 },
444 {
445 name: "Removed Permissions",
446 value: removedPermissions.length === 0 ? "*Nothing*" : "`" + removedPermissions.join("`, `") + "`",
447 inline: true
448 }
449 ],
450 footerText: "Updated"
451 });
452 }
453
454 async logNicknameUpdate(oldMember: GuildMember, newMember: GuildMember) {
455 await this.sendLogEmbed(newMember.guild, {
456 title: "Member nickname updated",
457 user: newMember.user,
458 color: 0x007bff,
459 fields: [
460 {
461 name: "Old Nickname",
462 value: oldMember.nickname ?? "*Nothing*",
463 inline: true
464 },
465 {
466 name: "New Nickname",
467 value: newMember.nickname ?? "*Nothing*",
468 inline: true
469 },
470 {
471 name: "User Information",
472 value: `Username: ${newMember.user.username}\nMention: ${newMember.user.toString()}\nID: ${newMember.user.id}`
473 }
474 ],
475 footerText: "Updated"
476 });
477 }
478
479 async logMemberRoleUpdate(oldMember: GuildMember, newMember: GuildMember) {
480 const added = newMember.roles.cache.filter(role => !oldMember.roles.cache.has(role.id));
481 const removed = oldMember.roles.cache.filter(role => !newMember.roles.cache.has(role.id));
482
483 await this.sendLogEmbed(
484 newMember.guild,
485 {
486 title: "Member roles updated",
487 user: newMember.user,
488 color: Colors.Green,
489 showUserId: false,
490 fields: [
491 {
492 name: "Added",
493 value: added.size === 0 ? "*Nothing added*" : added.reduce((acc, role) => `${acc} ${role.toString()}`, "")
494 },
495 {
496 name: "Removed",
497 value:
498 removed.size === 0
499 ? "*Nothing removed*"
500 : removed.reduce((acc, role) => `${acc} ${role.toString()}`, "")
501 },
502 {
503 name: "User Information",
504 value: `Username: ${newMember.user.username}\nMention: ${newMember.user.toString()}\nID: ${
505 newMember.user.id
506 }`
507 }
508 ],
509 footerText: "Roles Updated"
510 },
511 {
512 allowedMentions: {
513 roles: []
514 }
515 }
516 );
517 }
518
519 async logGuildMemberAdd(member: GuildMember) {
520 if (!this.client.configManager.config[member.guild.id]?.logging?.events.member_join) {
521 return;
522 }
523
524 let members = 0,
525 bots = 0;
526
527 for (const m of member.guild.members.cache.values()) {
528 if (m.user.bot) bots++;
529 else members++;
530 }
531
532 const createdAt = Math.round((member.user.createdAt?.getTime() ?? Date.now()) / 1000);
533 const inviteOrVanity = await this.client.inviteTracker.findNewMemberInviteLink(member);
534
535 console.log(createdAt);
536
537 await this.sendLogEmbed(
538 member.guild,
539 {
540 title: "New member joined",
541 user: member.user,
542 color: 0x007bff,
543 options: {
544 description: `${member.user.toString()} just joined the server!`
545 },
546 fields: [
547 {
548 name: "New Account?",
549 value: Date.now() - member.user.createdTimestamp < 3 * 24 * 60 * 60 * 1000 ? ":warning: Yes" : "No",
550 inline: true
551 },
552 {
553 name: "Bot?",
554 value: member.user.bot ? "Yes" : "No",
555 inline: true
556 },
557 {
558 name: "Account Created At",
559 value: `<t:${createdAt}:f> (<t:${createdAt}:R>)`
560 },
561 {
562 name: "User Information",
563 value: `Username: ${member.user.username}\nMention: ${member.user.toString()}\nID: ${member.user.id}`,
564 inline: true
565 },
566 {
567 name: "Positions",
568 value:
569 `Among all members: ${members + bots}th\n` +
570 (member.user.bot ? `Among the bots: ${bots}th` : `Among the human members: ${members}th`),
571 inline: true
572 },
573 ...(inviteOrVanity
574 ? [
575 {
576 name: "Invite Information",
577 value:
578 `Invite Link: https://discord.gg/${
579 inviteOrVanity?.vanity?.code ?? inviteOrVanity?.invite?.code
580 }\nUses: ${inviteOrVanity?.vanity?.uses ?? inviteOrVanity?.invite?.uses}` +
581 (!inviteOrVanity.isVanity
582 ? `\nInvited By: ${
583 inviteOrVanity.invite.inviterId
584 ? `<@${inviteOrVanity.invite.inviterId}> (${inviteOrVanity.invite.inviterId})`
585 : "Unknown"
586 }` +
587 (inviteOrVanity.invite.createdAt
588 ? `\nCreated: <t:${Math.round(
589 inviteOrVanity.invite.createdAt.getTime() / 1000
590 )}:R>`
591 : "") +
592 (inviteOrVanity.invite.expiresAt
593 ? `\nExpires: <t:${Math.round(
594 inviteOrVanity.invite.expiresAt.getTime() / 1000
595 )}:R>`
596 : "") +
597 (inviteOrVanity.invite.channelId
598 ? `\nChannel: <#${inviteOrVanity.invite.channelId}> (${inviteOrVanity.invite.channelId})`
599 : "") +
600 (inviteOrVanity.invite.temporary ? `\n\n__This is a temporary invite.__` : "")
601 : "")
602 }
603 ]
604 : [])
605 ],
606 footerText: `Joined • ${
607 member.guild.members.cache.size >= member.guild.memberCount
608 ? member.guild.members.cache.size
609 : member.guild.memberCount
610 } members total`
611 },
612 undefined,
613 "join_leave_channel"
614 );
615 }
616
617 async logGuildMemberRemove(member: GuildMember) {
618 if (!this.client.configManager.config[member.guild.id]?.logging?.events.member_leave) {
619 return;
620 }
621
622 const joinedAt = Math.round((member.joinedAt?.getTime() ?? Date.now()) / 1000);
623
624 await this.sendLogEmbed(
625 member.guild,
626 {
627 title: "Member left",
628 user: member.user,
629 color: 0xf14a60,
630 fields: [
631 {
632 name: "Roles",
633 value:
634 member.roles.cache.size === 1
635 ? "**No roles**"
636 : member.roles.cache
637 .filter(role => role.id !== member.guild.id)
638 .sort((m1, m2) => m2.position - m1.position)
639 .reduce((acc, role) => `${acc} ${roleMention(role.id)}`, "")
640 },
641 {
642 name: "Joined At",
643 value: `<t:${joinedAt}:f> (<t:${joinedAt}:R>)`
644 },
645 {
646 name: "User Information",
647 value: `Username: ${member.user.username}\nMention: ${member.user.toString()}\nID: ${member.user.id}`
648 },
649 {
650 name: "Bot?",
651 value: member.user.bot ? "Yes" : "No",
652 inline: true
653 }
654 ],
655 footerText: `Left • ${
656 member.guild.members.cache.size >= member.guild.memberCount
657 ? member.guild.members.cache.size
658 : member.guild.memberCount
659 } members total`
660 },
661 undefined,
662 "join_leave_channel"
663 );
664 }
665
666 async logMessageEdit(oldMessage: Message, newMessage: Message) {
667 if (!this.client.configManager.config[newMessage.guildId!]?.logging?.events.message_edit) {
668 return;
669 }
670
671 const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
672 new ButtonBuilder()
673 .setStyle(ButtonStyle.Link)
674 .setLabel("Go to context")
675 .setURL(`https://discord.com/channels/${newMessage.guildId!}/${newMessage.channelId!}/${newMessage.id}`)
676 );
677
678 if (newMessage.type === MessageType.Reply)
679 row.addComponents(
680 new ButtonBuilder()
681 .setStyle(ButtonStyle.Link)
682 .setLabel("Go to referenced message")
683 .setURL(
684 `https://discord.com/channels/${newMessage.guildId!}/${newMessage.channelId!}/${
685 newMessage.reference!.messageId
686 }`
687 )
688 );
689
690 await this.sendLogEmbed(
691 newMessage.guild!,
692 {
693 title: "Message Updated",
694 user: newMessage.author,
695 color: 0x007bff,
696 options: {
697 description: `**-+-+Before**\n${oldMessage.content}\n\n**-+-+After**\n${newMessage.content}`
698 },
699 fields: [
700 {
701 name: "User",
702 value: `${newMessage.author.toString()}\nUsername: ${newMessage.author.username}\nID: ${
703 newMessage.author.id
704 }`
705 },
706 {
707 name: "Channel",
708 value: `${newMessage.channel.toString()}\nName: ${(newMessage.channel as TextChannel).name}\nID: ${
709 newMessage.channel.id
710 }`
711 },
712 {
713 name: "Message",
714 value: `Link: [Click here](${`https://discord.com/channels/${newMessage.guildId!}/${newMessage.channelId!}/${
715 newMessage.id
716 }`})\nID: ${newMessage.id}`
717 }
718 ],
719 footerText: "Updated"
720 },
721 {
722 components: [row]
723 },
724 "message_logging_channel"
725 );
726 }
727
728 async logMessageDelete(message: Message) {
729 if (!this.client.configManager.config[message.guildId!]?.logging?.events.message_delete) {
730 return;
731 }
732
733 const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
734 new ButtonBuilder()
735 .setStyle(ButtonStyle.Link)
736 .setLabel("Go to context")
737 .setURL(`https://discord.com/channels/${message.guildId!}/${message.channelId!}/${message.id}`)
738 );
739
740 if (message.type === MessageType.Reply)
741 row.addComponents(
742 new ButtonBuilder()
743 .setStyle(ButtonStyle.Link)
744 .setLabel("Go to referenced message")
745 .setURL(
746 `https://discord.com/channels/${message.guildId!}/${message.channelId!}/${message.reference!.messageId}`
747 )
748 );
749
750 await this.sendLogEmbed(
751 message.guild!,
752 {
753 title: "Message Deleted",
754 color: Colors.Red,
755 user: message.author,
756 options: {
757 description: message.content
758 },
759 fields: [
760 {
761 name: "User",
762 value: `${message.author.toString()}\nUsername: ${message.author.username}\nID: ${message.author.id}`
763 },
764 {
765 name: "Channel",
766 value: `${message.channel.toString()}\nName: ${(message.channel as TextChannel).name}\nID: ${
767 message.channel.id
768 }`
769 },
770 {
771 name: "Message",
772 value: `Link: [Click here](${`https://discord.com/channels/${message.guildId!}/${message.channelId!}/${
773 message.id
774 }`})\nID: ${message.id}`
775 }
776 ],
777 footerText: "Deleted"
778 },
779 {
780 components: [row],
781 files: [
782 ...message.attachments
783 .map(
784 a =>
785 ({
786 attachment: a.proxyURL,
787 name: a.name
788 } as AttachmentBuilder)
789 )
790 .values()
791 ]
792 },
793 "message_logging_channel"
794 );
795 }
796
797 async logRaid({ guild, action }: { guild: Guild; action: string }) {
798 await this.sendLogEmbed(guild, {
799 title: "Possible raid detected",
800 reason: "Too many users joined in a short timeframe.",
801 color: Colors.Red,
802 fields: [
803 {
804 name: "Action",
805 value: action
806 }
807 ],
808 footerText: "Raid detected"
809 });
810 }
811
812 async logServerLockOrUnlock({
813 guild,
814 action,
815 moderator,
816 countInvalidChannel,
817 countSkipped,
818 countFailed,
819 countSuccess,
820 reason
821 }: {
822 guild: Guild;
823 action: "Locked" | "Unlocked";
824 moderator: User;
825 countInvalidChannel: number;
826 countSkipped: number;
827 countFailed: number;
828 countSuccess: number;
829 reason?: string;
830 }) {
831 await this.sendLogEmbed(guild, {
832 title: `Server ${action.toLowerCase()}`,
833 reason: reason ?? "The user ran a command to perform this action",
834 moderator,
835 color: 0x007bff,
836 footerText: action,
837 options: {
838 description: `Results:\n\n${countInvalidChannel === 0 ? "" : `InvalidChannel: ${countInvalidChannel}\n`}${
839 countSkipped === 0 ? "" : `Skipped: ${countSkipped}\n`
840 }${countSuccess === 0 ? "" : `Success: ${countSuccess}\n`}${countFailed === 0 ? "" : `Failed: ${countFailed}\n`}`
841 }
842 });
843 }
844
845 async logChannelLockOrUnlock({
846 guild,
847 action,
848 moderator,
849 channel,
850 reason
851 }: {
852 guild: Guild;
853 action: "Locked" | "Unlocked";
854 moderator: User;
855 channel: GuildChannel;
856 reason?: string;
857 }) {
858 await this.sendLogEmbed(guild, {
859 title: `Channel ${action.toLowerCase()}`,
860 reason: reason ?? "The user ran a command to perform this action",
861 moderator,
862 color: 0x007bff,
863 footerText: action,
864 fields: [
865 {
866 name: "Channel",
867 value: `${channel.toString()} (${channel.id})`
868 }
869 ]
870 });
871 }
872
873 async logUserBan({
874 moderator,
875 user,
876 deleteMessageSeconds,
877 reason,
878 guild,
879 id,
880 duration,
881 includeDeleteMessageSeconds = true
882 }: LogUserBanOptions) {
883 await this.sendLogEmbed(guild, {
884 user,
885 title: "A user was banned",
886 footerText: (duration ? "Temporarily " : "") + "Banned",
887 reason: reason ?? null,
888 moderator,
889 id,
890 color: Colors.Red,
891 fields: [
892 ...(includeDeleteMessageSeconds
893 ? [
894 {
895 name: "Message Deletion Timeframe",
896 value: deleteMessageSeconds
897 ? formatDistanceToNowStrict(new Date(Date.now() - deleteMessageSeconds * 1000))
898 : "*No timeframe provided*"
899 }
900 ]
901 : []),
902 ...(duration
903 ? [
904 {
905 name: "Duration",
906 value: formatDistanceToNowStrict(new Date(Date.now() - duration))
907 }
908 ]
909 : [])
910 ]
911 });
912 }
913
914 async logUserSoftBan({ moderator, user, deleteMessageSeconds, reason, guild, id }: LogUserBanOptions) {
915 await this.sendLogEmbed(guild, {
916 user,
917 title: "A user was softbanned",
918 footerText: "Softbanned",
919 reason: reason ?? null,
920 moderator,
921 id,
922 color: Colors.Red,
923 fields: [
924 {
925 name: "Message Deletion Timeframe",
926 value: deleteMessageSeconds
927 ? formatDistanceToNowStrict(new Date(Date.now() - deleteMessageSeconds * 1000))
928 : "*No timeframe provided*"
929 }
930 ]
931 });
932 }
933
934 async logUserUnban({ moderator, user, reason, guild, id }: LogUserUnbanOptions) {
935 this.sendLogEmbed(guild, {
936 user,
937 title: "A user was unbanned",
938 footerText: "Unbanned",
939 reason: reason ?? null,
940 moderator,
941 id,
942 color: Colors.Green
943 });
944 }
945
946 async logMemberKick({
947 moderator,
948 member,
949 reason,
950 guild,
951 id
952 }: CommonUserActionOptions & { member: GuildMember; reason?: string }) {
953 this.sendLogEmbed(guild, {
954 user: member.user,
955 title: "A member was kicked",
956 footerText: "Kicked",
957 reason: reason ?? null,
958 moderator,
959 id,
960 color: Colors.Orange
961 });
962 }
963
964 async logMemberMute({
965 moderator,
966 member,
967 reason,
968 guild,
969 id,
970 duration
971 }: CommonUserActionOptions & { member: GuildMember; reason?: string; duration?: number }) {
972 this.sendLogEmbed(guild, {
973 user: member.user,
974 title: "A member was muted",
975 footerText: "Muted",
976 reason: reason ?? null,
977 moderator,
978 id,
979 color: Colors.DarkGold,
980 fields: [
981 {
982 name: "Duration",
983 value: duration ? formatDistanceToNowStrict(new Date(Date.now() - duration)) : "*No duration was specified*"
984 }
985 ]
986 });
987 }
988
989 async logMemberWarning({
990 moderator,
991 member,
992 reason,
993 guild,
994 id
995 }: CommonUserActionOptions & { member: GuildMember; reason?: string }) {
996 this.sendLogEmbed(guild, {
997 user: member.user,
998 title: "A member was warned",
999 footerText: "Warned",
1000 reason: reason ?? null,
1001 moderator,
1002 id,
1003 color: Colors.Gold
1004 });
1005 }
1006
1007 async logBulkDeleteMessages({ messages, moderator, user, reason, guild, id, count, channel }: LogMessageBulkDelete) {
1008 const sendJSON = this.client.configManager.config[guild.id]?.logging?.bulk_delete_send_json;
1009
1010 const message = await this.sendLogEmbed(
1011 guild,
1012 {
1013 user,
1014 title: "Messages deleted in bulk",
1015 footerText: "Deleted",
1016 reason: reason ?? null,
1017 moderator,
1018 id,
1019 color: Colors.DarkRed,
1020 fields: [
1021 {
1022 name: "Deleted Message Count",
1023 value: `${count}`
1024 },
1025 {
1026 name: "Channel",
1027 value: `${channel.toString()} (${channel.id})`
1028 }
1029 ]
1030 },
1031 sendJSON
1032 ? {
1033 files: [
1034 {
1035 attachment: this.generateBulkDeleteJSON(messages),
1036 name: "messages.json"
1037 }
1038 ]
1039 }
1040 : undefined
1041 );
1042
1043 message
1044 ?.edit({
1045 embeds: [
1046 {
1047 ...(message?.embeds[0]?.data ?? {}),
1048 fields: [
1049 ...(message?.embeds[0].data.fields ?? []),
1050 {
1051 name: "Messages",
1052 value: `[Click here to view the deleted messages](${
1053 process.env.FRONTEND_URL
1054 }/view_deleted_messages?url=${encodeURIComponent(message.attachments.at(0)!.url)})`
1055 }
1056 ]
1057 }
1058 ]
1059 })
1060 .catch(logError);
1061 }
1062
1063 async logMemberUnmute({
1064 moderator,
1065 member,
1066 reason,
1067 guild,
1068 id
1069 }: CommonUserActionOptions & { member: GuildMember; reason?: string }) {
1070 this.sendLogEmbed(guild, {
1071 user: member.user,
1072 title: "A member was unmuted",
1073 footerText: "Unmuted",
1074 reason: reason ?? null,
1075 moderator,
1076 id,
1077 color: Colors.Green
1078 });
1079 }
1080
1081 async logBlockedWordOrToken({ guild, user, isToken, token, word, content }: BlockedTokenOrWordOptions) {
1082 this.sendLogEmbed(guild, {
1083 user,
1084 title: `Posted blocked ${isToken ? "token" : "word"}(s)`,
1085 footerText: "AutoMod",
1086 color: Colors.Yellow,
1087 fields: [
1088 {
1089 name: isToken ? "Token" : "Word",
1090 value: `||${escapeMarkdown((isToken ? token : word)!)}||`
1091 }
1092 ],
1093 options: {
1094 description: `${content}`
1095 }
1096 });
1097 }
1098
1099 async logUserMassBan({ users, reason, guild, moderator, deleteMessageSeconds }: LogUserMassBanOptions) {
1100 await this.sendLogEmbed(guild, {
1101 title: "A massban was executed",
1102 footerText: "Banned",
1103 reason: reason ?? null,
1104 moderator,
1105 color: Colors.Red,
1106 fields: [
1107 {
1108 name: "Message Deletion Timeframe",
1109 value: deleteMessageSeconds
1110 ? formatDistanceToNowStrict(new Date(Date.now() - deleteMessageSeconds * 1000))
1111 : "*No timeframe provided*"
1112 }
1113 ],
1114 options: {
1115 description: `The following users were banned:\n\n${users.reduce(
1116 (acc, user) => acc + (acc === "" ? "" : "\n") + "<@" + user + "> (`" + user + "`)",
1117 ""
1118 )}`
1119 }
1120 });
1121 }
1122
1123 async logMemberMassKick({ users, reason, guild, moderator }: Omit<LogUserMassBanOptions, "deleteMessageSeconds">) {
1124 await this.sendLogEmbed(guild, {
1125 title: "A masskick was executed",
1126 footerText: "Kicked",
1127 reason: reason ?? null,
1128 moderator,
1129 color: Colors.Orange,
1130 options: {
1131 description: `The following users were kicked:\n\n${users.reduce(
1132 (acc, user) => acc + (acc === "" ? "" : "\n") + "<@" + user + "> (`" + user + "`)",
1133 ""
1134 )}`
1135 }
1136 });
1137 }
1138
1139 generateBulkDeleteJSON(messages: MessageResolvable[]) {
1140 const mappedMessages = ((messages instanceof Collection ? [...messages.values()] : messages) as Message[]).map(m => ({
1141 ...m,
1142 author: m.author,
1143 member: m.member,
1144 authorColor: m.member?.roles.highest.color,
1145 authorRoleIcon: m.member?.roles.highest.iconURL() ?? undefined,
1146 authorRoleName: m.member?.roles.highest.name ?? undefined,
1147 authorAvatarURL: m.author.displayAvatarURL(),
1148 mentions: {
1149 everyone: m.mentions.everyone,
1150 users: m.mentions.users.map(({ id, username }) => ({
1151 id,
1152 username
1153 })),
1154 members:
1155 m.mentions.members?.map(({ nickname, user }) => ({
1156 nickname: nickname ?? user.username,
1157 id: user.id
1158 })) ?? [],
1159 channels: (m.mentions.channels as Collection<string, GuildChannel>).map(({ id, name }) => ({ id, name })),
1160 roles: m.mentions.roles.map(({ id, name }) => ({ id, name }))
1161 }
1162 }));
1163
1164 return Buffer.from(
1165 JSON.stringify(
1166 {
1167 messages: mappedMessages,
1168 generatedAt: new Date().toISOString(),
1169 channel: (messages.at(0) as Message)!.channel.toJSON({
1170 id: true,
1171 name: true,
1172 type: true
1173 }),
1174 guild: {
1175 id: (messages.at(0) as Message)!.guild!.id,
1176 name: (messages.at(0) as Message)!.guild!.name,
1177 iconURL: (messages.at(0) as Message)!.guild!.iconURL() ?? undefined
1178 },
1179 version: this.client.metadata.data.version
1180 },
1181 null,
1182 4
1183 )
1184 );
1185 }
1186 }
1187
1188 interface LogMessageBulkDelete extends Omit<CommonUserActionOptions, "id"> {
1189 user?: User;
1190 reason?: string;
1191 count: number;
1192 channel: TextChannel;
1193 id?: string;
1194 messages: MessageResolvable[];
1195 }
1196
1197 interface LogUserBanOptions extends BanOptions, CommonUserActionOptions {
1198 user: User;
1199 duration?: number;
1200 includeDeleteMessageSeconds?: boolean;
1201 }
1202
1203 interface LogUserMassBanOptions extends BanOptions, Omit<CommonUserActionOptions, "id"> {
1204 users: string[];
1205 }
1206
1207 interface LogUserUnbanOptions extends CommonUserActionOptions {
1208 user: User;
1209 reason?: string;
1210 }
1211
1212 interface CommonUserActionOptions {
1213 moderator: User;
1214 guild: Guild;
1215 id: string;
1216 }
1217
1218 interface BlockedTokenOrWordOptions {
1219 isToken: boolean;
1220 token?: string;
1221 word?: string;
1222 guild: Guild;
1223 user: User;
1224 content: string;
1225 }
1226
1227 export interface CreateLogEmbedOptions {
1228 id?: string;
1229 title?: string;
1230 options?: EmbedData;
1231 moderator?: User;
1232 user?: User;
1233 fields?: APIEmbedField[];
1234 footerText?: string;
1235 reason?: string | null;
1236 timestamp?: Date | false | null;
1237 color?: ColorResolvable;
1238 showUserId?: boolean;
1239 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26