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

Contents of /branches/8.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: 63038 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 { Infraction } from "@prisma/client";
21 import { formatDistanceToNowStrict } from "date-fns";
22 import {
23 APIEmbedField,
24 ActionRowBuilder,
25 AttachmentBuilder,
26 BanOptions,
27 ButtonBuilder,
28 ButtonStyle,
29 Channel,
30 ChannelType,
31 Collection,
32 ColorResolvable,
33 Colors,
34 EmbedBuilder,
35 EmbedData,
36 Guild,
37 GuildChannel,
38 GuildMember,
39 Message,
40 MessageCreateOptions,
41 MessagePayload,
42 MessageResolvable,
43 MessageType,
44 NonThreadGuildBasedChannel,
45 Role,
46 TextChannel,
47 User,
48 VoiceBasedChannel,
49 VoiceChannel,
50 VoiceState,
51 escapeMarkdown,
52 roleMention,
53 time
54 } from "discord.js";
55 import Service from "../core/Service";
56 import { MessageRuleType } from "../types/MessageRuleSchema";
57 import { NotUndefined } from "../types/NotUndefined";
58 import { log, logError } from "../utils/Logger";
59 import { userInfo } from "../utils/embed";
60 import { isTextableChannel } from "../utils/utils";
61 import { GuildConfig } from "./ConfigManager";
62
63 export const name = "loggerService";
64
65 type LoggingChannelType = Exclude<keyof NotUndefined<GuildConfig["logging"]>, "enabled">;
66
67 export default class LoggerService extends Service {
68 private async send(
69 guild: Guild,
70 options: string | MessagePayload | MessageCreateOptions,
71 channel?: LoggingChannelType
72 ) {
73 const channelId =
74 this.client.configManager.config[guild.id]?.logging?.[channel ?? "primary_channel"] ??
75 this.client.configManager.config[guild.id]?.logging?.primary_channel;
76 const enabled = this.client.configManager.config[guild.id]?.logging?.enabled;
77
78 if (!enabled || !channelId) return null;
79
80 try {
81 const channel = await guild.channels.fetch(channelId as string);
82
83 if (!channel || !isTextableChannel(channel)) return null;
84
85 return await channel.send(options);
86 } catch (e) {
87 logError(e);
88 return null;
89 }
90 }
91
92 private createLogEmbed({
93 options,
94 title,
95 user,
96 fields,
97 footerText,
98 timestamp,
99 moderator,
100 reason,
101 id,
102 color,
103 showUserId = true
104 }: CreateLogEmbedOptions) {
105 const embed = new EmbedBuilder({
106 title,
107 author: user
108 ? {
109 name: user.tag,
110 iconURL: user.displayAvatarURL()
111 }
112 : undefined,
113 fields: [
114 ...(reason !== undefined
115 ? [
116 {
117 name: "Reason",
118 value: `${reason ?? "*No reason provided*"}`
119 }
120 ]
121 : []),
122 ...(fields ?? []),
123 ...(moderator
124 ? [
125 {
126 name: "Responsible Moderator",
127 value:
128 moderator.id === this.client.user?.id
129 ? "System"
130 : `${moderator.tag} (${moderator.id})`
131 }
132 ]
133 : []),
134 ...(id
135 ? [
136 {
137 name: "Infraction ID",
138 value: `${id}`
139 }
140 ]
141 : []),
142 ...(user && showUserId
143 ? [
144 {
145 name: "User ID",
146 value: user.id
147 }
148 ]
149 : [])
150 ],
151 footer: footerText
152 ? {
153 text: footerText
154 }
155 : undefined,
156 ...options
157 });
158
159 if (timestamp === undefined) embed.setTimestamp();
160 else if (timestamp) embed.setTimestamp(timestamp);
161
162 if (color) embed.setColor(color);
163
164 return embed;
165 }
166
167 private async sendLogEmbed(
168 guild: Guild,
169 options: CreateLogEmbedOptions,
170 extraOptions?: MessagePayload | MessageCreateOptions,
171 channel?: LoggingChannelType
172 ) {
173 return await this.send(
174 guild,
175 {
176 ...(extraOptions ?? {}),
177 embeds: [
178 this.createLogEmbed(options),
179 ...(extraOptions && "embeds" in extraOptions ? extraOptions.embeds ?? [] : [])
180 ]
181 } as unknown as MessageCreateOptions | MessagePayload,
182 channel
183 );
184 }
185
186 private channelCheck(...channels: (Channel | null | undefined)[]) {
187 for (const channel of channels) {
188 if (!channel) {
189 continue;
190 }
191
192 if (channel.type === ChannelType.DM || channel.type === ChannelType.GroupDM) {
193 return false;
194 }
195
196 const excluded =
197 this.client.configManager.config[channel.guild.id]?.logging?.excluded_channels ??
198 [];
199
200 if (
201 excluded.includes(channel.id) ||
202 (channel.parentId && excluded.includes(channel.parentId))
203 ) {
204 return false;
205 }
206 }
207
208 return true;
209 }
210
211 /*
212 * Logging methods.
213 */
214
215 async logAIAutoModMessageDelete({
216 message,
217 toxicityScore,
218 severeToxicityScore,
219 threatScore,
220 isToxic,
221 isThreat,
222 isSeverelyToxic,
223 isExplicit,
224 isFlirty,
225 isAttack,
226 isInsult,
227 isProfanity,
228 explicitScore,
229 flirtationScore,
230 identityAttackScore,
231 insultScore,
232 profanityScore
233 }: {
234 message: Message;
235 toxicityScore: number;
236 severeToxicityScore: number;
237 threatScore: number;
238 explicitScore: number;
239 flirtationScore: number;
240 identityAttackScore: number;
241 insultScore: number;
242 profanityScore: number;
243 isToxic?: boolean;
244 isThreat?: boolean;
245 isSeverelyToxic?: boolean;
246 isExplicit?: boolean;
247 isFlirty?: boolean;
248 isAttack?: boolean;
249 isInsult?: boolean;
250 isProfanity?: boolean;
251 }) {
252 if (!this.channelCheck(message.channel)) {
253 return;
254 }
255
256 const {
257 max_severe_toxicity,
258 max_threat,
259 max_toxicity,
260 max_explicit,
261 max_flirtation,
262 max_identity_attack,
263 max_insult,
264 max_profanity
265 } = this.client.configManager.config[message.guildId!]?.ai_automod?.parameters ?? {
266 max_severe_toxicity: 100,
267 max_threat: 100,
268 max_toxicity: 100,
269 max_explicit: 100,
270 max_flirtation: 100,
271 max_identity_attack: 100,
272 max_insult: 100,
273 max_profanity: 100
274 };
275
276 const threat = isThreat === undefined ? threatScore >= max_threat : isThreat;
277 const toxic = isToxic === undefined ? toxicityScore >= max_toxicity : isToxic;
278 const severeToxic =
279 isSeverelyToxic === undefined
280 ? severeToxicityScore >= max_severe_toxicity
281 : isSeverelyToxic;
282
283 const explicit = isExplicit ?? explicitScore >= max_explicit;
284 const flirty = isFlirty ?? flirtationScore >= max_flirtation;
285 const attack = isAttack ?? identityAttackScore >= max_identity_attack;
286 const insult = isInsult ?? insultScore >= max_insult;
287 const profanity = isProfanity ?? profanityScore >= max_profanity;
288
289 let messageType: string = "removed for unknown reason";
290
291 if (threat) {
292 messageType = "threatening";
293 } else if (toxic) {
294 messageType = "toxic";
295 } else if (severeToxic) {
296 messageType = "severly toxic";
297 } else if (explicit) {
298 messageType = "sexually explicit";
299 } else if (flirty) {
300 messageType = "flirty";
301 } else if (attack) {
302 messageType = "attacking";
303 } else if (insult) {
304 messageType = "insulting";
305 } else if (profanity) {
306 messageType = "flagged for profanity";
307 }
308
309 await this.sendLogEmbed(message.guild!, {
310 title: "AI AutoMod has flagged this message",
311 color: Colors.Red,
312 user: message.author,
313 fields: [
314 {
315 name: "Score",
316 value: `Toxicity: ${toxicityScore.toFixed(2)}%\nThreat: ${threatScore.toFixed(
317 2
318 )}%\nSevere Toxicity: ${severeToxicityScore.toFixed(
319 2
320 )}%\nNSFW: ${explicitScore.toFixed(2)}%\nFlirtation: ${flirtationScore.toFixed(
321 2
322 )}%\nIdentity Attack: ${identityAttackScore.toFixed(
323 2
324 )}%\nInsult: ${insultScore.toFixed(2)}%\nProfanity: ${profanityScore.toFixed(
325 2
326 )}%`
327 },
328 {
329 name: "Reason",
330 value: messageType ? `This message seems to be ${messageType}.` : "Unknown"
331 }
332 ],
333 footerText: "Flagged",
334 moderator: this.client.user!
335 });
336 }
337
338 async logInfractionCreate(infraction: Infraction, user: User, moderator: User) {
339 await this.sendLogEmbed(
340 this.client.guilds.cache.get(infraction.guildId)!,
341 {
342 title: "Infraction Created",
343 color: Colors.Red,
344 user,
345 fields: [
346 {
347 name: "Type",
348 value: this.client.infractionManager.typeToString(infraction.type)
349 },
350 ...(infraction.expiresAt
351 ? [
352 {
353 name: "Expiry",
354 value: `${time(infraction.expiresAt, "R")}`
355 }
356 ]
357 : [])
358 ],
359 reason: infraction.reason,
360 id: infraction.id.toString(),
361 moderator,
362 footerText: "Created"
363 },
364 undefined,
365 "infraction_logging_channel"
366 );
367 }
368
369 async logVoiceChannelStateUpdate(
370 user: User,
371 oldChannel?: VoiceBasedChannel | null,
372 newChannel?: VoiceBasedChannel | null
373 ) {
374 if (newChannel?.id === oldChannel?.id) {
375 return;
376 }
377
378 if (!this.channelCheck(oldChannel, newChannel)) {
379 return;
380 }
381
382 if (oldChannel) {
383 await this.sendLogEmbed(oldChannel.guild, {
384 title: "Member left voice channel",
385 color: Colors.Red,
386 user,
387 fields: [
388 {
389 name: "Channel",
390 value: oldChannel.toString()
391 }
392 ],
393 footerText: "Left"
394 });
395 } else if (newChannel) {
396 await this.sendLogEmbed(newChannel.guild, {
397 title: "Member joined voice channel",
398 color: Colors.Green,
399 user,
400 fields: [
401 {
402 name: "Channel",
403 value: newChannel.toString()
404 }
405 ],
406 footerText: "Joined"
407 });
408 }
409 }
410
411 async logMemberDisconnect({
412 user,
413 guild,
414 moderator,
415 reason,
416 channel
417 }: {
418 reason?: string;
419 user: User;
420 guild: Guild;
421 moderator?: User;
422 channel: VoiceChannel;
423 }) {
424 if (!this.channelCheck(channel)) {
425 return;
426 }
427
428 await this.sendLogEmbed(guild, {
429 title: "Member disconnected",
430 color: Colors.Red,
431 user,
432 reason,
433 footerText: "Disconnected",
434 moderator,
435 fields: [
436 {
437 name: "Channel",
438 value: channel?.toString() ?? "None"
439 },
440
441 {
442 name: "User",
443 value: userInfo(user)
444 }
445 ]
446 });
447 }
448
449 async logMemberDeaf({
450 user,
451 guild,
452 moderator,
453 reason,
454 channel
455 }: {
456 reason?: string;
457 user: User;
458 guild: Guild;
459 moderator?: User;
460 channel: VoiceChannel;
461 }) {
462 if (!this.channelCheck(channel)) {
463 return;
464 }
465
466 await this.sendLogEmbed(guild, {
467 title: "Member deafened",
468 color: Colors.Red,
469 user,
470 reason,
471 footerText: "Deafened",
472 moderator,
473 fields: [
474 {
475 name: "Channel",
476 value: channel?.toString() ?? "None"
477 },
478
479 {
480 name: "User",
481 value: userInfo(user)
482 }
483 ]
484 });
485 }
486
487 async logMemberUndeaf({
488 user,
489 guild,
490 moderator,
491 reason,
492 channel
493 }: {
494 reason?: string;
495 user: User;
496 guild: Guild;
497 moderator?: User;
498 channel: VoiceChannel;
499 }) {
500 if (!this.channelCheck(channel)) {
501 return;
502 }
503
504 await this.sendLogEmbed(guild, {
505 title: "Member undeafened",
506 color: Colors.Green,
507 user,
508 reason,
509 footerText: "Undeafened",
510 moderator,
511 fields: [
512 {
513 name: "Channel",
514 value: channel?.toString() ?? "None"
515 },
516
517 {
518 name: "User",
519 value: userInfo(user)
520 }
521 ]
522 });
523 }
524
525 async logMemberVoiceMute({
526 user,
527 guild,
528 moderator,
529 reason,
530 channel
531 }: {
532 reason?: string;
533 user: User;
534 guild: Guild;
535 moderator?: User;
536 channel: VoiceChannel;
537 }) {
538 if (!this.channelCheck(channel)) {
539 return;
540 }
541
542 await this.sendLogEmbed(guild, {
543 title: "Member voice muted",
544 color: Colors.Red,
545 user,
546 reason,
547 footerText: "Voice Muted",
548 moderator,
549 fields: [
550 {
551 name: "Channel",
552 value: channel?.toString() ?? "None"
553 },
554
555 {
556 name: "User",
557 value: userInfo(user)
558 }
559 ]
560 });
561 }
562
563 async logMemberVoiceUnmute({
564 user,
565 guild,
566 moderator,
567 reason,
568 channel
569 }: {
570 reason?: string;
571 user: User;
572 guild: Guild;
573 moderator?: User;
574 channel: VoiceChannel;
575 }) {
576 if (!this.channelCheck(channel)) {
577 return;
578 }
579
580 await this.sendLogEmbed(guild, {
581 title: "Member unmuted",
582 color: Colors.Green,
583 user,
584 reason,
585 footerText: "Unmuted",
586 moderator,
587 fields: [
588 {
589 name: "Channel",
590 value: channel?.toString() ?? "None"
591 },
592
593 {
594 name: "User",
595 value: userInfo(user)
596 }
597 ]
598 });
599 }
600
601 async logMemberVoiceMove({
602 user,
603 guild,
604 moderator,
605 reason,
606 newChannel,
607 oldChannel
608 }: {
609 reason?: string;
610 user: User;
611 guild: Guild;
612 moderator?: User;
613 newChannel: VoiceState["channel"];
614 oldChannel: VoiceState["channel"];
615 }) {
616 if (!this.channelCheck(oldChannel, newChannel)) {
617 return;
618 }
619
620 await this.sendLogEmbed(guild, {
621 title: "Member moved to a new voice channel",
622 color: Colors.Blurple,
623 user,
624 reason,
625 footerText: "Moved",
626 moderator,
627 fields: [
628 {
629 name: "From",
630 value: oldChannel?.toString() ?? "None",
631 inline: true
632 },
633 {
634 name: "To",
635 value: newChannel?.toString() ?? "None",
636 inline: true
637 },
638 {
639 name: "User",
640 value: userInfo(user)
641 }
642 ]
643 });
644 }
645
646 async logMessageRuleAction({
647 message,
648 embedOptions = {},
649 rule,
650 actions
651 }: {
652 message: Message;
653 actions: MessageRuleType["actions"];
654 embedOptions?: CreateLogEmbedOptions;
655 rule: MessageRuleType["type"];
656 }) {
657 log("Actions", actions);
658
659 if (!this.channelCheck(message.channel)) {
660 return;
661 }
662
663 await this.sendLogEmbed(message.guild!, {
664 color: Colors.Red,
665 user: message.author,
666 footerText: "AutoMod",
667 moderator: this.client.user!,
668 ...embedOptions,
669 fields: [
670 ...(embedOptions.fields ?? []),
671 {
672 name: "Rule",
673 value: `\`${rule}\``,
674 inline: true
675 },
676 {
677 name: "Actions taken",
678 value: `\`${actions.length === 0 ? "none" : actions.join("`, `")}\``,
679 inline: true
680 },
681 {
682 name: "Message",
683 value: `${message.url}`
684 }
685 ]
686 });
687 }
688
689 async logFileFilterDeletedMessage(
690 message: Message,
691 {
692 contentType,
693 hash,
694 url
695 }: { hash: string; url: string; name?: string; contentType?: string | null }
696 ) {
697 if (!this.channelCheck(message.channel)) {
698 return;
699 }
700
701 await this.sendLogEmbed(message.guild!, {
702 title: "Blocked file detected",
703 color: Colors.Red,
704 user: message.author,
705 fields: [
706 {
707 name: "File",
708 value:
709 `${
710 name ? `[${escapeMarkdown(name)}](${url})` : `[Unnamed](${url})`
711 }: \`${hash}\`` + (contentType ? ` (\`${contentType}\`)` : "")
712 }
713 ],
714 footerText: "Deleted",
715 moderator: this.client.user!
716 });
717 }
718
719 async logMemberTimeout(
720 member: GuildMember,
721 {
722 reason,
723 id,
724 moderator
725 }: Omit<CommonUserActionOptions, "guild" | "id"> & { reason?: string; id?: string | number }
726 ) {
727 await this.sendLogEmbed(
728 member.guild,
729 {
730 title: "Member timed-out",
731 color: Colors.Red,
732 user: member.user,
733 fields: [
734 {
735 name: "Duration",
736 value: formatDistanceToNowStrict(member.communicationDisabledUntil!)
737 },
738 {
739 name: "User Information",
740 value: `Username: ${
741 member.user.username
742 }\nMention: ${member.user.toString()}\nID: ${member.user.id}`
743 }
744 ],
745 footerText: "Timed-out",
746 reason,
747 id: id?.toString(),
748 moderator
749 },
750 undefined,
751 "infraction_logging_channel"
752 );
753 }
754
755 async logMemberTimeoutRemove(
756 member: GuildMember,
757 {
758 reason,
759 id,
760 moderator
761 }: Omit<CommonUserActionOptions, "guild" | "id"> & { reason?: string; id?: string | number }
762 ) {
763 await this.sendLogEmbed(
764 member.guild,
765 {
766 title: "Member timeout removed",
767 color: Colors.Green,
768 user: member.user,
769 fields: [
770 {
771 name: "User Information",
772 value: `Username: ${
773 member.user.username
774 }\nMention: ${member.user.toString()}\nID: ${member.user.id}`
775 }
776 ],
777 footerText: "Timed-out removed",
778 reason,
779 id: id?.toString(),
780 moderator
781 },
782 undefined,
783 "infraction_logging_channel"
784 );
785 }
786
787 async logChannelCreate(channel: NonThreadGuildBasedChannel) {
788 if (!this.channelCheck(channel)) {
789 return;
790 }
791
792 await this.sendLogEmbed(channel.guild, {
793 title: "Channel Created",
794 color: Colors.Green,
795 fields: [
796 {
797 name: "Name",
798 value: channel.name
799 },
800 {
801 name: "ID",
802 value: channel.id
803 },
804 {
805 name: "Mention",
806 value: channel.toString()
807 },
808 {
809 name: "Type",
810 value: ChannelType[channel.type]
811 }
812 ],
813 footerText: "Created"
814 });
815 }
816
817 async logChannelDelete(channel: NonThreadGuildBasedChannel) {
818 if (!this.channelCheck(channel)) {
819 return;
820 }
821
822 await this.sendLogEmbed(channel.guild, {
823 title: "Channel Deleted",
824 color: Colors.Red,
825 fields: [
826 {
827 name: "Name",
828 value: channel.name
829 },
830 {
831 name: "ID",
832 value: channel.id
833 },
834 {
835 name: "Type",
836 value: ChannelType[channel.type]
837 }
838 ],
839 footerText: "Deleted"
840 });
841 }
842
843 async logChannelUpdate(
844 oldChannel: NonThreadGuildBasedChannel,
845 newChannel: NonThreadGuildBasedChannel
846 ) {
847 if (!this.channelCheck(oldChannel, newChannel)) {
848 return;
849 }
850
851 await this.sendLogEmbed(newChannel.guild, {
852 title: "Channel Updated",
853 color: Colors.Green,
854 fields: [
855 {
856 name: "Old Name",
857 value: oldChannel.name,
858 inline: true
859 },
860 {
861 name: "New Name",
862 value: newChannel.name,
863 inline: true
864 },
865 {
866 name: "ID",
867 value: newChannel.id
868 }
869 ],
870 footerText: "Updated"
871 });
872 }
873
874 async logRoleCreate(role: Role) {
875 const permissions = role.permissions.toArray();
876
877 await this.sendLogEmbed(role.guild, {
878 title: "Role Created",
879 color: Colors.Green,
880 fields: [
881 {
882 name: "Name",
883 value: role.name
884 },
885 {
886 name: "ID",
887 value: role.id
888 },
889 {
890 name: "Mention",
891 value: role.toString()
892 },
893 {
894 name: "Icon",
895 value: role.icon ? role.iconURL()! : "*None*"
896 },
897 {
898 name: "Permissions",
899 value:
900 permissions.length === 0
901 ? "*Nothing*"
902 : "`" + permissions.join("`, `") + "`"
903 }
904 ],
905 footerText: "Created"
906 });
907 }
908
909 async logRoleDelete(role: Role) {
910 const permissions = role.permissions.toArray();
911
912 await this.sendLogEmbed(role.guild, {
913 title: "Role Deleted",
914 color: Colors.Red,
915 fields: [
916 {
917 name: "Name",
918 value: role.name
919 },
920 {
921 name: "ID",
922 value: role.id
923 },
924 {
925 name: "Icon",
926 value: role.icon ? role.iconURL()! : "*None*"
927 },
928 {
929 name: "Permissions",
930 value:
931 permissions.length === 0
932 ? "*Nothing*"
933 : "`" + permissions.join("`, `") + "`"
934 }
935 ],
936 footerText: "Deleted"
937 });
938 }
939
940 async logRoleUpdate(oldRole: Role, newRole: Role) {
941 const newRolePermissions = newRole.permissions.toArray();
942 const oldRolePermissions = oldRole.permissions.toArray();
943 const addedPermissions = newRolePermissions.filter(
944 permission => !oldRolePermissions.includes(permission)
945 );
946 const removedPermissions = oldRolePermissions.filter(
947 permission => !newRolePermissions.includes(permission)
948 );
949
950 await this.sendLogEmbed(newRole.guild, {
951 title: "Role Updated",
952 color: Colors.Green,
953 fields: [
954 {
955 name: "Name",
956 value: `Old name: ${oldRole.name}\nNew name: ${newRole.name}`
957 },
958 {
959 name: "ID",
960 value: newRole.id
961 },
962 {
963 name: "Mention",
964 value: newRole.toString()
965 },
966 {
967 name: "Icon",
968 value: `Old icon: ${oldRole.icon ? oldRole.iconURL()! : "*None*"}\nNew icon: ${
969 newRole.icon ? newRole.iconURL()! : "*None*"
970 }`
971 },
972 {
973 name: "Added Permissions",
974 value:
975 addedPermissions.length === 0
976 ? "*Nothing*"
977 : "`" + addedPermissions.join("`, `") + "`",
978 inline: true
979 },
980 {
981 name: "Removed Permissions",
982 value:
983 removedPermissions.length === 0
984 ? "*Nothing*"
985 : "`" + removedPermissions.join("`, `") + "`",
986 inline: true
987 }
988 ],
989 footerText: "Updated"
990 });
991 }
992
993 async logNicknameUpdate(oldMember: GuildMember, newMember: GuildMember) {
994 await this.sendLogEmbed(newMember.guild, {
995 title: "Member nickname updated",
996 user: newMember.user,
997 color: 0x007bff,
998 fields: [
999 {
1000 name: "Old Nickname",
1001 value: oldMember.nickname ?? "*Nothing*",
1002 inline: true
1003 },
1004 {
1005 name: "New Nickname",
1006 value: newMember.nickname ?? "*Nothing*",
1007 inline: true
1008 },
1009 {
1010 name: "User Information",
1011 value: `Username: ${
1012 newMember.user.username
1013 }\nMention: ${newMember.user.toString()}\nID: ${newMember.user.id}`
1014 }
1015 ],
1016 footerText: "Updated"
1017 });
1018 }
1019
1020 async logMemberRoleUpdate(oldMember: GuildMember, newMember: GuildMember) {
1021 const added = newMember.roles.cache.filter(role => !oldMember.roles.cache.has(role.id));
1022 const removed = oldMember.roles.cache.filter(role => !newMember.roles.cache.has(role.id));
1023
1024 await this.sendLogEmbed(
1025 newMember.guild,
1026 {
1027 title: "Member roles updated",
1028 user: newMember.user,
1029 color: Colors.Green,
1030 showUserId: false,
1031 fields: [
1032 {
1033 name: "Added",
1034 value:
1035 added.size === 0
1036 ? "*Nothing added*"
1037 : added.reduce((acc, role) => `${acc} ${role.toString()}`, "")
1038 },
1039 {
1040 name: "Removed",
1041 value:
1042 removed.size === 0
1043 ? "*Nothing removed*"
1044 : removed.reduce((acc, role) => `${acc} ${role.toString()}`, "")
1045 },
1046 {
1047 name: "User Information",
1048 value: `Username: ${
1049 newMember.user.username
1050 }\nMention: ${newMember.user.toString()}\nID: ${newMember.user.id}`
1051 }
1052 ],
1053 footerText: "Roles Updated"
1054 },
1055 {
1056 allowedMentions: {
1057 roles: []
1058 }
1059 }
1060 );
1061 }
1062
1063 async logGuildMemberAdd(member: GuildMember) {
1064 if (!this.client.configManager.config[member.guild.id]?.logging?.events.member_join) {
1065 return;
1066 }
1067
1068 let members = 0,
1069 bots = 0;
1070
1071 for (const m of member.guild.members.cache.values()) {
1072 if (m.user.bot) bots++;
1073 else members++;
1074 }
1075
1076 const createdAt = Math.round((member.user.createdAt?.getTime() ?? Date.now()) / 1000);
1077 const inviteOrVanity = await this.client.inviteTracker.findNewMemberInviteLink(member);
1078
1079 await this.sendLogEmbed(
1080 member.guild,
1081 {
1082 title: "New member joined",
1083 user: member.user,
1084 color: 0x007bff,
1085 options: {
1086 description: `${member.user.toString()} just joined the server!`
1087 },
1088 fields: [
1089 {
1090 name: "New Account?",
1091 value:
1092 Date.now() - member.user.createdTimestamp < 3 * 24 * 60 * 60 * 1000
1093 ? ":warning: Yes"
1094 : "No",
1095 inline: true
1096 },
1097 {
1098 name: "Bot?",
1099 value: member.user.bot ? "Yes" : "No",
1100 inline: true
1101 },
1102 {
1103 name: "Account Created At",
1104 value: `<t:${createdAt}:f> (<t:${createdAt}:R>)`
1105 },
1106 {
1107 name: "User Information",
1108 value: `Username: ${
1109 member.user.username
1110 }\nMention: ${member.user.toString()}\nID: ${member.user.id}`,
1111 inline: true
1112 },
1113 {
1114 name: "Positions",
1115 value:
1116 `Among all members: ${members + bots}th\n` +
1117 (member.user.bot
1118 ? `Among the bots: ${bots}th`
1119 : `Among the human members: ${members}th`),
1120 inline: true
1121 },
1122 ...(inviteOrVanity
1123 ? [
1124 {
1125 name: "Invite Information",
1126 value:
1127 `Invite Link: https://discord.gg/${
1128 inviteOrVanity?.vanity?.code ??
1129 inviteOrVanity?.invite?.code
1130 }\nUses: ${
1131 inviteOrVanity?.vanity?.uses ??
1132 inviteOrVanity?.invite?.uses
1133 }` +
1134 (!inviteOrVanity.isVanity
1135 ? `\nInvited By: ${
1136 inviteOrVanity.invite.inviterId
1137 ? `<@${inviteOrVanity.invite.inviterId}> (${inviteOrVanity.invite.inviterId})`
1138 : "Unknown"
1139 }` +
1140 (inviteOrVanity.invite.createdAt
1141 ? `\nCreated: <t:${Math.round(
1142 inviteOrVanity.invite.createdAt.getTime() /
1143 1000
1144 )}:R>`
1145 : "") +
1146 (inviteOrVanity.invite.expiresAt
1147 ? `\nExpires: <t:${Math.round(
1148 inviteOrVanity.invite.expiresAt.getTime() /
1149 1000
1150 )}:R>`
1151 : "") +
1152 (inviteOrVanity.invite.channelId
1153 ? `\nChannel: <#${inviteOrVanity.invite.channelId}> (${inviteOrVanity.invite.channelId})`
1154 : "") +
1155 (inviteOrVanity.invite.temporary
1156 ? "\n\n__This is a temporary invite.__"
1157 : "")
1158 : "")
1159 }
1160 ]
1161 : [])
1162 ],
1163 footerText: `Joined • ${
1164 member.guild.members.cache.size >= member.guild.memberCount
1165 ? member.guild.members.cache.size
1166 : member.guild.memberCount
1167 } members total`
1168 },
1169 undefined,
1170 "join_leave_channel"
1171 );
1172 }
1173
1174 async logGuildMemberRemove(member: GuildMember) {
1175 if (!this.client.configManager.config[member.guild.id]?.logging?.events.member_leave) {
1176 return;
1177 }
1178
1179 const joinedAt = Math.round((member.joinedAt?.getTime() ?? Date.now()) / 1000);
1180
1181 await this.sendLogEmbed(
1182 member.guild,
1183 {
1184 title: "Member left",
1185 user: member.user,
1186 color: 0xf14a60,
1187 fields: [
1188 {
1189 name: "Roles",
1190 value:
1191 member.roles.cache.size === 1
1192 ? "**No roles**"
1193 : member.roles.cache
1194 .filter(role => role.id !== member.guild.id)
1195 .sort((m1, m2) => m2.position - m1.position)
1196 .reduce((acc, role) => `${acc} ${roleMention(role.id)}`, "")
1197 },
1198 {
1199 name: "Joined At",
1200 value: `<t:${joinedAt}:f> (<t:${joinedAt}:R>)`
1201 },
1202 {
1203 name: "User Information",
1204 value: `Username: ${
1205 member.user.username
1206 }\nMention: ${member.user.toString()}\nID: ${member.user.id}`
1207 },
1208 {
1209 name: "Bot?",
1210 value: member.user.bot ? "Yes" : "No",
1211 inline: true
1212 }
1213 ],
1214 footerText: `Left • ${
1215 member.guild.members.cache.size >= member.guild.memberCount
1216 ? member.guild.members.cache.size
1217 : member.guild.memberCount
1218 } members total`
1219 },
1220 undefined,
1221 "join_leave_channel"
1222 );
1223 }
1224
1225 async logMessageEdit(oldMessage: Message, newMessage: Message) {
1226 if (!this.client.configManager.config[newMessage.guildId!]?.logging?.events.message_edit) {
1227 return;
1228 }
1229
1230 if (!this.channelCheck(newMessage.channel ?? oldMessage.channel)) {
1231 return;
1232 }
1233
1234 const changedEmbeds = [];
1235 const mainArray =
1236 oldMessage.embeds.length > newMessage.embeds.length
1237 ? oldMessage.embeds
1238 : newMessage.embeds;
1239 const mainArrayRef =
1240 oldMessage.embeds.length > newMessage.embeds.length ? "oldMessage" : "newMessage";
1241 const otherArray =
1242 oldMessage.embeds.length > newMessage.embeds.length
1243 ? newMessage.embeds
1244 : oldMessage.embeds;
1245
1246 outerLoop: for (const embed of mainArray) {
1247 for (const otherEmbed of otherArray) {
1248 if (embed.equals(otherEmbed)) {
1249 continue outerLoop;
1250 }
1251 }
1252
1253 changedEmbeds.push({
1254 old: mainArrayRef === "oldMessage" ? embed : otherArray,
1255 new: mainArrayRef === "newMessage" ? embed : otherArray
1256 });
1257 }
1258
1259 const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
1260 new ButtonBuilder()
1261 .setStyle(ButtonStyle.Link)
1262 .setLabel("Go to context")
1263 .setURL(
1264 `https://discord.com/channels/${newMessage.guildId!}/${newMessage.channelId!}/${
1265 newMessage.id
1266 }`
1267 )
1268 );
1269
1270 if (newMessage.type === MessageType.Reply)
1271 row.addComponents(
1272 new ButtonBuilder()
1273 .setStyle(ButtonStyle.Link)
1274 .setLabel("Go to referenced message")
1275 .setURL(
1276 `https://discord.com/channels/${newMessage.guildId!}/${newMessage.channelId!}/${
1277 newMessage.reference!.messageId
1278 }`
1279 )
1280 );
1281
1282 await this.sendLogEmbed(
1283 newMessage.guild!,
1284 {
1285 title: "Message Updated",
1286 user: newMessage.author,
1287 color: 0x007bff,
1288 options: {
1289 description: `### Before\n${oldMessage.content}\n\n### After\n${newMessage.content}`
1290 },
1291 fields: [
1292 {
1293 name: "User",
1294 value: `${newMessage.author.toString()}\nUsername: ${
1295 newMessage.author.username
1296 }\nID: ${newMessage.author.id}`
1297 },
1298 {
1299 name: "Channel",
1300 value: `${newMessage.channel.toString()}\nName: ${
1301 (newMessage.channel as TextChannel).name
1302 }\nID: ${newMessage.channel.id}`,
1303 inline: true
1304 },
1305 {
1306 name: "Message",
1307 value: `Link: [Click here](${`https://discord.com/channels/${newMessage.guildId!}/${newMessage.channelId!}/${
1308 newMessage.id
1309 }`})\nID: ${newMessage.id}`,
1310 inline: true
1311 },
1312 ...(changedEmbeds.length > 0
1313 ? [
1314 {
1315 name: "Embeds",
1316 value: "The changed embeds are sent as JSON with this log message."
1317 }
1318 ]
1319 : [])
1320 ],
1321 footerText: "Updated"
1322 },
1323 {
1324 components: [row],
1325 files:
1326 changedEmbeds.length > 0
1327 ? [
1328 {
1329 name: "changed_embeds.json",
1330 attachment: Buffer.from(JSON.stringify(changedEmbeds, null, 4))
1331 }
1332 ]
1333 : undefined
1334 },
1335 "message_logging_channel"
1336 );
1337 }
1338
1339 async logMessageDelete(message: Message, moderator?: User | null) {
1340 if (!this.client.configManager.config[message.guildId!]?.logging?.events.message_delete) {
1341 return;
1342 }
1343
1344 if (!this.channelCheck(message.channel)) {
1345 return;
1346 }
1347
1348 const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
1349 new ButtonBuilder()
1350 .setStyle(ButtonStyle.Link)
1351 .setLabel("Go to context")
1352 .setURL(
1353 `https://discord.com/channels/${message.guildId!}/${message.channelId!}/${
1354 message.id
1355 }`
1356 )
1357 );
1358
1359 if (message.type === MessageType.Reply)
1360 row.addComponents(
1361 new ButtonBuilder()
1362 .setStyle(ButtonStyle.Link)
1363 .setLabel("Go to referenced message")
1364 .setURL(
1365 `https://discord.com/channels/${message.guildId!}/${message.channelId!}/${
1366 message.reference!.messageId
1367 }`
1368 )
1369 );
1370
1371 const fields = [
1372 {
1373 name: "User",
1374 value: `${message.author.toString()}\nUsername: ${message.author.username}\nID: ${
1375 message.author.id
1376 }`,
1377 inline: !!moderator
1378 }
1379 ];
1380
1381 if (moderator) {
1382 fields.push({
1383 name: "Responsible Moderator",
1384 value: userInfo(moderator),
1385 inline: true
1386 });
1387 }
1388
1389 fields.push(
1390 {
1391 name: "Channel",
1392 value: `${message.channel.toString()}\nName: ${
1393 (message.channel as TextChannel).name
1394 }\nID: ${message.channel.id}`,
1395 inline: !moderator
1396 },
1397 {
1398 name: "Message",
1399 value: `Link: [Click here](${`https://discord.com/channels/${message.guildId!}/${message.channelId!}/${
1400 message.id
1401 }`})\nID: ${message.id}`,
1402 inline: true
1403 }
1404 );
1405
1406 await this.sendLogEmbed(
1407 message.guild!,
1408 {
1409 title: "Message Deleted",
1410 color: Colors.Red,
1411 user: message.author,
1412 options: {
1413 description: message.content
1414 },
1415 fields,
1416 footerText: "Deleted"
1417 },
1418 {
1419 components: [row],
1420 files: [
1421 ...message.attachments
1422 .map(
1423 a =>
1424 ({
1425 attachment: a.proxyURL,
1426 name: a.name
1427 }) as AttachmentBuilder
1428 )
1429 .values()
1430 ],
1431 embeds: message.embeds
1432 },
1433 "message_logging_channel"
1434 );
1435 }
1436
1437 async logRaid({ guild, action }: { guild: Guild; action: string }) {
1438 await this.sendLogEmbed(guild, {
1439 title: "Possible raid detected",
1440 reason: "Too many users joined in a short timeframe.",
1441 color: Colors.Red,
1442 fields: [
1443 {
1444 name: "Action",
1445 value: action
1446 }
1447 ],
1448 footerText: "Raid detected"
1449 });
1450 }
1451
1452 async logServerLockOrUnlock({
1453 guild,
1454 action,
1455 moderator,
1456 countInvalidChannel,
1457 countSkipped,
1458 countFailed,
1459 countSuccess,
1460 reason
1461 }: {
1462 guild: Guild;
1463 action: "Locked" | "Unlocked";
1464 moderator: User;
1465 countInvalidChannel: number;
1466 countSkipped: number;
1467 countFailed: number;
1468 countSuccess: number;
1469 reason?: string;
1470 }) {
1471 const results = `${
1472 countInvalidChannel === 0 ? "" : `InvalidChannel: ${countInvalidChannel}\n`
1473 }${countSkipped === 0 ? "" : `Skipped: ${countSkipped}\n`}${
1474 countSuccess === 0 ? "" : `Success: ${countSuccess}\n`
1475 }${countFailed === 0 ? "" : `Failed: ${countFailed}\n`}`;
1476
1477 await this.sendLogEmbed(guild, {
1478 title: `Server ${action.toLowerCase()}`,
1479 reason: reason ?? "The user ran a command to perform this action",
1480 moderator,
1481 color: 0x007bff,
1482 footerText: action,
1483 options: {
1484 description: `Results:\n\n${results.trim() === "" ? "*Nothing changed*" : results}`
1485 }
1486 });
1487 }
1488
1489 async logChannelLockOrUnlock({
1490 guild,
1491 action,
1492 moderator,
1493 channel,
1494 reason
1495 }: {
1496 guild: Guild;
1497 action: "Locked" | "Unlocked";
1498 moderator: User;
1499 channel: GuildChannel;
1500 reason?: string;
1501 }) {
1502 await this.sendLogEmbed(guild, {
1503 title: `Channel ${action.toLowerCase()}`,
1504 reason: reason ?? "The user ran a command to perform this action",
1505 moderator,
1506 color: 0x007bff,
1507 footerText: action,
1508 fields: [
1509 {
1510 name: "Channel",
1511 value: `${channel.toString()} (${channel.id})`
1512 }
1513 ]
1514 });
1515 }
1516
1517 async logUserBan({
1518 moderator,
1519 user,
1520 deleteMessageSeconds,
1521 reason,
1522 guild,
1523 id,
1524 duration,
1525 includeDeleteMessageSeconds = true
1526 }: LogUserBanOptions) {
1527 await this.sendLogEmbed(
1528 guild,
1529 {
1530 user,
1531 title: "A user was banned",
1532 footerText: (duration ? "Temporarily " : "") + "Banned",
1533 reason: reason ?? null,
1534 moderator,
1535 id,
1536 color: Colors.Red,
1537 fields: [
1538 ...(includeDeleteMessageSeconds
1539 ? [
1540 {
1541 name: "Message Deletion Timeframe",
1542 value: deleteMessageSeconds
1543 ? formatDistanceToNowStrict(
1544 new Date(Date.now() - deleteMessageSeconds * 1000)
1545 )
1546 : "*No timeframe provided*"
1547 }
1548 ]
1549 : []),
1550 ...(duration
1551 ? [
1552 {
1553 name: "Duration",
1554 value: formatDistanceToNowStrict(new Date(Date.now() - duration))
1555 }
1556 ]
1557 : [])
1558 ]
1559 },
1560 undefined,
1561 "infraction_logging_channel"
1562 );
1563 }
1564
1565 async logUserSoftBan({
1566 moderator,
1567 user,
1568 deleteMessageSeconds,
1569 reason,
1570 guild,
1571 id
1572 }: LogUserBanOptions) {
1573 await this.sendLogEmbed(
1574 guild,
1575 {
1576 user,
1577 title: "A user was softbanned",
1578 footerText: "Softbanned",
1579 reason: reason ?? null,
1580 moderator,
1581 id,
1582 color: Colors.Red,
1583 fields: [
1584 {
1585 name: "Message Deletion Timeframe",
1586 value: deleteMessageSeconds
1587 ? formatDistanceToNowStrict(
1588 new Date(Date.now() - deleteMessageSeconds * 1000)
1589 )
1590 : "*No timeframe provided*"
1591 }
1592 ]
1593 },
1594 undefined,
1595 "infraction_logging_channel"
1596 );
1597 }
1598
1599 async logUserUnban({ moderator, user, reason, guild, id }: LogUserUnbanOptions) {
1600 this.sendLogEmbed(
1601 guild,
1602 {
1603 user,
1604 title: "A user was unbanned",
1605 footerText: "Unbanned",
1606 reason: reason ?? null,
1607 moderator,
1608 id,
1609 color: Colors.Green
1610 },
1611 undefined,
1612 "infraction_logging_channel"
1613 );
1614 }
1615
1616 async logMemberKick({
1617 moderator,
1618 member,
1619 reason,
1620 guild,
1621 id,
1622 user
1623 }: CommonUserActionOptions & { member?: GuildMember; user?: User; reason?: string }) {
1624 this.sendLogEmbed(
1625 guild,
1626 {
1627 user: user ?? member!.user,
1628 title: "A member was kicked",
1629 footerText: "Kicked",
1630 reason: reason ?? null,
1631 moderator,
1632 id,
1633 color: Colors.Orange
1634 },
1635 undefined,
1636 "infraction_logging_channel"
1637 );
1638 }
1639
1640 async logMemberMute({
1641 moderator,
1642 member,
1643 reason,
1644 guild,
1645 id,
1646 duration
1647 }: CommonUserActionOptions & { member: GuildMember; reason?: string; duration?: number }) {
1648 this.sendLogEmbed(
1649 guild,
1650 {
1651 user: member.user,
1652 title: "A member was muted",
1653 footerText: "Muted",
1654 reason: reason ?? null,
1655 moderator,
1656 id,
1657 color: Colors.DarkGold,
1658 fields: [
1659 {
1660 name: "Duration",
1661 value: duration
1662 ? formatDistanceToNowStrict(new Date(Date.now() - duration))
1663 : "*No duration was specified*"
1664 }
1665 ]
1666 },
1667 undefined,
1668 "infraction_logging_channel"
1669 );
1670 }
1671
1672 async logMemberWarning({
1673 moderator,
1674 member,
1675 reason,
1676 guild,
1677 id
1678 }: CommonUserActionOptions & { member: GuildMember; reason?: string }) {
1679 this.sendLogEmbed(
1680 guild,
1681 {
1682 user: member.user,
1683 title: "A member was warned",
1684 footerText: "Warned",
1685 reason: reason ?? null,
1686 moderator,
1687 id,
1688 color: Colors.Gold
1689 },
1690 undefined,
1691 "infraction_logging_channel"
1692 );
1693 }
1694
1695 async logBulkDeleteMessages({
1696 messages,
1697 moderator,
1698 user,
1699 reason,
1700 guild,
1701 id,
1702 count,
1703 channel
1704 }: LogMessageBulkDelete) {
1705 const sendJSON = this.client.configManager.config[guild.id]?.logging?.bulk_delete_send_json;
1706
1707 if (!this.channelCheck(channel)) {
1708 return;
1709 }
1710
1711 const message = await this.sendLogEmbed(
1712 guild,
1713 {
1714 user,
1715 title: "Messages deleted in bulk",
1716 footerText: "Deleted",
1717 reason: reason ?? null,
1718 moderator,
1719 id,
1720 color: Colors.DarkRed,
1721 fields: [
1722 {
1723 name: "Deleted Message Count",
1724 value: `${count}`
1725 },
1726 {
1727 name: "Channel",
1728 value: `${channel.toString()} (${channel.id})`
1729 }
1730 ]
1731 },
1732 sendJSON && messages.length > 0
1733 ? {
1734 files: [
1735 {
1736 attachment: this.generateBulkDeleteJSON(messages),
1737 name: "messages.json"
1738 }
1739 ]
1740 }
1741 : undefined,
1742 "infraction_logging_channel"
1743 );
1744
1745 if (messages.length > 0 && sendJSON) {
1746 message
1747 ?.edit({
1748 embeds: [
1749 {
1750 ...(message?.embeds[0]?.data ?? {}),
1751 fields: [
1752 ...(message?.embeds[0].data.fields ?? []),
1753 {
1754 name: "Messages",
1755 value: `[Click here to view the deleted messages](${
1756 process.env.FRONTEND_URL
1757 }/view_deleted_messages?url=${encodeURIComponent(
1758 message.attachments.at(0)!.url
1759 )})`
1760 }
1761 ]
1762 }
1763 ]
1764 })
1765 .catch(logError);
1766 }
1767 }
1768
1769 async logMemberUnmute({
1770 moderator,
1771 member,
1772 reason,
1773 guild,
1774 id
1775 }: CommonUserActionOptions & { member: GuildMember; reason?: string }) {
1776 this.sendLogEmbed(
1777 guild,
1778 {
1779 user: member.user,
1780 title: "A member was unmuted",
1781 footerText: "Unmuted",
1782 reason: reason ?? null,
1783 moderator,
1784 id,
1785 color: Colors.Green
1786 },
1787 undefined,
1788 "infraction_logging_channel"
1789 );
1790 }
1791
1792 async logBlockedWordOrToken({
1793 guild,
1794 user,
1795 blockType,
1796 token,
1797 word,
1798 message,
1799 content
1800 }: BlockedTokenOrWordOptions) {
1801 let value: string;
1802 let title: string;
1803
1804 switch (blockType) {
1805 case "token":
1806 value = `||${escapeMarkdown(token!)}||`;
1807 title = "Posted blocked token(s)";
1808 break;
1809 case "word":
1810 value = `||${escapeMarkdown(word!)}||`;
1811 title = "Posted blocked word(s)";
1812 break;
1813 case "message":
1814 value = `||${escapeMarkdown(message!)}||`;
1815 title = "Posted blocked message(s)";
1816 break;
1817 default:
1818 return;
1819 }
1820
1821 this.sendLogEmbed(guild, {
1822 user,
1823 title,
1824 footerText: "AutoMod",
1825 color: Colors.Yellow,
1826 fields: [
1827 {
1828 name: blockType[0].toUpperCase() + blockType.substring(1),
1829 value
1830 }
1831 ],
1832 options: {
1833 description: `${content}`
1834 }
1835 });
1836 }
1837
1838 async logUserMassBan({
1839 users,
1840 reason,
1841 guild,
1842 moderator,
1843 deleteMessageSeconds
1844 }: LogUserMassBanOptions) {
1845 await this.sendLogEmbed(
1846 guild,
1847 {
1848 title: "A massban was executed",
1849 footerText: "Banned",
1850 reason: reason ?? null,
1851 moderator,
1852 color: Colors.Red,
1853 fields: [
1854 {
1855 name: "Message Deletion Timeframe",
1856 value: deleteMessageSeconds
1857 ? formatDistanceToNowStrict(
1858 new Date(Date.now() - deleteMessageSeconds * 1000)
1859 )
1860 : "*No timeframe provided*"
1861 }
1862 ],
1863 options: {
1864 description: `The following users were banned:\n\n${users.reduce(
1865 (acc, user) =>
1866 acc + (acc === "" ? "" : "\n") + "<@" + user + "> (`" + user + "`)",
1867 ""
1868 )}`
1869 }
1870 },
1871 undefined,
1872 "infraction_logging_channel"
1873 );
1874 }
1875
1876 async logMemberMassKick({
1877 users,
1878 reason,
1879 guild,
1880 moderator
1881 }: Omit<LogUserMassBanOptions, "deleteMessageSeconds">) {
1882 await this.sendLogEmbed(
1883 guild,
1884 {
1885 title: "A masskick was executed",
1886 footerText: "Kicked",
1887 reason: reason ?? null,
1888 moderator,
1889 color: Colors.Orange,
1890 options: {
1891 description: `The following users were kicked:\n\n${users.reduce(
1892 (acc, user) =>
1893 acc + (acc === "" ? "" : "\n") + "<@" + user + "> (`" + user + "`)",
1894 ""
1895 )}`
1896 }
1897 },
1898 undefined,
1899 "infraction_logging_channel"
1900 );
1901 }
1902
1903 generateBulkDeleteJSON(messages: MessageResolvable[]) {
1904 const mappedMessages = (
1905 (messages instanceof Collection ? [...messages.values()] : messages) as Message[]
1906 ).map(m => ({
1907 ...m,
1908 author: m.author,
1909 member: m.member,
1910 authorColor: m.member?.displayColor ?? m.member?.roles.highest.color,
1911 authorRoleIcon: m.member?.roles.highest.iconURL() ?? undefined,
1912 authorRoleName: m.member?.roles.highest.name ?? undefined,
1913 authorAvatarURL: m.author.displayAvatarURL(),
1914 mentions: {
1915 everyone: m.mentions.everyone,
1916 users: m.mentions.users.map(({ id, username }) => ({
1917 id,
1918 username
1919 })),
1920 members:
1921 m.mentions.members?.map(({ nickname, user }) => ({
1922 nickname: nickname ?? user.username,
1923 id: user.id
1924 })) ?? [],
1925 channels: (m.mentions.channels as Collection<string, GuildChannel>).map(
1926 ({ id, name }) => ({ id, name })
1927 ),
1928 roles: m.mentions.roles.map(({ id, name }) => ({ id, name }))
1929 }
1930 }));
1931
1932 return Buffer.from(
1933 JSON.stringify(
1934 {
1935 messages: mappedMessages,
1936 generatedAt: new Date().toISOString(),
1937 channel: (messages.at(0) as Message)!.channel.toJSON({
1938 id: true,
1939 name: true,
1940 type: true
1941 }),
1942 guild: {
1943 id: (messages.at(0) as Message)!.guild!.id,
1944 name: (messages.at(0) as Message)!.guild!.name,
1945 iconURL: (messages.at(0) as Message)!.guild!.iconURL() ?? undefined
1946 },
1947 version: this.client.metadata.data.version
1948 },
1949 null,
1950 4
1951 )
1952 );
1953 }
1954 }
1955
1956 interface LogMessageBulkDelete extends Omit<CommonUserActionOptions, "id"> {
1957 user?: User;
1958 reason?: string;
1959 count: number;
1960 channel: TextChannel;
1961 id?: string;
1962 messages: MessageResolvable[];
1963 }
1964
1965 interface LogUserBanOptions extends BanOptions, CommonUserActionOptions {
1966 user: User;
1967 duration?: number;
1968 includeDeleteMessageSeconds?: boolean;
1969 }
1970
1971 interface LogUserMassBanOptions extends BanOptions, Omit<CommonUserActionOptions, "id"> {
1972 users: string[];
1973 }
1974
1975 interface LogUserUnbanOptions extends CommonUserActionOptions {
1976 user: User;
1977 reason?: string;
1978 }
1979
1980 interface CommonUserActionOptions {
1981 moderator: User;
1982 guild: Guild;
1983 id: string;
1984 }
1985
1986 interface BlockedTokenOrWordOptions {
1987 blockType: "token" | "word" | "message";
1988 token?: string;
1989 word?: string;
1990 message?: string;
1991 guild: Guild;
1992 user: User;
1993 content: string;
1994 }
1995
1996 export interface CreateLogEmbedOptions {
1997 id?: string;
1998 title?: string;
1999 options?: EmbedData;
2000 moderator?: User;
2001 user?: User;
2002 fields?: APIEmbedField[];
2003 footerText?: string;
2004 reason?: string | null;
2005 timestamp?: Date | false | null;
2006 color?: ColorResolvable;
2007 showUserId?: boolean;
2008 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26