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

Annotation of /branches/6.x/src/services/LoggerService.ts

Parent Directory Parent Directory | Revision Log Revision Log


Revision 577 - (hide annotations)
Mon Jul 29 18:52:37 2024 UTC (8 months ago) by rakinar2
File MIME type: application/typescript
File size: 47129 byte(s)
chore: add old version archive branches (2.x to 9.x-dev)
1 rakinar2 577 /**
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 logAIAutoModMessageDelete({
174     message,
175     toxicityScore,
176     severeToxicityScore,
177     threatScore,
178     isToxic,
179     isThreat,
180     isSeverelyToxic,
181     isExplicit,
182     isFlirty,
183     isAttack,
184     isInsult,
185     isProfanity,
186     explicitScore,
187     flirtationScore,
188     identityAttackScore,
189     insultScore,
190     profanityScore
191     }: {
192     message: Message;
193     toxicityScore: number;
194     severeToxicityScore: number;
195     threatScore: number;
196     explicitScore: number;
197     flirtationScore: number;
198     identityAttackScore: number;
199     insultScore: number;
200     profanityScore: number;
201     isToxic?: boolean;
202     isThreat?: boolean;
203     isSeverelyToxic?: boolean;
204     isExplicit?: boolean;
205     isFlirty?: boolean;
206     isAttack?: boolean;
207     isInsult?: boolean;
208     isProfanity?: boolean;
209     }) {
210     const {
211     max_severe_toxicity,
212     max_threat,
213     max_toxicity,
214     max_explicit,
215     max_flirtation,
216     max_identity_attack,
217     max_insult,
218     max_profanity
219     } = this.client.configManager.config[message.guildId!]?.ai_automod?.parameters ?? {
220     max_severe_toxicity: 100,
221     max_threat: 100,
222     max_toxicity: 100,
223     max_explicit: 100,
224     max_flirtation: 100,
225     max_identity_attack: 100,
226     max_insult: 100,
227     max_profanity: 100
228     };
229    
230     const threat = isThreat === undefined ? threatScore >= max_threat : isThreat;
231     const toxic = isToxic === undefined ? toxicityScore >= max_toxicity : isToxic;
232     const severeToxic = isSeverelyToxic === undefined ? severeToxicityScore >= max_severe_toxicity : isSeverelyToxic;
233    
234     const explicit = isExplicit ?? explicitScore >= max_explicit;
235     const flirty = isFlirty ?? flirtationScore >= max_flirtation;
236     const attack = isAttack ?? identityAttackScore >= max_identity_attack;
237     const insult = isInsult ?? insultScore >= max_insult;
238     const profanity = isProfanity ?? profanityScore >= max_profanity;
239    
240     let messageType: string = "removed for unknown reason";
241    
242     if (threat) {
243     messageType = "threatening";
244     } else if (toxic) {
245     messageType = "toxic";
246     } else if (severeToxic) {
247     messageType = "severly toxic";
248     } else if (explicit) {
249     messageType = "sexually explicit";
250     } else if (flirty) {
251     messageType = "flirty";
252     } else if (attack) {
253     messageType = "attacking";
254     } else if (insult) {
255     messageType = "insulting";
256     } else if (profanity) {
257     messageType = "flagged for profanity";
258     }
259    
260     await this.sendLogEmbed(message.guild!, {
261     title: "AI AutoMod has flagged this message",
262     color: Colors.Red,
263     user: message.author,
264     fields: [
265     {
266     name: "Score",
267     value: `Toxicity: ${toxicityScore.toFixed(2)}%\nThreat: ${threatScore.toFixed(
268     2
269     )}%\nSevere Toxicity: ${severeToxicityScore.toFixed(2)}%\nNSFW: ${explicitScore.toFixed(
270     2
271     )}%\nFlirtation: ${flirtationScore.toFixed(2)}%\nIdentity Attack: ${identityAttackScore.toFixed(
272     2
273     )}%\nInsult: ${insultScore.toFixed(2)}%\nProfanity: ${profanityScore.toFixed(2)}%`
274     },
275     {
276     name: "Reason",
277     value: messageType ? `This message seems to be ${messageType}.` : "Unknown"
278     }
279     ],
280     footerText: "Flagged",
281     moderator: this.client.user!
282     });
283     }
284    
285     async logMessageRuleAction({
286     message,
287     embedOptions = {},
288     rule,
289     actions
290     }: {
291     message: Message;
292     actions: MessageRuleType["actions"];
293     embedOptions?: CreateLogEmbedOptions;
294     rule: MessageRuleType["type"];
295     }) {
296     log("Actions", actions);
297    
298     await this.sendLogEmbed(message.guild!, {
299     color: Colors.Red,
300     user: message.author,
301     footerText: "AutoMod",
302     moderator: this.client.user!,
303     ...embedOptions,
304     fields: [
305     ...(embedOptions.fields ?? []),
306     {
307     name: "Rule",
308     value: `\`${rule}\``,
309     inline: true
310     },
311     {
312     name: "Actions taken",
313     value: `\`${actions.length === 0 ? "none" : actions.join("`, `")}\``,
314     inline: true
315     },
316     {
317     name: "Message",
318     value: `${message.url}`
319     }
320     ]
321     });
322     }
323    
324     async logFileFilterDeletedMessage(
325     message: Message,
326     { contentType, hash, url }: { hash: string; url: string; name?: string; contentType?: string | null }
327     ) {
328     await this.sendLogEmbed(message.guild!, {
329     title: "Blocked file detected",
330     color: Colors.Red,
331     user: message.author,
332     fields: [
333     {
334     name: "File",
335     value:
336     `${name ? `[${escapeMarkdown(name)}](${url})` : `[Unnamed](${url})`}: \`${hash}\`` +
337     (contentType ? ` (\`${contentType}\`)` : "")
338     }
339     ],
340     footerText: "Deleted",
341     moderator: this.client.user!
342     });
343     }
344    
345     async logMemberTimeout(
346     member: GuildMember,
347     { reason, id, moderator }: Omit<CommonUserActionOptions, "guild" | "id"> & { reason?: string; id?: string | number }
348     ) {
349     await this.sendLogEmbed(member.guild, {
350     title: "Member timed-out",
351     color: Colors.Red,
352     user: member.user,
353     fields: [
354     {
355     name: "Duration",
356     value: formatDistanceToNowStrict(member.communicationDisabledUntil!)
357     },
358     {
359     name: "User Information",
360     value: `Username: ${member.user.username}\nMention: ${member.user.toString()}\nID: ${member.user.id}`
361     }
362     ],
363     footerText: "Timed-out",
364     reason,
365     id: id?.toString(),
366     moderator
367     });
368     }
369    
370     async logMemberTimeoutRemove(
371     member: GuildMember,
372     { reason, id, moderator }: Omit<CommonUserActionOptions, "guild" | "id"> & { reason?: string; id?: string | number }
373     ) {
374     await this.sendLogEmbed(member.guild, {
375     title: "Member timeout removed",
376     color: Colors.Green,
377     user: member.user,
378     fields: [
379     {
380     name: "User Information",
381     value: `Username: ${member.user.username}\nMention: ${member.user.toString()}\nID: ${member.user.id}`
382     }
383     ],
384     footerText: "Timed-out removed",
385     reason,
386     id: id?.toString(),
387     moderator
388     });
389     }
390    
391     async logChannelCreate(channel: NonThreadGuildBasedChannel) {
392     await this.sendLogEmbed(channel.guild, {
393     title: "Channel Created",
394     color: Colors.Green,
395     fields: [
396     {
397     name: "Name",
398     value: channel.name
399     },
400     {
401     name: "ID",
402     value: channel.id
403     },
404     {
405     name: "Mention",
406     value: channel.toString()
407     },
408     {
409     name: "Type",
410     value: ChannelType[channel.type]
411     }
412     ],
413     footerText: "Created"
414     });
415     }
416    
417     async logChannelDelete(channel: NonThreadGuildBasedChannel) {
418     await this.sendLogEmbed(channel.guild, {
419     title: "Channel Deleted",
420     color: Colors.Red,
421     fields: [
422     {
423     name: "Name",
424     value: channel.name
425     },
426     {
427     name: "ID",
428     value: channel.id
429     },
430     {
431     name: "Type",
432     value: ChannelType[channel.type]
433     }
434     ],
435     footerText: "Deleted"
436     });
437     }
438    
439     async logChannelUpdate(oldChannel: NonThreadGuildBasedChannel, newChannel: NonThreadGuildBasedChannel) {
440     await this.sendLogEmbed(newChannel.guild, {
441     title: "Channel Updated",
442     color: Colors.Green,
443     fields: [
444     {
445     name: "Old Name",
446     value: oldChannel.name,
447     inline: true
448     },
449     {
450     name: "New Name",
451     value: newChannel.name,
452     inline: true
453     },
454     {
455     name: "ID",
456     value: newChannel.id
457     }
458     ],
459     footerText: "Updated"
460     });
461     }
462    
463     async logRoleCreate(role: Role) {
464     const permissions = role.permissions.toArray();
465    
466     await this.sendLogEmbed(role.guild, {
467     title: "Role Created",
468     color: Colors.Green,
469     fields: [
470     {
471     name: "Name",
472     value: role.name
473     },
474     {
475     name: "ID",
476     value: role.id
477     },
478     {
479     name: "Mention",
480     value: role.toString()
481     },
482     {
483     name: "Icon",
484     value: role.icon ? role.iconURL()! : "*None*"
485     },
486     {
487     name: "Permissions",
488     value: permissions.length === 0 ? "*Nothing*" : "`" + permissions.join("`, `") + "`"
489     }
490     ],
491     footerText: "Created"
492     });
493     }
494    
495     async logRoleDelete(role: Role) {
496     const permissions = role.permissions.toArray();
497    
498     await this.sendLogEmbed(role.guild, {
499     title: "Role Deleted",
500     color: Colors.Red,
501     fields: [
502     {
503     name: "Name",
504     value: role.name
505     },
506     {
507     name: "ID",
508     value: role.id
509     },
510     {
511     name: "Icon",
512     value: role.icon ? role.iconURL()! : "*None*"
513     },
514     {
515     name: "Permissions",
516     value: permissions.length === 0 ? "*Nothing*" : "`" + permissions.join("`, `") + "`"
517     }
518     ],
519     footerText: "Deleted"
520     });
521     }
522    
523     async logRoleUpdate(oldRole: Role, newRole: Role) {
524     const newRolePermissions = newRole.permissions.toArray();
525     const oldRolePermissions = oldRole.permissions.toArray();
526     const addedPermissions = newRolePermissions.filter(permission => !oldRolePermissions.includes(permission));
527     const removedPermissions = oldRolePermissions.filter(permission => !newRolePermissions.includes(permission));
528    
529     await this.sendLogEmbed(newRole.guild, {
530     title: "Role Updated",
531     color: Colors.Green,
532     fields: [
533     {
534     name: "Name",
535     value: `Old name: ${oldRole.name}\nNew name: ${newRole.name}`
536     },
537     {
538     name: "ID",
539     value: newRole.id
540     },
541     {
542     name: "Mention",
543     value: newRole.toString()
544     },
545     {
546     name: "Icon",
547     value: `Old icon: ${oldRole.icon ? oldRole.iconURL()! : "*None*"}\nNew icon: ${
548     newRole.icon ? newRole.iconURL()! : "*None*"
549     }`
550     },
551     {
552     name: "Added Permissions",
553     value: addedPermissions.length === 0 ? "*Nothing*" : "`" + addedPermissions.join("`, `") + "`",
554     inline: true
555     },
556     {
557     name: "Removed Permissions",
558     value: removedPermissions.length === 0 ? "*Nothing*" : "`" + removedPermissions.join("`, `") + "`",
559     inline: true
560     }
561     ],
562     footerText: "Updated"
563     });
564     }
565    
566     async logNicknameUpdate(oldMember: GuildMember, newMember: GuildMember) {
567     await this.sendLogEmbed(newMember.guild, {
568     title: "Member nickname updated",
569     user: newMember.user,
570     color: 0x007bff,
571     fields: [
572     {
573     name: "Old Nickname",
574     value: oldMember.nickname ?? "*Nothing*",
575     inline: true
576     },
577     {
578     name: "New Nickname",
579     value: newMember.nickname ?? "*Nothing*",
580     inline: true
581     },
582     {
583     name: "User Information",
584     value: `Username: ${newMember.user.username}\nMention: ${newMember.user.toString()}\nID: ${newMember.user.id}`
585     }
586     ],
587     footerText: "Updated"
588     });
589     }
590    
591     async logMemberRoleUpdate(oldMember: GuildMember, newMember: GuildMember) {
592     const added = newMember.roles.cache.filter(role => !oldMember.roles.cache.has(role.id));
593     const removed = oldMember.roles.cache.filter(role => !newMember.roles.cache.has(role.id));
594    
595     await this.sendLogEmbed(
596     newMember.guild,
597     {
598     title: "Member roles updated",
599     user: newMember.user,
600     color: Colors.Green,
601     showUserId: false,
602     fields: [
603     {
604     name: "Added",
605     value: added.size === 0 ? "*Nothing added*" : added.reduce((acc, role) => `${acc} ${role.toString()}`, "")
606     },
607     {
608     name: "Removed",
609     value:
610     removed.size === 0
611     ? "*Nothing removed*"
612     : removed.reduce((acc, role) => `${acc} ${role.toString()}`, "")
613     },
614     {
615     name: "User Information",
616     value: `Username: ${newMember.user.username}\nMention: ${newMember.user.toString()}\nID: ${
617     newMember.user.id
618     }`
619     }
620     ],
621     footerText: "Roles Updated"
622     },
623     {
624     allowedMentions: {
625     roles: []
626     }
627     }
628     );
629     }
630    
631     async logGuildMemberAdd(member: GuildMember) {
632     if (!this.client.configManager.config[member.guild.id]?.logging?.events.member_join) {
633     return;
634     }
635    
636     let members = 0,
637     bots = 0;
638    
639     for (const m of member.guild.members.cache.values()) {
640     if (m.user.bot) bots++;
641     else members++;
642     }
643    
644     const createdAt = Math.round((member.user.createdAt?.getTime() ?? Date.now()) / 1000);
645     const inviteOrVanity = await this.client.inviteTracker.findNewMemberInviteLink(member);
646    
647     console.log(createdAt);
648    
649     await this.sendLogEmbed(
650     member.guild,
651     {
652     title: "New member joined",
653     user: member.user,
654     color: 0x007bff,
655     options: {
656     description: `${member.user.toString()} just joined the server!`
657     },
658     fields: [
659     {
660     name: "New Account?",
661     value: Date.now() - member.user.createdTimestamp < 3 * 24 * 60 * 60 * 1000 ? ":warning: Yes" : "No",
662     inline: true
663     },
664     {
665     name: "Bot?",
666     value: member.user.bot ? "Yes" : "No",
667     inline: true
668     },
669     {
670     name: "Account Created At",
671     value: `<t:${createdAt}:f> (<t:${createdAt}:R>)`
672     },
673     {
674     name: "User Information",
675     value: `Username: ${member.user.username}\nMention: ${member.user.toString()}\nID: ${member.user.id}`,
676     inline: true
677     },
678     {
679     name: "Positions",
680     value:
681     `Among all members: ${members + bots}th\n` +
682     (member.user.bot ? `Among the bots: ${bots}th` : `Among the human members: ${members}th`),
683     inline: true
684     },
685     ...(inviteOrVanity
686     ? [
687     {
688     name: "Invite Information",
689     value:
690     `Invite Link: https://discord.gg/${
691     inviteOrVanity?.vanity?.code ?? inviteOrVanity?.invite?.code
692     }\nUses: ${inviteOrVanity?.vanity?.uses ?? inviteOrVanity?.invite?.uses}` +
693     (!inviteOrVanity.isVanity
694     ? `\nInvited By: ${
695     inviteOrVanity.invite.inviterId
696     ? `<@${inviteOrVanity.invite.inviterId}> (${inviteOrVanity.invite.inviterId})`
697     : "Unknown"
698     }` +
699     (inviteOrVanity.invite.createdAt
700     ? `\nCreated: <t:${Math.round(
701     inviteOrVanity.invite.createdAt.getTime() / 1000
702     )}:R>`
703     : "") +
704     (inviteOrVanity.invite.expiresAt
705     ? `\nExpires: <t:${Math.round(
706     inviteOrVanity.invite.expiresAt.getTime() / 1000
707     )}:R>`
708     : "") +
709     (inviteOrVanity.invite.channelId
710     ? `\nChannel: <#${inviteOrVanity.invite.channelId}> (${inviteOrVanity.invite.channelId})`
711     : "") +
712     (inviteOrVanity.invite.temporary ? `\n\n__This is a temporary invite.__` : "")
713     : "")
714     }
715     ]
716     : [])
717     ],
718     footerText: `Joined • ${
719     member.guild.members.cache.size >= member.guild.memberCount
720     ? member.guild.members.cache.size
721     : member.guild.memberCount
722     } members total`
723     },
724     undefined,
725     "join_leave_channel"
726     );
727     }
728    
729     async logGuildMemberRemove(member: GuildMember) {
730     if (!this.client.configManager.config[member.guild.id]?.logging?.events.member_leave) {
731     return;
732     }
733    
734     const joinedAt = Math.round((member.joinedAt?.getTime() ?? Date.now()) / 1000);
735    
736     await this.sendLogEmbed(
737     member.guild,
738     {
739     title: "Member left",
740     user: member.user,
741     color: 0xf14a60,
742     fields: [
743     {
744     name: "Roles",
745     value:
746     member.roles.cache.size === 1
747     ? "**No roles**"
748     : member.roles.cache
749     .filter(role => role.id !== member.guild.id)
750     .sort((m1, m2) => m2.position - m1.position)
751     .reduce((acc, role) => `${acc} ${roleMention(role.id)}`, "")
752     },
753     {
754     name: "Joined At",
755     value: `<t:${joinedAt}:f> (<t:${joinedAt}:R>)`
756     },
757     {
758     name: "User Information",
759     value: `Username: ${member.user.username}\nMention: ${member.user.toString()}\nID: ${member.user.id}`
760     },
761     {
762     name: "Bot?",
763     value: member.user.bot ? "Yes" : "No",
764     inline: true
765     }
766     ],
767     footerText: `Left • ${
768     member.guild.members.cache.size >= member.guild.memberCount
769     ? member.guild.members.cache.size
770     : member.guild.memberCount
771     } members total`
772     },
773     undefined,
774     "join_leave_channel"
775     );
776     }
777    
778     async logMessageEdit(oldMessage: Message, newMessage: Message) {
779     if (!this.client.configManager.config[newMessage.guildId!]?.logging?.events.message_edit) {
780     return;
781     }
782    
783     const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
784     new ButtonBuilder()
785     .setStyle(ButtonStyle.Link)
786     .setLabel("Go to context")
787     .setURL(`https://discord.com/channels/${newMessage.guildId!}/${newMessage.channelId!}/${newMessage.id}`)
788     );
789    
790     if (newMessage.type === MessageType.Reply)
791     row.addComponents(
792     new ButtonBuilder()
793     .setStyle(ButtonStyle.Link)
794     .setLabel("Go to referenced message")
795     .setURL(
796     `https://discord.com/channels/${newMessage.guildId!}/${newMessage.channelId!}/${
797     newMessage.reference!.messageId
798     }`
799     )
800     );
801    
802     await this.sendLogEmbed(
803     newMessage.guild!,
804     {
805     title: "Message Updated",
806     user: newMessage.author,
807     color: 0x007bff,
808     options: {
809     description: `**-+-+Before**\n${oldMessage.content}\n\n**-+-+After**\n${newMessage.content}`
810     },
811     fields: [
812     {
813     name: "User",
814     value: `${newMessage.author.toString()}\nUsername: ${newMessage.author.username}\nID: ${
815     newMessage.author.id
816     }`
817     },
818     {
819     name: "Channel",
820     value: `${newMessage.channel.toString()}\nName: ${(newMessage.channel as TextChannel).name}\nID: ${
821     newMessage.channel.id
822     }`
823     },
824     {
825     name: "Message",
826     value: `Link: [Click here](${`https://discord.com/channels/${newMessage.guildId!}/${newMessage.channelId!}/${
827     newMessage.id
828     }`})\nID: ${newMessage.id}`
829     }
830     ],
831     footerText: "Updated"
832     },
833     {
834     components: [row]
835     },
836     "message_logging_channel"
837     );
838     }
839    
840     async logMessageDelete(message: Message) {
841     if (!this.client.configManager.config[message.guildId!]?.logging?.events.message_delete) {
842     return;
843     }
844    
845     const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
846     new ButtonBuilder()
847     .setStyle(ButtonStyle.Link)
848     .setLabel("Go to context")
849     .setURL(`https://discord.com/channels/${message.guildId!}/${message.channelId!}/${message.id}`)
850     );
851    
852     if (message.type === MessageType.Reply)
853     row.addComponents(
854     new ButtonBuilder()
855     .setStyle(ButtonStyle.Link)
856     .setLabel("Go to referenced message")
857     .setURL(
858     `https://discord.com/channels/${message.guildId!}/${message.channelId!}/${message.reference!.messageId}`
859     )
860     );
861    
862     await this.sendLogEmbed(
863     message.guild!,
864     {
865     title: "Message Deleted",
866     color: Colors.Red,
867     user: message.author,
868     options: {
869     description: message.content
870     },
871     fields: [
872     {
873     name: "User",
874     value: `${message.author.toString()}\nUsername: ${message.author.username}\nID: ${message.author.id}`
875     },
876     {
877     name: "Channel",
878     value: `${message.channel.toString()}\nName: ${(message.channel as TextChannel).name}\nID: ${
879     message.channel.id
880     }`
881     },
882     {
883     name: "Message",
884     value: `Link: [Click here](${`https://discord.com/channels/${message.guildId!}/${message.channelId!}/${
885     message.id
886     }`})\nID: ${message.id}`
887     }
888     ],
889     footerText: "Deleted"
890     },
891     {
892     components: [row],
893     files: [
894     ...message.attachments
895     .map(
896     a =>
897     ({
898     attachment: a.proxyURL,
899     name: a.name
900     } as AttachmentBuilder)
901     )
902     .values()
903     ]
904     },
905     "message_logging_channel"
906     );
907     }
908    
909     async logRaid({ guild, action }: { guild: Guild; action: string }) {
910     await this.sendLogEmbed(guild, {
911     title: "Possible raid detected",
912     reason: "Too many users joined in a short timeframe.",
913     color: Colors.Red,
914     fields: [
915     {
916     name: "Action",
917     value: action
918     }
919     ],
920     footerText: "Raid detected"
921     });
922     }
923    
924     async logServerLockOrUnlock({
925     guild,
926     action,
927     moderator,
928     countInvalidChannel,
929     countSkipped,
930     countFailed,
931     countSuccess,
932     reason
933     }: {
934     guild: Guild;
935     action: "Locked" | "Unlocked";
936     moderator: User;
937     countInvalidChannel: number;
938     countSkipped: number;
939     countFailed: number;
940     countSuccess: number;
941     reason?: string;
942     }) {
943     const results = `${countInvalidChannel === 0 ? "" : `InvalidChannel: ${countInvalidChannel}\n`}${
944     countSkipped === 0 ? "" : `Skipped: ${countSkipped}\n`
945     }${countSuccess === 0 ? "" : `Success: ${countSuccess}\n`}${countFailed === 0 ? "" : `Failed: ${countFailed}\n`}`;
946    
947     await this.sendLogEmbed(guild, {
948     title: `Server ${action.toLowerCase()}`,
949     reason: reason ?? "The user ran a command to perform this action",
950     moderator,
951     color: 0x007bff,
952     footerText: action,
953     options: {
954     description: `Results:\n\n${results.trim() === "" ? "*Nothing changed*" : results}`
955     }
956     });
957     }
958    
959     async logChannelLockOrUnlock({
960     guild,
961     action,
962     moderator,
963     channel,
964     reason
965     }: {
966     guild: Guild;
967     action: "Locked" | "Unlocked";
968     moderator: User;
969     channel: GuildChannel;
970     reason?: string;
971     }) {
972     await this.sendLogEmbed(guild, {
973     title: `Channel ${action.toLowerCase()}`,
974     reason: reason ?? "The user ran a command to perform this action",
975     moderator,
976     color: 0x007bff,
977     footerText: action,
978     fields: [
979     {
980     name: "Channel",
981     value: `${channel.toString()} (${channel.id})`
982     }
983     ]
984     });
985     }
986    
987     async logUserBan({
988     moderator,
989     user,
990     deleteMessageSeconds,
991     reason,
992     guild,
993     id,
994     duration,
995     includeDeleteMessageSeconds = true
996     }: LogUserBanOptions) {
997     await this.sendLogEmbed(guild, {
998     user,
999     title: "A user was banned",
1000     footerText: (duration ? "Temporarily " : "") + "Banned",
1001     reason: reason ?? null,
1002     moderator,
1003     id,
1004     color: Colors.Red,
1005     fields: [
1006     ...(includeDeleteMessageSeconds
1007     ? [
1008     {
1009     name: "Message Deletion Timeframe",
1010     value: deleteMessageSeconds
1011     ? formatDistanceToNowStrict(new Date(Date.now() - deleteMessageSeconds * 1000))
1012     : "*No timeframe provided*"
1013     }
1014     ]
1015     : []),
1016     ...(duration
1017     ? [
1018     {
1019     name: "Duration",
1020     value: formatDistanceToNowStrict(new Date(Date.now() - duration))
1021     }
1022     ]
1023     : [])
1024     ]
1025     });
1026     }
1027    
1028     async logUserSoftBan({ moderator, user, deleteMessageSeconds, reason, guild, id }: LogUserBanOptions) {
1029     await this.sendLogEmbed(guild, {
1030     user,
1031     title: "A user was softbanned",
1032     footerText: "Softbanned",
1033     reason: reason ?? null,
1034     moderator,
1035     id,
1036     color: Colors.Red,
1037     fields: [
1038     {
1039     name: "Message Deletion Timeframe",
1040     value: deleteMessageSeconds
1041     ? formatDistanceToNowStrict(new Date(Date.now() - deleteMessageSeconds * 1000))
1042     : "*No timeframe provided*"
1043     }
1044     ]
1045     });
1046     }
1047    
1048     async logUserUnban({ moderator, user, reason, guild, id }: LogUserUnbanOptions) {
1049     this.sendLogEmbed(guild, {
1050     user,
1051     title: "A user was unbanned",
1052     footerText: "Unbanned",
1053     reason: reason ?? null,
1054     moderator,
1055     id,
1056     color: Colors.Green
1057     });
1058     }
1059    
1060     async logMemberKick({
1061     moderator,
1062     member,
1063     reason,
1064     guild,
1065     id
1066     }: CommonUserActionOptions & { member: GuildMember; reason?: string }) {
1067     this.sendLogEmbed(guild, {
1068     user: member.user,
1069     title: "A member was kicked",
1070     footerText: "Kicked",
1071     reason: reason ?? null,
1072     moderator,
1073     id,
1074     color: Colors.Orange
1075     });
1076     }
1077    
1078     async logMemberMute({
1079     moderator,
1080     member,
1081     reason,
1082     guild,
1083     id,
1084     duration
1085     }: CommonUserActionOptions & { member: GuildMember; reason?: string; duration?: number }) {
1086     this.sendLogEmbed(guild, {
1087     user: member.user,
1088     title: "A member was muted",
1089     footerText: "Muted",
1090     reason: reason ?? null,
1091     moderator,
1092     id,
1093     color: Colors.DarkGold,
1094     fields: [
1095     {
1096     name: "Duration",
1097     value: duration ? formatDistanceToNowStrict(new Date(Date.now() - duration)) : "*No duration was specified*"
1098     }
1099     ]
1100     });
1101     }
1102    
1103     async logMemberWarning({
1104     moderator,
1105     member,
1106     reason,
1107     guild,
1108     id
1109     }: CommonUserActionOptions & { member: GuildMember; reason?: string }) {
1110     this.sendLogEmbed(guild, {
1111     user: member.user,
1112     title: "A member was warned",
1113     footerText: "Warned",
1114     reason: reason ?? null,
1115     moderator,
1116     id,
1117     color: Colors.Gold
1118     });
1119     }
1120    
1121     async logBulkDeleteMessages({ messages, moderator, user, reason, guild, id, count, channel }: LogMessageBulkDelete) {
1122     const sendJSON = this.client.configManager.config[guild.id]?.logging?.bulk_delete_send_json;
1123    
1124     const message = await this.sendLogEmbed(
1125     guild,
1126     {
1127     user,
1128     title: "Messages deleted in bulk",
1129     footerText: "Deleted",
1130     reason: reason ?? null,
1131     moderator,
1132     id,
1133     color: Colors.DarkRed,
1134     fields: [
1135     {
1136     name: "Deleted Message Count",
1137     value: `${count}`
1138     },
1139     {
1140     name: "Channel",
1141     value: `${channel.toString()} (${channel.id})`
1142     }
1143     ]
1144     },
1145     sendJSON && messages.length > 0
1146     ? {
1147     files: [
1148     {
1149     attachment: this.generateBulkDeleteJSON(messages),
1150     name: "messages.json"
1151     }
1152     ]
1153     }
1154     : undefined
1155     );
1156    
1157     if (messages.length > 0 && sendJSON) {
1158     message
1159     ?.edit({
1160     embeds: [
1161     {
1162     ...(message?.embeds[0]?.data ?? {}),
1163     fields: [
1164     ...(message?.embeds[0].data.fields ?? []),
1165     {
1166     name: "Messages",
1167     value: `[Click here to view the deleted messages](${
1168     process.env.FRONTEND_URL
1169     }/view_deleted_messages?url=${encodeURIComponent(message.attachments.at(0)!.url)})`
1170     }
1171     ]
1172     }
1173     ]
1174     })
1175     .catch(logError);
1176     }
1177     }
1178    
1179     async logMemberUnmute({
1180     moderator,
1181     member,
1182     reason,
1183     guild,
1184     id
1185     }: CommonUserActionOptions & { member: GuildMember; reason?: string }) {
1186     this.sendLogEmbed(guild, {
1187     user: member.user,
1188     title: "A member was unmuted",
1189     footerText: "Unmuted",
1190     reason: reason ?? null,
1191     moderator,
1192     id,
1193     color: Colors.Green
1194     });
1195     }
1196    
1197     async logBlockedWordOrToken({ guild, user, blockType, token, word, message, content }: BlockedTokenOrWordOptions) {
1198     let value: string;
1199     let title: string;
1200    
1201     switch (blockType) {
1202     case "token":
1203     value = `||${escapeMarkdown(token!)}||`;
1204     title = "Posted blocked token(s)";
1205     break;
1206     case "word":
1207     value = `||${escapeMarkdown(word!)}||`;
1208     title = "Posted blocked word(s)";
1209     break;
1210     case "message":
1211     value = `||${escapeMarkdown(message!)}||`;
1212     title = "Posted blocked message(s)";
1213     break;
1214     default:
1215     return;
1216     }
1217    
1218     this.sendLogEmbed(guild, {
1219     user,
1220     title,
1221     footerText: "AutoMod",
1222     color: Colors.Yellow,
1223     fields: [
1224     {
1225     name: blockType[0].toUpperCase() + blockType.substring(1),
1226     value
1227     }
1228     ],
1229     options: {
1230     description: `${content}`
1231     }
1232     });
1233     }
1234    
1235     async logUserMassBan({ users, reason, guild, moderator, deleteMessageSeconds }: LogUserMassBanOptions) {
1236     await this.sendLogEmbed(guild, {
1237     title: "A massban was executed",
1238     footerText: "Banned",
1239     reason: reason ?? null,
1240     moderator,
1241     color: Colors.Red,
1242     fields: [
1243     {
1244     name: "Message Deletion Timeframe",
1245     value: deleteMessageSeconds
1246     ? formatDistanceToNowStrict(new Date(Date.now() - deleteMessageSeconds * 1000))
1247     : "*No timeframe provided*"
1248     }
1249     ],
1250     options: {
1251     description: `The following users were banned:\n\n${users.reduce(
1252     (acc, user) => acc + (acc === "" ? "" : "\n") + "<@" + user + "> (`" + user + "`)",
1253     ""
1254     )}`
1255     }
1256     });
1257     }
1258    
1259     async logMemberMassKick({ users, reason, guild, moderator }: Omit<LogUserMassBanOptions, "deleteMessageSeconds">) {
1260     await this.sendLogEmbed(guild, {
1261     title: "A masskick was executed",
1262     footerText: "Kicked",
1263     reason: reason ?? null,
1264     moderator,
1265     color: Colors.Orange,
1266     options: {
1267     description: `The following users were kicked:\n\n${users.reduce(
1268     (acc, user) => acc + (acc === "" ? "" : "\n") + "<@" + user + "> (`" + user + "`)",
1269     ""
1270     )}`
1271     }
1272     });
1273     }
1274    
1275     generateBulkDeleteJSON(messages: MessageResolvable[]) {
1276     const mappedMessages = ((messages instanceof Collection ? [...messages.values()] : messages) as Message[]).map(m => ({
1277     ...m,
1278     author: m.author,
1279     member: m.member,
1280     authorColor: m.member?.displayColor ?? m.member?.roles.highest.color,
1281     authorRoleIcon: m.member?.roles.highest.iconURL() ?? undefined,
1282     authorRoleName: m.member?.roles.highest.name ?? undefined,
1283     authorAvatarURL: m.author.displayAvatarURL(),
1284     mentions: {
1285     everyone: m.mentions.everyone,
1286     users: m.mentions.users.map(({ id, username }) => ({
1287     id,
1288     username
1289     })),
1290     members:
1291     m.mentions.members?.map(({ nickname, user }) => ({
1292     nickname: nickname ?? user.username,
1293     id: user.id
1294     })) ?? [],
1295     channels: (m.mentions.channels as Collection<string, GuildChannel>).map(({ id, name }) => ({ id, name })),
1296     roles: m.mentions.roles.map(({ id, name }) => ({ id, name }))
1297     }
1298     }));
1299    
1300     return Buffer.from(
1301     JSON.stringify(
1302     {
1303     messages: mappedMessages,
1304     generatedAt: new Date().toISOString(),
1305     channel: (messages.at(0) as Message)!.channel.toJSON({
1306     id: true,
1307     name: true,
1308     type: true
1309     }),
1310     guild: {
1311     id: (messages.at(0) as Message)!.guild!.id,
1312     name: (messages.at(0) as Message)!.guild!.name,
1313     iconURL: (messages.at(0) as Message)!.guild!.iconURL() ?? undefined
1314     },
1315     version: this.client.metadata.data.version
1316     },
1317     null,
1318     4
1319     )
1320     );
1321     }
1322     }
1323    
1324     interface LogMessageBulkDelete extends Omit<CommonUserActionOptions, "id"> {
1325     user?: User;
1326     reason?: string;
1327     count: number;
1328     channel: TextChannel;
1329     id?: string;
1330     messages: MessageResolvable[];
1331     }
1332    
1333     interface LogUserBanOptions extends BanOptions, CommonUserActionOptions {
1334     user: User;
1335     duration?: number;
1336     includeDeleteMessageSeconds?: boolean;
1337     }
1338    
1339     interface LogUserMassBanOptions extends BanOptions, Omit<CommonUserActionOptions, "id"> {
1340     users: string[];
1341     }
1342    
1343     interface LogUserUnbanOptions extends CommonUserActionOptions {
1344     user: User;
1345     reason?: string;
1346     }
1347    
1348     interface CommonUserActionOptions {
1349     moderator: User;
1350     guild: Guild;
1351     id: string;
1352     }
1353    
1354     interface BlockedTokenOrWordOptions {
1355     blockType: "token" | "word" | "message";
1356     token?: string;
1357     word?: string;
1358     message?: string;
1359     guild: Guild;
1360     user: User;
1361     content: string;
1362     }
1363    
1364     export interface CreateLogEmbedOptions {
1365     id?: string;
1366     title?: string;
1367     options?: EmbedData;
1368     moderator?: User;
1369     user?: User;
1370     fields?: APIEmbedField[];
1371     footerText?: string;
1372     reason?: string | null;
1373     timestamp?: Date | false | null;
1374     color?: ColorResolvable;
1375     showUserId?: boolean;
1376     }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26