1 |
/** |
2 |
* This file is part of SudoBot. |
3 |
* |
4 |
* Copyright (C) 2021-2022 OSN Inc. |
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 { roleMention } from "@discordjs/builders"; |
21 |
import { formatDistanceStrict, formatDistanceToNowStrict, formatDuration, intervalToDuration } from "date-fns"; |
22 |
import { |
23 |
Message, |
24 |
MessageEmbedOptions, |
25 |
MessageEmbed as MessageEmbedDiscord, |
26 |
TextChannel, |
27 |
MessageActionRow, |
28 |
MessageButton, |
29 |
FileOptions, |
30 |
GuildBan, |
31 |
BanOptions, |
32 |
Guild, |
33 |
User, |
34 |
GuildMember, |
35 |
Util, |
36 |
GuildChannel, |
37 |
Role, |
38 |
Collection, |
39 |
} from "discord.js"; |
40 |
import ms from "ms"; |
41 |
import BaseMessageEmbed from "../client/MessageEmbed"; |
42 |
import { IGuildInfo } from "../models/GuildInfo"; |
43 |
import Punishment, { IPunishment } from "../models/Punishment"; |
44 |
import PunishmentType from "../types/PunishmentType"; |
45 |
import Service from "../utils/structures/Service"; |
46 |
import { timeSince } from "../utils/util"; |
47 |
|
48 |
class MessageEmbed extends BaseMessageEmbed { |
49 |
constructor(options?: MessageEmbedDiscord | MessageEmbedOptions) { |
50 |
super(options); |
51 |
this.setTimestamp(); |
52 |
} |
53 |
} |
54 |
|
55 |
export default class Logger extends Service { |
56 |
loggingChannel(id: string) { |
57 |
return this.client.guilds.cache.get(id)?.channels.cache.get(this.client.config.props[id].logging_channel) as TextChannel | null; |
58 |
} |
59 |
|
60 |
loggingChannelBoosts(id: string) { |
61 |
return this.client.guilds.cache |
62 |
.get(id) |
63 |
?.channels.cache.get( |
64 |
this.client.config.props[id].logging_channel_boosts ?? this.client.config.props[id].logging_channel |
65 |
) as TextChannel | null; |
66 |
} |
67 |
|
68 |
loggingChannelJoinLeave(id: string) { |
69 |
return this.client.guilds.cache.get(id)?.channels.cache.get(this.client.config.props[id].logging_channel_join_leave) as TextChannel | null; |
70 |
} |
71 |
|
72 |
async onRoleCreate(role: Role) { |
73 |
this.loggingChannel(role.guild.id)?.send({ |
74 |
embeds: [ |
75 |
new MessageEmbed({ |
76 |
title: "Role created", |
77 |
fields: [ |
78 |
{ |
79 |
name: "Role", |
80 |
value: `Name: ${role.name}\nID: ${role.id}\nMention: ${role.toString()}` |
81 |
} |
82 |
], |
83 |
footer: { |
84 |
text: "Created" |
85 |
}, |
86 |
color: 0x007bff |
87 |
}) |
88 |
.setTimestamp() |
89 |
], |
90 |
allowedMentions: { |
91 |
roles: [] |
92 |
} |
93 |
}); |
94 |
} |
95 |
|
96 |
|
97 |
async onRoleDelete(role: Role) { |
98 |
this.loggingChannel(role.guild.id)?.send({ |
99 |
embeds: [ |
100 |
new MessageEmbed({ |
101 |
title: "Role deleted", |
102 |
fields: [ |
103 |
{ |
104 |
name: "Role", |
105 |
value: `Name: ${role.name}\nID: ${role.id}\nMention: ${role.toString()}` |
106 |
} |
107 |
], |
108 |
footer: { |
109 |
text: "Deleted" |
110 |
}, |
111 |
color: 0xf14a60 |
112 |
}) |
113 |
.setTimestamp() |
114 |
], |
115 |
allowedMentions: { |
116 |
roles: [] |
117 |
} |
118 |
}); |
119 |
} |
120 |
|
121 |
|
122 |
async onRoleUpdate(oldRole: Role, newRole: Role) { |
123 |
this.loggingChannel(newRole.guild.id)?.send({ |
124 |
embeds: [ |
125 |
new MessageEmbed({ |
126 |
title: "Role updated", |
127 |
fields: [ |
128 |
...(newRole.name !== oldRole.name ? [ |
129 |
{ |
130 |
name: "Name", |
131 |
value: `New Name: ${newRole.name}\nOld Name: ${oldRole.name}` |
132 |
} |
133 |
] : [ |
134 |
{ |
135 |
name: "Name", |
136 |
value: `${newRole.name}` |
137 |
} |
138 |
]), |
139 |
...(newRole.color !== oldRole.color ? [ |
140 |
{ |
141 |
name: "Color", |
142 |
value: `New Color: ${newRole.hexColor}\nOld Color: ${oldRole.hexColor}` |
143 |
} |
144 |
] : []), |
145 |
...(!newRole.permissions.equals(oldRole.permissions) ? [ |
146 |
{ |
147 |
name: "New Permissions", |
148 |
value: `\`${newRole.permissions.toArray().join('`, `')}\`` |
149 |
} |
150 |
] : []), |
151 |
...(oldRole.icon !== newRole.icon ? [ |
152 |
{ |
153 |
name: "Icon", |
154 |
value: `New Icon: ${newRole.iconURL()}\nOld Icon: ${oldRole.iconURL()}` |
155 |
} |
156 |
] : []), |
157 |
{ |
158 |
name: "ID", |
159 |
value: `${newRole.id}` |
160 |
} |
161 |
], |
162 |
footer: { |
163 |
text: "Updated" |
164 |
}, |
165 |
}) |
166 |
.setTimestamp() |
167 |
.setColor("GREEN") |
168 |
], |
169 |
allowedMentions: { |
170 |
roles: [] |
171 |
} |
172 |
}); |
173 |
} |
174 |
|
175 |
async onChannelCreate(channel: GuildChannel) { |
176 |
this.loggingChannel(channel.guild.id)?.send({ |
177 |
embeds: [ |
178 |
new MessageEmbed({ |
179 |
title: `Channel${channel.type === "GUILD_CATEGORY" ? " Category" : ""} Created`, |
180 |
color: 0x07bff, |
181 |
fields: [ |
182 |
{ |
183 |
name: "Channel", |
184 |
value: `#${channel.name} (${channel.toString()}, ${channel.id})` |
185 |
}, |
186 |
{ |
187 |
name: "Type", |
188 |
value: channel.type.replace("GUILD_", '') |
189 |
} |
190 |
], |
191 |
footer: { |
192 |
text: 'Created' |
193 |
} |
194 |
}).setTimestamp() |
195 |
] |
196 |
}).catch(console.error); |
197 |
} |
198 |
|
199 |
async onChannelDelete(channel: GuildChannel) { |
200 |
this.loggingChannel(channel.guild.id)?.send({ |
201 |
embeds: [ |
202 |
new MessageEmbed({ |
203 |
title: `Channel${channel.type === "GUILD_CATEGORY" ? " Category" : ""} Deleted`, |
204 |
color: 0xf14a60, |
205 |
fields: [ |
206 |
{ |
207 |
name: "Channel", |
208 |
value: `#${channel.name} (${channel.id})` |
209 |
}, |
210 |
{ |
211 |
name: "Type", |
212 |
value: channel.type.replace("GUILD_", '') |
213 |
} |
214 |
], |
215 |
footer: { |
216 |
text: 'Deleted' |
217 |
} |
218 |
}).setTimestamp() |
219 |
] |
220 |
}).catch(console.error); |
221 |
} |
222 |
|
223 |
async onMemberRoleAdd(member: GuildMember, roles: Role[]) { |
224 |
this.loggingChannel(member.guild.id)?.send({ |
225 |
embeds: [ |
226 |
new MessageEmbed({ |
227 |
title: `Roles added to member`, |
228 |
color: 0xf14a60, |
229 |
fields: [ |
230 |
{ |
231 |
name: "Member", |
232 |
value: `${member.user.toString()} (${member.user.id})` |
233 |
}, |
234 |
{ |
235 |
name: "Roles", |
236 |
value: `${roles.map(r => r.toString()).join(', ')}` |
237 |
} |
238 |
], |
239 |
footer: { |
240 |
text: 'Roles Added' |
241 |
} |
242 |
}).setTimestamp() |
243 |
], |
244 |
allowedMentions: { |
245 |
roles: [], |
246 |
users: [] |
247 |
} |
248 |
}).catch(console.error); |
249 |
} |
250 |
|
251 |
async onMemberRoleUpdate(newMember: GuildMember, oldMember: GuildMember) { |
252 |
this.loggingChannel(newMember.guild.id)?.send({ |
253 |
embeds: [ |
254 |
new MessageEmbed({ |
255 |
author: { |
256 |
name: newMember.user.username, |
257 |
icon_url: newMember.displayAvatarURL() |
258 |
}, |
259 |
title: `Roles modified`, |
260 |
color: 0xf14a60, |
261 |
fields: [ |
262 |
{ |
263 |
name: "Member", |
264 |
value: `${newMember.user.toString()} (${newMember.user.id})` |
265 |
}, |
266 |
{ |
267 |
name: "Old Roles", |
268 |
value: `${oldMember.roles.cache.map(r => r.toString()).join(', ')}` |
269 |
}, |
270 |
{ |
271 |
name: "New Roles", |
272 |
value: `${newMember.roles.cache.map(r => r.toString()).join(', ')}` |
273 |
} |
274 |
], |
275 |
footer: { |
276 |
text: 'Roles Updated' |
277 |
} |
278 |
}).setTimestamp() |
279 |
], |
280 |
allowedMentions: { |
281 |
roles: [], |
282 |
users: [] |
283 |
} |
284 |
}).catch(console.error); |
285 |
} |
286 |
|
287 |
async onMemberRoleRemove(member: GuildMember, roles: Role[]) { |
288 |
this.loggingChannel(member.guild.id)?.send({ |
289 |
embeds: [ |
290 |
new MessageEmbed({ |
291 |
title: `Roles removed from member`, |
292 |
color: 0xf14a60, |
293 |
fields: [ |
294 |
{ |
295 |
name: "Member", |
296 |
value: `${member.user.toString()} (${member.user.id})` |
297 |
}, |
298 |
{ |
299 |
name: "Roles", |
300 |
value: `${roles.map(r => r.toString()).join(', ')}` |
301 |
} |
302 |
], |
303 |
footer: { |
304 |
text: 'Roles Removed' |
305 |
} |
306 |
}).setTimestamp() |
307 |
], |
308 |
allowedMentions: { |
309 |
roles: [], |
310 |
users: [] |
311 |
} |
312 |
}).catch(console.error); |
313 |
} |
314 |
|
315 |
|
316 |
async onBulkDelete(messages: Collection<string, Message>) { |
317 |
this.loggingChannel(messages.at(0)?.guildId!)?.send({ |
318 |
embeds: [ |
319 |
new MessageEmbed({ |
320 |
title: `Messages deleted in bulk`, |
321 |
color: 0xf14a60, |
322 |
fields: [ |
323 |
{ |
324 |
name: "Channel", |
325 |
value: `${messages.at(0)?.channel.toString()} (${messages.at(0)?.channelId})` |
326 |
}, |
327 |
{ |
328 |
name: "Count", |
329 |
value: `${messages.size}` |
330 |
} |
331 |
], |
332 |
footer: { |
333 |
text: 'Deleted' |
334 |
} |
335 |
}).setTimestamp() |
336 |
] |
337 |
}).catch(console.error); |
338 |
} |
339 |
|
340 |
async onAIModMessageDelete(message: Message, config: any, response: any, meta: any) { |
341 |
const toxicScore = response.data.attributeScores.TOXICITY.summaryScore.value * 100; |
342 |
const severeToxicScore = response.data.attributeScores.SEVERE_TOXICITY.summaryScore.value * 100; |
343 |
const threatScore = response.data.attributeScores.THREAT.summaryScore.value * 100; |
344 |
|
345 |
let conf = meta.toxic ? `Toxicity score is ${toxicScore}%, which is higher than or equal to the given maximum value in the configuration (${config.toxicity * 100}%)` : ( |
346 |
meta.severeToxic ? `Severe Toxicity score is ${severeToxicScore}%, which is higher than or equal to the given maximum value in the configuration (${config.severe_toxicity * 100}%)` : ( |
347 |
meta.threat ? `Threat score is ${threatScore}%, which is higher than or equal to the given maximum value in the configuration (${config.threat * 100}%)` : `Unknown` |
348 |
) |
349 |
); |
350 |
|
351 |
await this.loggingChannel(message.guildId!)?.send({ |
352 |
embeds: [ |
353 |
new MessageEmbed({ |
354 |
title: "AI Moderator has deleted this message", |
355 |
author: { |
356 |
name: message.author.tag, |
357 |
iconURL: message.author.displayAvatarURL(), |
358 |
}, |
359 |
color: 0xf14a60, |
360 |
description: message.content, |
361 |
fields: [ |
362 |
{ |
363 |
name: "User ID", |
364 |
value: message.author.id, |
365 |
}, |
366 |
{ |
367 |
name: "Message Analysis Results", |
368 |
value: `Toxicity: ${toxicScore}%\nSevere Toxicity: ${severeToxicScore}%\nThreat: ${threatScore}%`, |
369 |
}, |
370 |
{ |
371 |
name: "Reason", |
372 |
value: `Deleted the message because of the following configuration:\n\`\`\`${conf}\`\`\`` |
373 |
} |
374 |
], |
375 |
footer: { |
376 |
text: 'Deleted' |
377 |
} |
378 |
}) |
379 |
.setTimestamp(), |
380 |
], |
381 |
}); |
382 |
} |
383 |
|
384 |
async onServerBoost(member: GuildMember, level: number) { |
385 |
const distance = formatDistanceToNowStrict(member.premiumSince!); |
386 |
|
387 |
await this.loggingChannelBoosts(member.guild.id)?.send({ |
388 |
embeds: [ |
389 |
new MessageEmbed({ |
390 |
title: "Server Boosted", |
391 |
author: { |
392 |
name: member.user.tag, |
393 |
iconURL: member.user.displayAvatarURL(), |
394 |
}, |
395 |
description: `${member.user.toString()} has boosted the server!${level > 0 ? ` The server has reached level **${level}**!` : ""}`, |
396 |
fields: [ |
397 |
{ |
398 |
name: "Boosting Since", |
399 |
value: `${member.premiumSince!.toUTCString()} (${distance.startsWith("0 second") ? "just now" : distance})`, |
400 |
}, |
401 |
], |
402 |
footer: { |
403 |
text: "Boosted", |
404 |
}, |
405 |
}).setTimestamp(), |
406 |
], |
407 |
}); |
408 |
} |
409 |
|
410 |
async onServerUnboost(oldMember: GuildMember, newMember: GuildMember) { |
411 |
await this.loggingChannelBoosts(oldMember.guild.id)?.send({ |
412 |
embeds: [ |
413 |
new MessageEmbed({ |
414 |
title: "Server Unboosted", |
415 |
author: { |
416 |
name: newMember.user.tag, |
417 |
iconURL: newMember.user.displayAvatarURL(), |
418 |
}, |
419 |
description: `${newMember.user.toString()} has unboosted the server.`, |
420 |
fields: [ |
421 |
{ |
422 |
name: "Boosting Since", |
423 |
value: `${oldMember.premiumSince!.toUTCString()} (${formatDistanceToNowStrict(oldMember.premiumSince!)})`, |
424 |
}, |
425 |
], |
426 |
footer: { |
427 |
text: "Unboosted", |
428 |
}, |
429 |
}) |
430 |
.setColor("PURPLE") |
431 |
.setTimestamp(), |
432 |
], |
433 |
}); |
434 |
} |
435 |
|
436 |
async onNicknameChange(oldMember: GuildMember, newMember: GuildMember) { |
437 |
await this.loggingChannel(oldMember.guild.id)?.send({ |
438 |
embeds: [ |
439 |
new MessageEmbed({ |
440 |
title: "Nickname Updated", |
441 |
author: { |
442 |
name: oldMember.user.tag, |
443 |
iconURL: oldMember.user.displayAvatarURL(), |
444 |
}, |
445 |
color: 0xf14a60, |
446 |
fields: [ |
447 |
{ |
448 |
name: "Old Nickname", |
449 |
value: oldMember.nickname ? Util.escapeMarkdown(oldMember.nickname) : "*No nickname was set*", |
450 |
}, |
451 |
{ |
452 |
name: "New Nickname", |
453 |
value: newMember.nickname ? Util.escapeMarkdown(newMember.nickname) : "*Nickname was removed*", |
454 |
}, |
455 |
{ |
456 |
name: "User ID", |
457 |
value: newMember.user.id, |
458 |
}, |
459 |
], |
460 |
footer: { |
461 |
text: "Updated", |
462 |
}, |
463 |
}).setTimestamp(), |
464 |
], |
465 |
}); |
466 |
} |
467 |
|
468 |
async onMessageUpdate(oldMessage: Message, newMessage: Message) { |
469 |
const row = new MessageActionRow().addComponents(new MessageButton().setLabel("Go to context").setStyle("LINK").setURL(newMessage.url)); |
470 |
|
471 |
if (newMessage.type === "REPLY") { |
472 |
row.addComponents( |
473 |
new MessageButton() |
474 |
.setLabel("Go to referenced message") |
475 |
.setStyle("LINK") |
476 |
.setURL(`https://discord.com/channels/${newMessage.guildId!}/${newMessage.channelId!}/${newMessage.reference!.messageId}`) |
477 |
); |
478 |
} |
479 |
|
480 |
await this.loggingChannel(newMessage.guild!.id)?.send({ |
481 |
embeds: [ |
482 |
new MessageEmbed({ |
483 |
title: "Message Updated", |
484 |
author: { |
485 |
name: oldMessage.author.tag, |
486 |
iconURL: oldMessage.author.displayAvatarURL(), |
487 |
}, |
488 |
description: "**-+-+Before**\n" + oldMessage.content + "\n\n**-+-+After**\n" + newMessage.content, |
489 |
fields: [ |
490 |
{ |
491 |
name: "Message ID", |
492 |
value: newMessage.id, |
493 |
}, |
494 |
{ |
495 |
name: "User ID", |
496 |
value: oldMessage.author.id, |
497 |
}, |
498 |
{ |
499 |
name: "Channel", |
500 |
value: `${oldMessage.channel} (${oldMessage.channelId})`, |
501 |
}, |
502 |
], |
503 |
footer: { |
504 |
text: `Updated • ${newMessage.id}`, |
505 |
}, |
506 |
}), |
507 |
], |
508 |
components: [row], |
509 |
}); |
510 |
} |
511 |
|
512 |
async onMessageDelete(message: Message) { |
513 |
const embed = new MessageEmbed({ |
514 |
title: "Message Deleted", |
515 |
color: 0xf14a60, |
516 |
author: { |
517 |
name: message.author.tag, |
518 |
iconURL: message.author.displayAvatarURL(), |
519 |
}, |
520 |
description: message.content, |
521 |
fields: [ |
522 |
{ |
523 |
name: "Message ID", |
524 |
value: message.id, |
525 |
}, |
526 |
{ |
527 |
name: "User ID", |
528 |
value: message.author.id, |
529 |
}, |
530 |
{ |
531 |
name: "Channel", |
532 |
value: `${message.channel} (${message.channelId})`, |
533 |
}, |
534 |
], |
535 |
footer: { |
536 |
text: `Deleted • ${message.id}`, |
537 |
}, |
538 |
}); |
539 |
|
540 |
const files: FileOptions[] = []; |
541 |
|
542 |
if (message.attachments.size > 0) { |
543 |
let str = ""; |
544 |
|
545 |
message.attachments.forEach((a) => { |
546 |
str += `${a.name}\n`; |
547 |
files.push({ |
548 |
name: a.name!, |
549 |
attachment: a.proxyURL, |
550 |
}); |
551 |
}); |
552 |
|
553 |
embed.addFields({ |
554 |
name: "Attachments (top)", |
555 |
value: str, |
556 |
}); |
557 |
} |
558 |
|
559 |
const row = new MessageActionRow().addComponents(new MessageButton().setLabel("Go to context").setStyle("LINK").setURL(message.url)); |
560 |
|
561 |
if (message.type === "REPLY") { |
562 |
row.addComponents( |
563 |
new MessageButton() |
564 |
.setLabel("Go to referenced message") |
565 |
.setStyle("LINK") |
566 |
.setURL(`https://discord.com/channels/${message.guildId!}/${message.channelId!}/${message.reference!.messageId}`) |
567 |
); |
568 |
} |
569 |
|
570 |
await this.loggingChannel(message.guild!.id)?.send({ |
571 |
embeds: [embed], |
572 |
components: [row], |
573 |
files, |
574 |
}); |
575 |
} |
576 |
|
577 |
async onGuildBanAdd(ban: GuildBan, _executor?: User, id?: string | number) { |
578 |
const auditLog = ( |
579 |
await ban.guild.fetchAuditLogs({ |
580 |
limit: 1, |
581 |
type: "MEMBER_BAN_ADD", |
582 |
}) |
583 |
).entries.first(); |
584 |
|
585 |
const executor = _executor ?? auditLog?.executor; |
586 |
|
587 |
if (executor?.id === this.client.user!.id) { |
588 |
console.log("Action taken by bot"); |
589 |
return; |
590 |
} |
591 |
|
592 |
const guildBan = await ban.guild.bans.fetch(ban.user.id); |
593 |
const reason = ban.reason ?? guildBan.reason ?? "*No reason provided*"; |
594 |
|
595 |
await this.loggingChannel(ban.guild.id)?.send({ |
596 |
embeds: [ |
597 |
new MessageEmbed() |
598 |
.setColor("#f14a60") |
599 |
.setTitle("A user was banned") |
600 |
.setAuthor({ |
601 |
name: guildBan.user.tag, |
602 |
iconURL: guildBan.user.displayAvatarURL(), |
603 |
}) |
604 |
.addField("Reason", reason) |
605 |
.addField("User ID", guildBan.user.id) |
606 |
.addFields( |
607 |
{ |
608 |
name: "Banned by", |
609 |
value: executor ? `${executor.tag} (${executor.id})` : "Unknown", |
610 |
}, |
611 |
{ |
612 |
name: "Infraction ID", |
613 |
value: id?.toString() ?? "*Unavailable*", |
614 |
} |
615 |
) |
616 |
.setFooter({ |
617 |
text: "Banned", |
618 |
}), |
619 |
], |
620 |
}); |
621 |
} |
622 |
|
623 |
async onGuildBanRemove(ban: GuildBan, _executor?: User) { |
624 |
const auditLog = ( |
625 |
await ban.guild.fetchAuditLogs({ |
626 |
limit: 1, |
627 |
type: "MEMBER_BAN_REMOVE", |
628 |
}) |
629 |
).entries.first(); |
630 |
|
631 |
const executor = _executor ?? auditLog?.executor; |
632 |
|
633 |
await this.loggingChannel(ban.guild.id)?.send({ |
634 |
embeds: [ |
635 |
new MessageEmbed() |
636 |
.setColor("#f14a60") |
637 |
.setTitle("A user was unbanned") |
638 |
.setAuthor({ |
639 |
name: ban.user.tag, |
640 |
iconURL: ban.user.displayAvatarURL(), |
641 |
}) |
642 |
.addField("User ID", ban.user.id) |
643 |
.addFields({ |
644 |
name: "Unbanned By", |
645 |
value: executor ? `${executor.tag} (${executor.id})` : "Unknown", |
646 |
}) |
647 |
.setFooter({ |
648 |
text: "Unbanned", |
649 |
}) |
650 |
.setTimestamp(), |
651 |
], |
652 |
}); |
653 |
} |
654 |
|
655 |
async onSoftban(banOptions: BanOptions, guild: Guild, user: User, model: IPunishment) { |
656 |
let r = banOptions.reason ?? "*No reason provided*"; |
657 |
|
658 |
await this.loggingChannel(guild.id)?.send({ |
659 |
embeds: [ |
660 |
new MessageEmbed() |
661 |
.setColor("#f14a60") |
662 |
.setTitle("A user was softbanned") |
663 |
.setAuthor({ |
664 |
name: user.tag, |
665 |
iconURL: user.displayAvatarURL(), |
666 |
}) |
667 |
.addField("Reason", r) |
668 |
.addField("Softbanned by", `${model.mod_tag} (${model.mod_id})`) |
669 |
.addField("User ID", user.id) |
670 |
.addFields({ |
671 |
name: "Case ID", |
672 |
value: model.numericId + "", |
673 |
}) |
674 |
.setFooter({ |
675 |
text: "Softbanned", |
676 |
}) |
677 |
.setTimestamp(), |
678 |
], |
679 |
}); |
680 |
} |
681 |
|
682 |
async onTempBan(banOptions: BanOptions, guild: Guild, user: User, model: IPunishment) { |
683 |
let r = banOptions.reason ?? "*No reason provided*"; |
684 |
|
685 |
await this.loggingChannel(guild.id)?.send({ |
686 |
embeds: [ |
687 |
new MessageEmbed() |
688 |
.setColor("#f14a60") |
689 |
.setTitle("A user was temporarily banned") |
690 |
.setAuthor({ |
691 |
name: user.tag, |
692 |
iconURL: user.displayAvatarURL(), |
693 |
}) |
694 |
.addField("Reason", r) |
695 |
.addField("Banned by", `${model.mod_tag} (${model.mod_id})`) |
696 |
.addField("User ID", user.id) |
697 |
.addField("Duration", ms((model.meta as any).time)) |
698 |
.addFields({ |
699 |
name: "Case ID", |
700 |
value: model.numericId + "", |
701 |
}) |
702 |
.setFooter({ |
703 |
text: "Temporarily banned", |
704 |
}) |
705 |
.setTimestamp(), |
706 |
], |
707 |
}); |
708 |
} |
709 |
|
710 |
async onGuildMemberAdd(member: GuildMember, info?: IGuildInfo) { |
711 |
let members = 0, |
712 |
bots = 0; |
713 |
|
714 |
for (const m of member.guild!.members.cache.values()) { |
715 |
if (m.user.bot) bots++; |
716 |
else members++; |
717 |
} |
718 |
|
719 |
const invite = member.user.bot ? null : await this.client.inviteTracker.getInviteInfo(member); |
720 |
|
721 |
await this.loggingChannelJoinLeave(member.guild.id)?.send({ |
722 |
embeds: [ |
723 |
new MessageEmbed() |
724 |
.setColor("#007bff") |
725 |
.setTitle("New member joined") |
726 |
.setAuthor({ |
727 |
name: member.user.tag, |
728 |
iconURL: member.user.displayAvatarURL(), |
729 |
}) |
730 |
.setDescription(`<@${member.user.id}> just joined the server! Their position is ${members + bots}th.`) |
731 |
.addField("Account Created", `${member.user.createdAt.toLocaleString()} (${timeSince(member.user.createdAt.getTime())})`) |
732 |
.addField( |
733 |
"New Account?", |
734 |
new Date().getTime() - member.user.createdAt.getTime() <= 3 * 24 * 60 * 60 * 1000 ? ":warning: Yes :warning:" : "No" |
735 |
) |
736 |
.addField("Bot?", member.user.bot === true ? "Yes" : "No") |
737 |
.addField("User ID", member.user.id) |
738 |
.addFields( |
739 |
{ |
740 |
name: "Total Members Joined", |
741 |
value: info?.totalMembersJoined?.toString() ?? "*Information unavailable*", |
742 |
}, |
743 |
{ |
744 |
name: "Positions", |
745 |
value: `Among All members: ${members + bots}th\n${ |
746 |
member.user.bot ? `Among bots: ${bots}th` : `Among human members: ${members}th` |
747 |
}`, |
748 |
}, |
749 |
{ |
750 |
name: "Invite Information", |
751 |
value: invite |
752 |
? `Inviter: ${invite.inviter ?? "*Unavailable*"}\nInvite URL: [https://discord.gg/${ |
753 |
invite.code |
754 |
}](https://discord.gg/${invite.code})` |
755 |
: "*Unavailable*", |
756 |
} |
757 |
) |
758 |
.setFooter({ |
759 |
text: `Joined • ${members + bots} members total`, |
760 |
}) |
761 |
.setTimestamp(), |
762 |
], |
763 |
}); |
764 |
} |
765 |
|
766 |
async onGuildMemberRemove(member: GuildMember) { |
767 |
const roles = member.roles.cache.filter((role) => role.id !== member.guild.id).reduce((acc, val) => ` ${acc} ${roleMention(val.id)}`, ""); |
768 |
|
769 |
await this.loggingChannelJoinLeave(member.guild.id)?.send({ |
770 |
embeds: [ |
771 |
new MessageEmbed() |
772 |
.setColor("#f14a60") |
773 |
.setTitle("Member left") |
774 |
.setAuthor({ |
775 |
name: member.user.tag, |
776 |
iconURL: member.user.displayAvatarURL(), |
777 |
}) |
778 |
.setDescription(`**Roles**\n${roles}`) |
779 |
.addField("Joined at", `${member.joinedAt!.toLocaleString()} (${timeSince(member.joinedAt!.getTime())})`) |
780 |
.addField("User ID", member.user.id) |
781 |
.addField("Bot?", member.user.bot === true ? "Yes" : "No") |
782 |
.setFooter({ |
783 |
text: `Left • ${member.guild.memberCount} members total`, |
784 |
}) |
785 |
.setTimestamp(), |
786 |
], |
787 |
}); |
788 |
} |
789 |
|
790 |
async onMemberShot(member: GuildMember, moderator: User, reason?: string, id?: string) { |
791 |
await this.loggingChannel(member.guild.id)?.send({ |
792 |
embeds: [ |
793 |
new MessageEmbed() |
794 |
.setColor("#007bff") |
795 |
.setTitle("Member got shot") |
796 |
.setAuthor({ |
797 |
name: member.user.tag, |
798 |
iconURL: member.user.displayAvatarURL(), |
799 |
}) |
800 |
.addField("Reason", reason ?? "*No reason provided*") |
801 |
.addField("Doctor 💉", moderator.tag) |
802 |
.addField("User ID", member.user.id) |
803 |
.addFields({ |
804 |
name: "Infraction ID", |
805 |
value: id ?? "*Unavailable*", |
806 |
}) |
807 |
.setFooter({ |
808 |
text: "Shot delivered", |
809 |
}) |
810 |
.setTimestamp(), |
811 |
], |
812 |
}); |
813 |
} |
814 |
|
815 |
async onMemberKick(member: GuildMember, reason?: string, executor?: User, id?: string) { |
816 |
await this.loggingChannel(member.guild.id)?.send({ |
817 |
embeds: [ |
818 |
new MessageEmbed({ |
819 |
author: { |
820 |
name: member.user.tag, |
821 |
iconURL: member.user.displayAvatarURL(), |
822 |
}, |
823 |
title: "Member Kicked", |
824 |
description: "This user has left the server, probably due to a kick.", |
825 |
fields: [ |
826 |
{ |
827 |
name: "Kicked by", |
828 |
value: executor?.tag ?? "Unknown", |
829 |
}, |
830 |
{ |
831 |
name: "Reason", |
832 |
value: reason ?? "*No reason provided*", |
833 |
}, |
834 |
{ |
835 |
name: "Infraction ID", |
836 |
value: id ?? "*Unavailable*", |
837 |
}, |
838 |
], |
839 |
footer: { |
840 |
text: "Kicked", |
841 |
}, |
842 |
}).setTimestamp(), |
843 |
], |
844 |
}); |
845 |
} |
846 |
|
847 |
async onMemberMute(member: GuildMember, duration?: number, reason?: string, executor?: User, hard = false, id?: string) { |
848 |
await this.loggingChannel(member.guild.id)?.send({ |
849 |
embeds: [ |
850 |
new MessageEmbed() |
851 |
.setColor("#f14a60") |
852 |
.setTitle("Member muted") |
853 |
.setAuthor({ |
854 |
name: member.user.tag, |
855 |
iconURL: member.user.displayAvatarURL(), |
856 |
}) |
857 |
.addField("Reason", reason ?? "*No reason provided*") |
858 |
.addField("Muted by", executor?.tag ?? "Unknown") |
859 |
.addField( |
860 |
"Duration Until", |
861 |
duration |
862 |
? `${new Date(Date.now() + duration).toLocaleString()} (${formatDuration( |
863 |
intervalToDuration({ start: 0, end: duration }) |
864 |
)})` |
865 |
: "*No duration set*" |
866 |
) |
867 |
.addField("User ID", member.user.id) |
868 |
.addField("Hardmute", hard ? "Yes" : "No") |
869 |
.addFields({ |
870 |
name: "Infraction ID", |
871 |
value: id ?? "*Unavailable*", |
872 |
}) |
873 |
.setFooter({ |
874 |
text: "Muted", |
875 |
}) |
876 |
.setTimestamp(), |
877 |
], |
878 |
}); |
879 |
} |
880 |
|
881 |
async onMemberUnmute(member: GuildMember, executor?: User) { |
882 |
await this.loggingChannel(member.guild.id)?.send({ |
883 |
embeds: [ |
884 |
new MessageEmbed() |
885 |
.setColor("#007bff") |
886 |
.setTitle("Member unmuted") |
887 |
.setAuthor({ |
888 |
name: member.user.tag, |
889 |
iconURL: member.user.displayAvatarURL(), |
890 |
}) |
891 |
.addField("Unmuted by", executor?.tag ?? "Unknown") |
892 |
.addField("User ID", member.user.id) |
893 |
.setFooter({ |
894 |
text: "Unmuted", |
895 |
}) |
896 |
.setTimestamp(), |
897 |
], |
898 |
}); |
899 |
} |
900 |
|
901 |
async onMemberWarn(user: User, guildID: string, id: string, reason?: string, moderator?: User) { |
902 |
await this.loggingChannel(guildID)?.send({ |
903 |
embeds: [ |
904 |
new MessageEmbed() |
905 |
.setColor("GOLD") |
906 |
.setTitle("Member warned") |
907 |
.setAuthor({ |
908 |
name: user.tag, |
909 |
iconURL: user.displayAvatarURL(), |
910 |
}) |
911 |
.addField("Reason", reason ?? "*No reason provided*") |
912 |
.addField("Warned by", moderator?.tag ?? "*No reason provided*") |
913 |
.addField("User ID", user.id) |
914 |
.addField("Infraction ID", id + "") |
915 |
.setFooter({ |
916 |
text: "Warned", |
917 |
}) |
918 |
.setTimestamp(), |
919 |
], |
920 |
}); |
921 |
} |
922 |
|
923 |
async onMemberWarningDelete(member: GuildMember, id: string, reason?: string, moderator?: User) { |
924 |
await this.loggingChannel(member.guild.id)?.send({ |
925 |
embeds: [ |
926 |
new MessageEmbed() |
927 |
.setColor("GOLD") |
928 |
.setTitle("Warning deleted") |
929 |
.setAuthor({ |
930 |
name: member.user.tag, |
931 |
iconURL: member.user.displayAvatarURL(), |
932 |
}) |
933 |
.addField("Warned by", moderator?.tag ?? "Unknown") |
934 |
.addField("Infraction ID", id + "") |
935 |
.addField("User ID", member.user.id) |
936 |
.setFooter({ |
937 |
text: "Warning Deleted", |
938 |
}) |
939 |
.setTimestamp(), |
940 |
], |
941 |
}); |
942 |
} |
943 |
|
944 |
async onMemberUpdate(oldMember: GuildMember, newMember: GuildMember) { |
945 |
if (oldMember.isCommunicationDisabled() && !newMember.isCommunicationDisabled()) { |
946 |
setTimeout(async () => { |
947 |
const auditLog = await newMember.guild.fetchAuditLogs({ |
948 |
type: "MEMBER_UPDATE", |
949 |
limit: 1, |
950 |
}); |
951 |
|
952 |
const data = auditLog?.entries.first(); |
953 |
|
954 |
console.log(data); |
955 |
|
956 |
Punishment.create({ |
957 |
createdAt: new Date(), |
958 |
guild_id: newMember.guild.id, |
959 |
mod_id: data?.executor?.id, |
960 |
mod_tag: data?.executor?.tag, |
961 |
user_id: newMember.user.id, |
962 |
type: PunishmentType.TIMEOUT_REMOVE, |
963 |
}).catch(console.error); |
964 |
|
965 |
await this.loggingChannel(newMember.guild.id)?.send({ |
966 |
embeds: [ |
967 |
new MessageEmbed() |
968 |
.setColor("GOLD") |
969 |
.setTitle("Member Timeout Removed") |
970 |
.setAuthor({ |
971 |
name: newMember.user.tag, |
972 |
iconURL: newMember.user.displayAvatarURL(), |
973 |
}) |
974 |
.addField( |
975 |
"Removed by", |
976 |
data?.target?.id === newMember.user.id && data?.executor?.tag |
977 |
? `${data?.executor?.tag} (${data?.executor?.id})` |
978 |
: "*Timeout expired automatically*" |
979 |
) |
980 |
.addField("User ID", oldMember.user.id) |
981 |
.setFooter({ |
982 |
text: "Timeout Removed", |
983 |
}) |
984 |
.setTimestamp(), |
985 |
], |
986 |
}); |
987 |
}, 2000); |
988 |
} else if (!oldMember.isCommunicationDisabled() && newMember.isCommunicationDisabled()) { |
989 |
setTimeout(async () => { |
990 |
const auditLog = await newMember.guild.fetchAuditLogs({ |
991 |
type: "MEMBER_UPDATE", |
992 |
limit: 1, |
993 |
}); |
994 |
|
995 |
const data = auditLog?.entries.first(); |
996 |
|
997 |
console.log(data); |
998 |
|
999 |
Punishment.create({ |
1000 |
createdAt: new Date(), |
1001 |
guild_id: newMember.guild.id, |
1002 |
mod_id: data?.executor?.id, |
1003 |
mod_tag: data?.executor?.tag, |
1004 |
reason: data?.reason, |
1005 |
user_id: newMember.user.id, |
1006 |
type: PunishmentType.TIMEOUT, |
1007 |
meta: { |
1008 |
time: ms(newMember.communicationDisabledUntilTimestamp - Date.now()), |
1009 |
}, |
1010 |
}).catch(console.error); |
1011 |
|
1012 |
await this.loggingChannel(newMember.guild.id)?.send({ |
1013 |
embeds: [ |
1014 |
new MessageEmbed() |
1015 |
.setColor("#f14a60") |
1016 |
.setTitle("Member Timed Out") |
1017 |
.setAuthor({ |
1018 |
name: newMember.user.tag, |
1019 |
iconURL: newMember.user.displayAvatarURL(), |
1020 |
}) |
1021 |
.addFields( |
1022 |
{ |
1023 |
name: "Reason", |
1024 |
value: data?.reason ?? "*No reason provided*", |
1025 |
}, |
1026 |
{ |
1027 |
name: "Duration", |
1028 |
value: newMember.communicationDisabledUntil |
1029 |
? `${newMember.communicationDisabledUntil.toUTCString()} (${formatDistanceStrict( |
1030 |
newMember.communicationDisabledUntil, |
1031 |
new Date() |
1032 |
)})` |
1033 |
: "Unknown", |
1034 |
} |
1035 |
) |
1036 |
.addField( |
1037 |
"Action taken by", |
1038 |
data?.target?.id === newMember.user.id && data?.executor?.tag |
1039 |
? `${data?.executor?.tag} (${data?.executor?.id})` |
1040 |
: "Unknown" |
1041 |
) |
1042 |
.addField("User ID", oldMember.user.id) |
1043 |
.setFooter({ |
1044 |
text: "Timed-out", |
1045 |
}) |
1046 |
.setTimestamp(), |
1047 |
], |
1048 |
}); |
1049 |
}, 2000); |
1050 |
} |
1051 |
} |
1052 |
} |