blob: b2b078dcb1e80f1811038e59bca525373cf32fc5 [file] [log] [blame]
import * as Discord from "discord.js";
import { toHexArray } from "./calculate.js";
import { promisify } from "util";
import generateKeyValueList from "./generateKeyValueList.js";
import client from "./client.js";
import { DiscordAPIError } from "discord.js";
import EmojiEmbed from "./generateEmojiEmbed.js";
const wait = promisify(setTimeout);
export interface LoggerOptions {
meta: {
type: string;
displayName: string;
calculateType: string;
color: number;
emoji: string;
timestamp: number;
buttons?: { buttonText: string; buttonId: string; buttonStyle: Discord.ButtonStyle }[];
imageData?: string;
};
list: Record<string | symbol | number, unknown>;
hidden: {
guild: string;
};
separate?: {
start?: string;
end?: string;
};
}
async function isLogging(guild: string, type: string): Promise<boolean> {
const config = await client.database.guilds.read(guild);
if (!config.logging.logs.enabled) return false;
if (!config.logging.logs.channel) return false;
if (!toHexArray(config.logging.logs.toLog).includes(type)) {
return false;
}
return true;
}
const NucleusColors = {
red: 0xf27878,
yellow: 0xf2d478,
green: 0x68d49e,
blue: 0x72aef5
};
export const Logger = {
renderUser(user: Discord.User | string) {
if (typeof user === "string") user = client.users.cache.get(user)!;
return `${user.username} [<@${user.id}>]`;
},
renderTime(t: number) {
if (isNaN(t)) return "Unknown";
t = Math.floor((t /= 1000));
return `<t:${t}:D> at <t:${t}:T>`;
},
renderDelta(t: number) {
if (isNaN(t)) return "Unknown";
t = Math.floor((t /= 1000));
return `<t:${t}:R> (<t:${t}:D> at <t:${t}:T>)`;
},
renderDateFooter(t: number) {
if (isNaN(t)) return "Unknown";
const date = new Date(t);
return `${date.getUTCFullYear()}-${
date.getUTCMonth() + 1
}-${date.getUTCDate()} at ${date.getUTCHours()}:${date.getUTCMinutes()}:${date.getUTCSeconds()} UTC`;
},
renderNumberDelta(num1: number, num2: number) {
const delta = num2 - num1;
return `${num1} -> ${num2} (${delta > 0 ? "+" : ""}${delta})`;
},
entry(
value: string | number | boolean | null | (string | boolean)[],
displayValue: string
): { value: string | boolean | null | (string | boolean | number)[]; displayValue: string } {
if (typeof value === "number") value = value.toString();
return { value: value, displayValue: displayValue };
},
renderChannel(channel: Discord.GuildChannel | Discord.ThreadChannel | string) {
if (typeof channel === "string")
channel = client.channels.cache.get(channel) as Discord.GuildChannel | Discord.ThreadChannel;
return `${channel.name} [<#${channel.id}>]`;
},
renderRole(role: Discord.Role | string, guild?: Discord.Guild | string) {
if (typeof role === "string")
role = (typeof guild === "string" ? client.guilds.cache.get(guild) : guild)!.roles.cache.get(role)!;
return `${role.name} [<@&${role.id}>]`;
},
renderEmoji(emoji: Discord.GuildEmoji) {
return `<${emoji.animated ? "a" : ""}:${emoji.name}:${emoji.id}> [\`:${emoji.name}:\`]`;
},
NucleusColors,
async getAuditLog(
guild: Discord.Guild,
event: Discord.GuildAuditLogsResolvable,
delay?: number
): Promise<Discord.GuildAuditLogsEntry[]> {
if (!guild.members.me?.permissions.has("ViewAuditLog")) return [];
await wait(delay ?? 250);
try {
const auditLog = (await guild.fetchAuditLogs({ type: event })).entries.map((m) => m);
return auditLog as Discord.GuildAuditLogsEntry[];
} catch (e) {
if (e instanceof DiscordAPIError) return [];
throw e;
}
},
async log(log: LoggerOptions): Promise<void> {
if (!(await isLogging(log.hidden.guild, log.meta.calculateType))) return;
const config = await client.database.guilds.read(log.hidden.guild);
if (config.logging.logs.channel) {
const channel = (await client.channels.fetch(config.logging.logs.channel)) as Discord.TextChannel | null;
const description: Record<string, string> = {};
Object.entries(log.list).map((entry) => {
const key: string = entry[0];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const value: any = entry[1];
if (value.displayValue) {
description[key] = value.displayValue;
} else {
description[key] = value;
}
});
if (channel) {
log.separate = log.separate ?? {};
const messageOptions: Parameters<Discord.TextChannel["send"]>[0] = {};
const components: Discord.ActionRowBuilder<Discord.ButtonBuilder> = new Discord.ActionRowBuilder();
messageOptions.embeds = [
new EmojiEmbed()
.setEmoji(log.meta.emoji)
.setTitle(log.meta.displayName)
.setDescription(
(log.separate.start ? log.separate.start + "\n" : "") +
generateKeyValueList(description) +
(log.separate.end ? "\n" + log.separate.end : "")
)
.setTimestamp(log.meta.timestamp)
.setColor(log.meta.color)
.setImage(log.meta.imageData ? "attachment://extra_log_data.json.base64" : null)
];
if (log.meta.buttons) {
const buttons = [];
for (const button of log.meta.buttons) {
buttons.push(
new Discord.ButtonBuilder()
.setCustomId(button.buttonId)
.setLabel(button.buttonText)
.setStyle(button.buttonStyle)
);
}
components.addComponents(buttons);
messageOptions.components = [components];
}
if (log.meta.imageData) {
messageOptions.files = [
{
attachment: Buffer.from(btoa(log.meta.imageData), "utf-8"), // Use base 64 to prevent virus scanning (EICAR)
name: "extra_log_data.json.base64"
}
];
}
await channel.send(messageOptions);
}
}
},
isLogging
};
export default {};