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 |
} |