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

Contents of /branches/6.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: 47129 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 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