blob: b2b078dcb1e80f1811038e59bca525373cf32fc5 [file] [log] [blame]
pineafan63fc5e22022-08-04 22:04:10 +01001import * as Discord from "discord.js";
pineafan63fc5e22022-08-04 22:04:10 +01002import { toHexArray } from "./calculate.js";
3import { promisify } from "util";
4import generateKeyValueList from "./generateKeyValueList.js";
5import client from "./client.js";
Skyler Grey67691762023-03-06 09:58:19 +00006import { DiscordAPIError } from "discord.js";
TheCodedProf4a7c25d2023-06-07 17:09:45 -04007import EmojiEmbed from "./generateEmojiEmbed.js";
pineafan32767212022-03-14 21:27:39 +00008
9const wait = promisify(setTimeout);
10
TheCodedProf6ec331b2023-02-20 12:13:06 -050011export interface LoggerOptions {
12 meta: {
13 type: string;
14 displayName: string;
15 calculateType: string;
16 color: number;
17 emoji: string;
18 timestamp: number;
pineafan435a8782023-06-24 12:45:58 +010019 buttons?: { buttonText: string; buttonId: string; buttonStyle: Discord.ButtonStyle }[];
pineafan67c9f1f2023-06-23 22:50:26 +010020 imageData?: string;
TheCodedProf6ec331b2023-02-20 12:13:06 -050021 };
TheCodedProf7b985d82023-06-08 16:40:41 -040022 list: Record<string | symbol | number, unknown>;
TheCodedProf6ec331b2023-02-20 12:13:06 -050023 hidden: {
24 guild: string;
Skyler Greyda16adf2023-03-05 10:22:12 +000025 };
TheCodedProf6ec331b2023-02-20 12:13:06 -050026 separate?: {
27 start?: string;
28 end?: string;
Skyler Greyda16adf2023-03-05 10:22:12 +000029 };
TheCodedProf6ec331b2023-02-20 12:13:06 -050030}
31
32async 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 Greyda16adf2023-03-05 10:22:12 +000036 if (!toHexArray(config.logging.logs.toLog).includes(type)) {
37 return false;
38 }
TheCodedProf6ec331b2023-02-20 12:13:06 -050039 return true;
40}
PineaFan64486c42022-12-28 09:21:04 +000041
pineafan67c9f1f2023-06-23 22:50:26 +010042const NucleusColors = {
43 red: 0xf27878,
44 yellow: 0xf2d478,
45 green: 0x68d49e,
pineafan435a8782023-06-24 12:45:58 +010046 blue: 0x72aef5
pineafan67c9f1f2023-06-23 22:50:26 +010047};
48
PineaFan64486c42022-12-28 09:21:04 +000049export const Logger = {
pineafane625d782022-05-09 18:04:32 +010050 renderUser(user: Discord.User | string) {
PineaFanb0d0c242023-02-05 10:59:45 +000051 if (typeof user === "string") user = client.users.cache.get(user)!;
pineafane625d782022-05-09 18:04:32 +010052 return `${user.username} [<@${user.id}>]`;
PineaFan64486c42022-12-28 09:21:04 +000053 },
pineafane625d782022-05-09 18:04:32 +010054 renderTime(t: number) {
Skyler Grey67691762023-03-06 09:58:19 +000055 if (isNaN(t)) return "Unknown";
Skyler Grey75ea9172022-08-06 10:22:23 +010056 t = Math.floor((t /= 1000));
pineafane625d782022-05-09 18:04:32 +010057 return `<t:${t}:D> at <t:${t}:T>`;
PineaFan64486c42022-12-28 09:21:04 +000058 },
pineafane625d782022-05-09 18:04:32 +010059 renderDelta(t: number) {
Skyler Grey67691762023-03-06 09:58:19 +000060 if (isNaN(t)) return "Unknown";
Skyler Grey75ea9172022-08-06 10:22:23 +010061 t = Math.floor((t /= 1000));
pineafane625d782022-05-09 18:04:32 +010062 return `<t:${t}:R> (<t:${t}:D> at <t:${t}:T>)`;
PineaFan64486c42022-12-28 09:21:04 +000063 },
pineafan435a8782023-06-24 12:45:58 +010064 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 },
pineafanbd02b4a2022-08-05 22:01:38 +010071 renderNumberDelta(num1: number, num2: number) {
pineafan63fc5e22022-08-04 22:04:10 +010072 const delta = num2 - num1;
73 return `${num1} -> ${num2} (${delta > 0 ? "+" : ""}${delta})`;
PineaFan64486c42022-12-28 09:21:04 +000074 },
Skyler Greyda16adf2023-03-05 10:22:12 +000075 entry(
76 value: string | number | boolean | null | (string | boolean)[],
77 displayValue: string
78 ): { value: string | boolean | null | (string | boolean | number)[]; displayValue: string } {
PineaFan0d06edc2023-01-17 22:10:31 +000079 if (typeof value === "number") value = value.toString();
pineafan63fc5e22022-08-04 22:04:10 +010080 return { value: value, displayValue: displayValue };
PineaFan64486c42022-12-28 09:21:04 +000081 },
TheCodedProf4a6d5712023-01-19 15:54:40 -050082 renderChannel(channel: Discord.GuildChannel | Discord.ThreadChannel | string) {
Skyler Greyda16adf2023-03-05 10:22:12 +000083 if (typeof channel === "string")
84 channel = client.channels.cache.get(channel) as Discord.GuildChannel | Discord.ThreadChannel;
pineafane625d782022-05-09 18:04:32 +010085 return `${channel.name} [<#${channel.id}>]`;
PineaFan64486c42022-12-28 09:21:04 +000086 },
TheCodedProf486bca32023-02-02 16:49:44 -050087 renderRole(role: Discord.Role | string, guild?: Discord.Guild | string) {
Skyler Greyda16adf2023-03-05 10:22:12 +000088 if (typeof role === "string")
89 role = (typeof guild === "string" ? client.guilds.cache.get(guild) : guild)!.roles.cache.get(role)!;
pineafane625d782022-05-09 18:04:32 +010090 return `${role.name} [<@&${role.id}>]`;
PineaFan64486c42022-12-28 09:21:04 +000091 },
pineafane625d782022-05-09 18:04:32 +010092 renderEmoji(emoji: Discord.GuildEmoji) {
Skyler Grey11236ba2022-08-08 21:13:33 +010093 return `<${emoji.animated ? "a" : ""}:${emoji.name}:${emoji.id}> [\`:${emoji.name}:\`]`;
PineaFan64486c42022-12-28 09:21:04 +000094 },
pineafan67c9f1f2023-06-23 22:50:26 +010095 NucleusColors,
Skyler Greyda16adf2023-03-05 10:22:12 +000096 async getAuditLog(
97 guild: Discord.Guild,
98 event: Discord.GuildAuditLogsResolvable,
99 delay?: number
100 ): Promise<Discord.GuildAuditLogsEntry[]> {
Skyler Grey1b669632023-03-05 10:58:11 +0000101 if (!guild.members.me?.permissions.has("ViewAuditLog")) return [];
TheCodedProf686829f2023-02-22 15:08:01 -0500102 await wait(delay ?? 250);
Skyler Grey1b669632023-03-05 10:58:11 +0000103 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 }
PineaFan64486c42022-12-28 09:21:04 +0000110 },
pineafan67c9f1f2023-06-23 22:50:26 +0100111
TheCodedProf6ec331b2023-02-20 12:13:06 -0500112 async log(log: LoggerOptions): Promise<void> {
Skyler Greyda16adf2023-03-05 10:22:12 +0000113 if (!(await isLogging(log.hidden.guild, log.meta.calculateType))) return;
pineafan63fc5e22022-08-04 22:04:10 +0100114 const config = await client.database.guilds.read(log.hidden.guild);
Samuel Shuerta1511f92023-03-04 13:55:33 -0500115
pineafane625d782022-05-09 18:04:32 +0100116 if (config.logging.logs.channel) {
Skyler Grey11236ba2022-08-08 21:13:33 +0100117 const channel = (await client.channels.fetch(config.logging.logs.channel)) as Discord.TextChannel | null;
pineafanbd02b4a2022-08-05 22:01:38 +0100118 const description: Record<string, string> = {};
Skyler Grey75ea9172022-08-06 10:22:23 +0100119 Object.entries(log.list).map((entry) => {
pineafanbd02b4a2022-08-05 22:01:38 +0100120 const key: string = entry[0];
pineafan63fc5e22022-08-04 22:04:10 +0100121 // eslint-disable-next-line @typescript-eslint/no-explicit-any
122 const value: any = entry[1];
Skyler Grey75ea9172022-08-06 10:22:23 +0100123 if (value.displayValue) {
pineafane625d782022-05-09 18:04:32 +0100124 description[key] = value.displayValue;
125 } else {
126 description[key] = value;
127 }
pineafan63fc5e22022-08-04 22:04:10 +0100128 });
pineafane625d782022-05-09 18:04:32 +0100129 if (channel) {
TheCodedProf1807fb32023-02-20 14:33:48 -0500130 log.separate = log.separate ?? {};
TheCodedProf4a7c25d2023-06-07 17:09:45 -0400131 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)
pineafan67c9f1f2023-06-23 22:50:26 +0100144 .setImage(log.meta.imageData ? "attachment://extra_log_data.json.base64" : null)
TheCodedProf4a7c25d2023-06-07 17:09:45 -0400145 ];
pineafan67c9f1f2023-06-23 22:50:26 +0100146 if (log.meta.buttons) {
pineafan435a8782023-06-24 12:45:58 +0100147 const buttons = [];
pineafan67c9f1f2023-06-23 22:50:26 +0100148 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)
pineafan435a8782023-06-24 12:45:58 +0100154 );
pineafan67c9f1f2023-06-23 22:50:26 +0100155 }
156 components.addComponents(buttons);
TheCodedProf4a7c25d2023-06-07 17:09:45 -0400157 messageOptions.components = [components];
158 }
pineafan67c9f1f2023-06-23 22:50:26 +0100159 if (log.meta.imageData) {
160 messageOptions.files = [
161 {
pineafan435a8782023-06-24 12:45:58 +0100162 attachment: Buffer.from(btoa(log.meta.imageData), "utf-8"), // Use base 64 to prevent virus scanning (EICAR)
pineafan67c9f1f2023-06-23 22:50:26 +0100163 name: "extra_log_data.json.base64"
164 }
165 ];
166 }
TheCodedProf4a7c25d2023-06-07 17:09:45 -0400167 await channel.send(messageOptions);
pineafane625d782022-05-09 18:04:32 +0100168 }
169 }
TheCodedProf6ec331b2023-02-20 12:13:06 -0500170 },
171 isLogging
PineaFan64486c42022-12-28 09:21:04 +0000172};
173
pineafan63fc5e22022-08-04 22:04:10 +0100174export default {};