pineafan | 63fc5e2 | 2022-08-04 22:04:10 +0100 | [diff] [blame] | 1 | import * as Discord from "discord.js"; |
pineafan | 63fc5e2 | 2022-08-04 22:04:10 +0100 | [diff] [blame] | 2 | import { toHexArray } from "./calculate.js"; |
| 3 | import { promisify } from "util"; |
| 4 | import generateKeyValueList from "./generateKeyValueList.js"; |
| 5 | import client from "./client.js"; |
Skyler Grey | 6769176 | 2023-03-06 09:58:19 +0000 | [diff] [blame] | 6 | import { DiscordAPIError } from "discord.js"; |
TheCodedProf | 4a7c25d | 2023-06-07 17:09:45 -0400 | [diff] [blame] | 7 | import EmojiEmbed from "./generateEmojiEmbed.js"; |
pineafan | 3276721 | 2022-03-14 21:27:39 +0000 | [diff] [blame] | 8 | |
| 9 | const wait = promisify(setTimeout); |
| 10 | |
TheCodedProf | 6ec331b | 2023-02-20 12:13:06 -0500 | [diff] [blame] | 11 | export interface LoggerOptions { |
| 12 | meta: { |
| 13 | type: string; |
| 14 | displayName: string; |
| 15 | calculateType: string; |
| 16 | color: number; |
| 17 | emoji: string; |
| 18 | timestamp: number; |
pineafan | 435a878 | 2023-06-24 12:45:58 +0100 | [diff] [blame] | 19 | buttons?: { buttonText: string; buttonId: string; buttonStyle: Discord.ButtonStyle }[]; |
pineafan | 67c9f1f | 2023-06-23 22:50:26 +0100 | [diff] [blame] | 20 | imageData?: string; |
TheCodedProf | 6ec331b | 2023-02-20 12:13:06 -0500 | [diff] [blame] | 21 | }; |
TheCodedProf | 7b985d8 | 2023-06-08 16:40:41 -0400 | [diff] [blame] | 22 | list: Record<string | symbol | number, unknown>; |
TheCodedProf | 6ec331b | 2023-02-20 12:13:06 -0500 | [diff] [blame] | 23 | hidden: { |
| 24 | guild: string; |
Skyler Grey | da16adf | 2023-03-05 10:22:12 +0000 | [diff] [blame] | 25 | }; |
TheCodedProf | 6ec331b | 2023-02-20 12:13:06 -0500 | [diff] [blame] | 26 | separate?: { |
| 27 | start?: string; |
| 28 | end?: string; |
Skyler Grey | da16adf | 2023-03-05 10:22:12 +0000 | [diff] [blame] | 29 | }; |
TheCodedProf | 6ec331b | 2023-02-20 12:13:06 -0500 | [diff] [blame] | 30 | } |
| 31 | |
| 32 | async function isLogging(guild: string, type: string): Promise<boolean> { |
| 33 | const config = await client.database.guilds.read(guild); |
| 34 | if (!config.logging.logs.enabled) return false; |
| 35 | if (!config.logging.logs.channel) return false; |
Skyler Grey | da16adf | 2023-03-05 10:22:12 +0000 | [diff] [blame] | 36 | if (!toHexArray(config.logging.logs.toLog).includes(type)) { |
| 37 | return false; |
| 38 | } |
TheCodedProf | 6ec331b | 2023-02-20 12:13:06 -0500 | [diff] [blame] | 39 | return true; |
| 40 | } |
PineaFan | 64486c4 | 2022-12-28 09:21:04 +0000 | [diff] [blame] | 41 | |
pineafan | 67c9f1f | 2023-06-23 22:50:26 +0100 | [diff] [blame] | 42 | const NucleusColors = { |
| 43 | red: 0xf27878, |
| 44 | yellow: 0xf2d478, |
| 45 | green: 0x68d49e, |
pineafan | 435a878 | 2023-06-24 12:45:58 +0100 | [diff] [blame] | 46 | blue: 0x72aef5 |
pineafan | 67c9f1f | 2023-06-23 22:50:26 +0100 | [diff] [blame] | 47 | }; |
| 48 | |
PineaFan | 64486c4 | 2022-12-28 09:21:04 +0000 | [diff] [blame] | 49 | export const Logger = { |
pineafan | e625d78 | 2022-05-09 18:04:32 +0100 | [diff] [blame] | 50 | renderUser(user: Discord.User | string) { |
PineaFan | b0d0c24 | 2023-02-05 10:59:45 +0000 | [diff] [blame] | 51 | if (typeof user === "string") user = client.users.cache.get(user)!; |
pineafan | e625d78 | 2022-05-09 18:04:32 +0100 | [diff] [blame] | 52 | return `${user.username} [<@${user.id}>]`; |
PineaFan | 64486c4 | 2022-12-28 09:21:04 +0000 | [diff] [blame] | 53 | }, |
pineafan | e625d78 | 2022-05-09 18:04:32 +0100 | [diff] [blame] | 54 | renderTime(t: number) { |
Skyler Grey | 6769176 | 2023-03-06 09:58:19 +0000 | [diff] [blame] | 55 | if (isNaN(t)) return "Unknown"; |
Skyler Grey | 75ea917 | 2022-08-06 10:22:23 +0100 | [diff] [blame] | 56 | t = Math.floor((t /= 1000)); |
pineafan | e625d78 | 2022-05-09 18:04:32 +0100 | [diff] [blame] | 57 | return `<t:${t}:D> at <t:${t}:T>`; |
PineaFan | 64486c4 | 2022-12-28 09:21:04 +0000 | [diff] [blame] | 58 | }, |
pineafan | e625d78 | 2022-05-09 18:04:32 +0100 | [diff] [blame] | 59 | renderDelta(t: number) { |
Skyler Grey | 6769176 | 2023-03-06 09:58:19 +0000 | [diff] [blame] | 60 | if (isNaN(t)) return "Unknown"; |
Skyler Grey | 75ea917 | 2022-08-06 10:22:23 +0100 | [diff] [blame] | 61 | t = Math.floor((t /= 1000)); |
pineafan | e625d78 | 2022-05-09 18:04:32 +0100 | [diff] [blame] | 62 | return `<t:${t}:R> (<t:${t}:D> at <t:${t}:T>)`; |
PineaFan | 64486c4 | 2022-12-28 09:21:04 +0000 | [diff] [blame] | 63 | }, |
pineafan | 435a878 | 2023-06-24 12:45:58 +0100 | [diff] [blame] | 64 | renderDateFooter(t: number) { |
| 65 | if (isNaN(t)) return "Unknown"; |
| 66 | const date = new Date(t); |
| 67 | return `${date.getUTCFullYear()}-${ |
| 68 | date.getUTCMonth() + 1 |
| 69 | }-${date.getUTCDate()} at ${date.getUTCHours()}:${date.getUTCMinutes()}:${date.getUTCSeconds()} UTC`; |
| 70 | }, |
pineafan | bd02b4a | 2022-08-05 22:01:38 +0100 | [diff] [blame] | 71 | renderNumberDelta(num1: number, num2: number) { |
pineafan | 63fc5e2 | 2022-08-04 22:04:10 +0100 | [diff] [blame] | 72 | const delta = num2 - num1; |
| 73 | return `${num1} -> ${num2} (${delta > 0 ? "+" : ""}${delta})`; |
PineaFan | 64486c4 | 2022-12-28 09:21:04 +0000 | [diff] [blame] | 74 | }, |
Skyler Grey | da16adf | 2023-03-05 10:22:12 +0000 | [diff] [blame] | 75 | entry( |
| 76 | value: string | number | boolean | null | (string | boolean)[], |
| 77 | displayValue: string |
| 78 | ): { value: string | boolean | null | (string | boolean | number)[]; displayValue: string } { |
PineaFan | 0d06edc | 2023-01-17 22:10:31 +0000 | [diff] [blame] | 79 | if (typeof value === "number") value = value.toString(); |
pineafan | 63fc5e2 | 2022-08-04 22:04:10 +0100 | [diff] [blame] | 80 | return { value: value, displayValue: displayValue }; |
PineaFan | 64486c4 | 2022-12-28 09:21:04 +0000 | [diff] [blame] | 81 | }, |
TheCodedProf | 4a6d571 | 2023-01-19 15:54:40 -0500 | [diff] [blame] | 82 | renderChannel(channel: Discord.GuildChannel | Discord.ThreadChannel | string) { |
Skyler Grey | da16adf | 2023-03-05 10:22:12 +0000 | [diff] [blame] | 83 | if (typeof channel === "string") |
| 84 | channel = client.channels.cache.get(channel) as Discord.GuildChannel | Discord.ThreadChannel; |
pineafan | e625d78 | 2022-05-09 18:04:32 +0100 | [diff] [blame] | 85 | return `${channel.name} [<#${channel.id}>]`; |
PineaFan | 64486c4 | 2022-12-28 09:21:04 +0000 | [diff] [blame] | 86 | }, |
TheCodedProf | 486bca3 | 2023-02-02 16:49:44 -0500 | [diff] [blame] | 87 | renderRole(role: Discord.Role | string, guild?: Discord.Guild | string) { |
Skyler Grey | da16adf | 2023-03-05 10:22:12 +0000 | [diff] [blame] | 88 | if (typeof role === "string") |
| 89 | role = (typeof guild === "string" ? client.guilds.cache.get(guild) : guild)!.roles.cache.get(role)!; |
pineafan | e625d78 | 2022-05-09 18:04:32 +0100 | [diff] [blame] | 90 | return `${role.name} [<@&${role.id}>]`; |
PineaFan | 64486c4 | 2022-12-28 09:21:04 +0000 | [diff] [blame] | 91 | }, |
pineafan | e625d78 | 2022-05-09 18:04:32 +0100 | [diff] [blame] | 92 | renderEmoji(emoji: Discord.GuildEmoji) { |
Skyler Grey | 11236ba | 2022-08-08 21:13:33 +0100 | [diff] [blame] | 93 | return `<${emoji.animated ? "a" : ""}:${emoji.name}:${emoji.id}> [\`:${emoji.name}:\`]`; |
PineaFan | 64486c4 | 2022-12-28 09:21:04 +0000 | [diff] [blame] | 94 | }, |
pineafan | 67c9f1f | 2023-06-23 22:50:26 +0100 | [diff] [blame] | 95 | NucleusColors, |
Skyler Grey | da16adf | 2023-03-05 10:22:12 +0000 | [diff] [blame] | 96 | async getAuditLog( |
| 97 | guild: Discord.Guild, |
| 98 | event: Discord.GuildAuditLogsResolvable, |
| 99 | delay?: number |
| 100 | ): Promise<Discord.GuildAuditLogsEntry[]> { |
Skyler Grey | 1b66963 | 2023-03-05 10:58:11 +0000 | [diff] [blame] | 101 | if (!guild.members.me?.permissions.has("ViewAuditLog")) return []; |
TheCodedProf | 686829f | 2023-02-22 15:08:01 -0500 | [diff] [blame] | 102 | await wait(delay ?? 250); |
Skyler Grey | 1b66963 | 2023-03-05 10:58:11 +0000 | [diff] [blame] | 103 | try { |
| 104 | const auditLog = (await guild.fetchAuditLogs({ type: event })).entries.map((m) => m); |
| 105 | return auditLog as Discord.GuildAuditLogsEntry[]; |
| 106 | } catch (e) { |
| 107 | if (e instanceof DiscordAPIError) return []; |
| 108 | throw e; |
| 109 | } |
PineaFan | 64486c4 | 2022-12-28 09:21:04 +0000 | [diff] [blame] | 110 | }, |
pineafan | 67c9f1f | 2023-06-23 22:50:26 +0100 | [diff] [blame] | 111 | |
TheCodedProf | 6ec331b | 2023-02-20 12:13:06 -0500 | [diff] [blame] | 112 | async log(log: LoggerOptions): Promise<void> { |
Skyler Grey | da16adf | 2023-03-05 10:22:12 +0000 | [diff] [blame] | 113 | if (!(await isLogging(log.hidden.guild, log.meta.calculateType))) return; |
pineafan | 63fc5e2 | 2022-08-04 22:04:10 +0100 | [diff] [blame] | 114 | const config = await client.database.guilds.read(log.hidden.guild); |
Samuel Shuert | a1511f9 | 2023-03-04 13:55:33 -0500 | [diff] [blame] | 115 | |
pineafan | e625d78 | 2022-05-09 18:04:32 +0100 | [diff] [blame] | 116 | if (config.logging.logs.channel) { |
Skyler Grey | 11236ba | 2022-08-08 21:13:33 +0100 | [diff] [blame] | 117 | const channel = (await client.channels.fetch(config.logging.logs.channel)) as Discord.TextChannel | null; |
pineafan | bd02b4a | 2022-08-05 22:01:38 +0100 | [diff] [blame] | 118 | const description: Record<string, string> = {}; |
Skyler Grey | 75ea917 | 2022-08-06 10:22:23 +0100 | [diff] [blame] | 119 | Object.entries(log.list).map((entry) => { |
pineafan | bd02b4a | 2022-08-05 22:01:38 +0100 | [diff] [blame] | 120 | const key: string = entry[0]; |
pineafan | 63fc5e2 | 2022-08-04 22:04:10 +0100 | [diff] [blame] | 121 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 122 | const value: any = entry[1]; |
Skyler Grey | 75ea917 | 2022-08-06 10:22:23 +0100 | [diff] [blame] | 123 | if (value.displayValue) { |
pineafan | e625d78 | 2022-05-09 18:04:32 +0100 | [diff] [blame] | 124 | description[key] = value.displayValue; |
| 125 | } else { |
| 126 | description[key] = value; |
| 127 | } |
pineafan | 63fc5e2 | 2022-08-04 22:04:10 +0100 | [diff] [blame] | 128 | }); |
pineafan | e625d78 | 2022-05-09 18:04:32 +0100 | [diff] [blame] | 129 | if (channel) { |
TheCodedProf | 1807fb3 | 2023-02-20 14:33:48 -0500 | [diff] [blame] | 130 | log.separate = log.separate ?? {}; |
TheCodedProf | 4a7c25d | 2023-06-07 17:09:45 -0400 | [diff] [blame] | 131 | const messageOptions: Parameters<Discord.TextChannel["send"]>[0] = {}; |
| 132 | const components: Discord.ActionRowBuilder<Discord.ButtonBuilder> = new Discord.ActionRowBuilder(); |
| 133 | messageOptions.embeds = [ |
| 134 | new EmojiEmbed() |
| 135 | .setEmoji(log.meta.emoji) |
| 136 | .setTitle(log.meta.displayName) |
| 137 | .setDescription( |
| 138 | (log.separate.start ? log.separate.start + "\n" : "") + |
| 139 | generateKeyValueList(description) + |
| 140 | (log.separate.end ? "\n" + log.separate.end : "") |
| 141 | ) |
| 142 | .setTimestamp(log.meta.timestamp) |
| 143 | .setColor(log.meta.color) |
pineafan | 67c9f1f | 2023-06-23 22:50:26 +0100 | [diff] [blame] | 144 | .setImage(log.meta.imageData ? "attachment://extra_log_data.json.base64" : null) |
TheCodedProf | 4a7c25d | 2023-06-07 17:09:45 -0400 | [diff] [blame] | 145 | ]; |
pineafan | 67c9f1f | 2023-06-23 22:50:26 +0100 | [diff] [blame] | 146 | if (log.meta.buttons) { |
pineafan | 435a878 | 2023-06-24 12:45:58 +0100 | [diff] [blame] | 147 | const buttons = []; |
pineafan | 67c9f1f | 2023-06-23 22:50:26 +0100 | [diff] [blame] | 148 | for (const button of log.meta.buttons) { |
| 149 | buttons.push( |
| 150 | new Discord.ButtonBuilder() |
| 151 | .setCustomId(button.buttonId) |
| 152 | .setLabel(button.buttonText) |
| 153 | .setStyle(button.buttonStyle) |
pineafan | 435a878 | 2023-06-24 12:45:58 +0100 | [diff] [blame] | 154 | ); |
pineafan | 67c9f1f | 2023-06-23 22:50:26 +0100 | [diff] [blame] | 155 | } |
| 156 | components.addComponents(buttons); |
TheCodedProf | 4a7c25d | 2023-06-07 17:09:45 -0400 | [diff] [blame] | 157 | messageOptions.components = [components]; |
| 158 | } |
pineafan | 67c9f1f | 2023-06-23 22:50:26 +0100 | [diff] [blame] | 159 | if (log.meta.imageData) { |
| 160 | messageOptions.files = [ |
| 161 | { |
pineafan | 435a878 | 2023-06-24 12:45:58 +0100 | [diff] [blame] | 162 | attachment: Buffer.from(btoa(log.meta.imageData), "utf-8"), // Use base 64 to prevent virus scanning (EICAR) |
pineafan | 67c9f1f | 2023-06-23 22:50:26 +0100 | [diff] [blame] | 163 | name: "extra_log_data.json.base64" |
| 164 | } |
| 165 | ]; |
| 166 | } |
TheCodedProf | 4a7c25d | 2023-06-07 17:09:45 -0400 | [diff] [blame] | 167 | await channel.send(messageOptions); |
pineafan | e625d78 | 2022-05-09 18:04:32 +0100 | [diff] [blame] | 168 | } |
| 169 | } |
TheCodedProf | 6ec331b | 2023-02-20 12:13:06 -0500 | [diff] [blame] | 170 | }, |
| 171 | isLogging |
PineaFan | 64486c4 | 2022-12-28 09:21:04 +0000 | [diff] [blame] | 172 | }; |
| 173 | |
pineafan | 63fc5e2 | 2022-08-04 22:04:10 +0100 | [diff] [blame] | 174 | export default {}; |