poixpixel-discord-bot/src/util/logging/utils.ts

249 lines
6.1 KiB
TypeScript

import {
User,
GuildMember,
GuildChannel,
EmbedField,
PermissionsBitField,
} from 'discord.js';
import { LogActionPayload, LogActionType, RoleProperties } from './types.js';
import { ACTION_EMOJIS } from './constants.js';
/**
* Formats a permission name to be more readable
* @param perm - The permission to format
* @returns - The formatted permission name
*/
export const formatPermissionName = (perm: string): string => {
return perm
.split('_')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ');
};
/**
* Creates a field for a user
* @param user - The user to create a field for
* @param label - The label for the field
* @returns - The created field
*/
export const createUserField = (
user: User | GuildMember,
label = 'User',
): EmbedField => ({
name: label,
value: `<@${user.id}>`,
inline: true,
});
/**
* Creates a field for a moderator
* @param moderator - The moderator to create a field for
* @param label - The label for the field
* @returns - The created field
*/
export const createModeratorField = (
moderator?: GuildMember,
label = 'Moderator',
): EmbedField | null =>
moderator
? {
name: label,
value: `<@${moderator.id}>`,
inline: true,
}
: null;
/**
* Creates a field for a channel
* @param channel - The channel to create a field for
* @returns - The created field
*/
export const createChannelField = (channel: GuildChannel): EmbedField => ({
name: 'Channel',
value: `<#${channel.id}>`,
inline: true,
});
/**
* Creates a field for changed permissions
* @param oldPerms - The old permissions
* @param newPerms - The new permissions
* @returns - The created fields
*/
export const createPermissionChangeFields = (
oldPerms: Readonly<PermissionsBitField>,
newPerms: Readonly<PermissionsBitField>,
): EmbedField[] => {
const fields: EmbedField[] = [];
const changes: { added: string[]; removed: string[] } = {
added: [],
removed: [],
};
Object.keys(PermissionsBitField.Flags).forEach((perm) => {
const hasOld = oldPerms.has(perm as keyof typeof PermissionsBitField.Flags);
const hasNew = newPerms.has(perm as keyof typeof PermissionsBitField.Flags);
if (hasOld !== hasNew) {
if (hasNew) {
changes.added.push(formatPermissionName(perm));
} else {
changes.removed.push(formatPermissionName(perm));
}
}
});
if (changes.added.length) {
fields.push({
name: '✅ Added Permissions',
value: changes.added.join('\n'),
inline: true,
});
}
if (changes.removed.length) {
fields.push({
name: '❌ Removed Permissions',
value: changes.removed.join('\n'),
inline: true,
});
}
return fields;
};
/**
* Gets the names of the permissions in a bitfield
* @param permissions - The permissions to get the names of
* @returns - The names of the permissions
*/
export const getPermissionNames = (
permissions: Readonly<PermissionsBitField>,
): string[] => {
const names: string[] = [];
Object.keys(PermissionsBitField.Flags).forEach((perm) => {
if (permissions.has(perm as keyof typeof PermissionsBitField.Flags)) {
names.push(formatPermissionName(perm));
}
});
return names;
};
/**
* Compares two bitfields and returns the names of the permissions that are in the first bitfield but not the second
* @param a - The first bitfield
* @param b - The second bitfield
* @returns - The names of the permissions that are in the first bitfield but not the second
*/
export const getPermissionDifference = (
a: Readonly<PermissionsBitField>,
b: Readonly<PermissionsBitField>,
): string[] => {
const names: string[] = [];
Object.keys(PermissionsBitField.Flags).forEach((perm) => {
const permKey = perm as keyof typeof PermissionsBitField.Flags;
if (a.has(permKey) && !b.has(permKey)) {
names.push(formatPermissionName(perm));
}
});
return names;
};
/**
* Creates a field for a role
* @param oldRole - The old role
* @param newRole - The new role
* @returns - The fields for the role changes
*/
export const createRoleChangeFields = (
oldRole: Partial<RoleProperties>,
newRole: Partial<RoleProperties>,
): EmbedField[] => {
const fields: EmbedField[] = [];
if (oldRole.name !== newRole.name) {
fields.push({
name: 'Name Changed',
value: `${oldRole.name}${newRole.name}`,
inline: true,
});
}
if (oldRole.color !== newRole.color) {
fields.push({
name: 'Color Changed',
value: `${oldRole.color || 'None'}${newRole.color || 'None'}`,
inline: true,
});
}
const booleanProps: Array<
keyof Pick<RoleProperties, 'hoist' | 'mentionable'>
> = ['hoist', 'mentionable'];
for (const prop of booleanProps) {
if (oldRole[prop] !== newRole[prop]) {
fields.push({
name: `${prop.charAt(0).toUpperCase() + prop.slice(1)} Changed`,
value: `${oldRole[prop] ? 'Yes' : 'No'}${newRole[prop] ? 'Yes' : 'No'}`,
inline: true,
});
}
}
return fields;
};
/**
* Gets the ID of the item that was logged
* @param payload - The payload to get the log item ID from
* @returns - The ID of the log item
*/
export const getLogItemId = (payload: LogActionPayload): string => {
switch (payload.action) {
case 'roleCreate':
case 'roleDelete':
case 'roleUpdate':
case 'roleAdd':
case 'roleRemove':
return payload.role.id;
case 'channelCreate':
case 'channelDelete':
case 'channelUpdate':
return payload.channel.id;
case 'messageDelete':
case 'messageEdit':
return payload.message.id;
case 'memberJoin':
case 'memberLeave':
return payload.member.id;
case 'ban':
case 'kick':
case 'mute':
case 'unban':
case 'unmute':
case 'warn':
return payload.target.id;
default:
return 'N/A';
}
};
/**
* Gets the emoji for an action
* @param action - The action to get an emoji for
* @returns - The emoji for the action
*/
export const getEmojiForAction = (action: LogActionType): string => {
return ACTION_EMOJIS[action] || '📝';
};