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

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26