Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Advanced logging system #527

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion config.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,4 @@ module.exports = {
CREATE_EMBED: "#068ADD",
CLOSE_EMBED: "#068ADD",
},
};
};
249 changes: 249 additions & 0 deletions src/commands/admin/logging.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
const { EmbedBuilder } = require("discord.js");
const { ApplicationCommandOptionType, ChannelType } = require("discord.js");
const subs = ["channels", "bans", "messages", "voice", "members", "emojis", "invites", "status"]
/**
* @type {import("@structures/Command")}
*/
module.exports = {
name: "logging",
description: "enable or disable advance logs",
category: "ADMIN",
userPermissions: ["ManageGuild"],
command: {
enabled: true,
subcommands: [
{
trigger: "channels <#channel|off>",
description: "enable logging for channel updates",
},
{
trigger: "roles <#channel|off>",
description: "enable logging for role updates",
},
{
trigger: "members <#channel|off>",
description: "enable logging for member updates",
},
{
trigger: "voice <#channel|off>",
description: "enable logging for voice channel updates",
},
{
trigger: "emojis <#channel|off>",
description: "enable logging for emojis creation/updates and deletion",
},
{
trigger: "invites <#channel|off>",
description: "enable logging for invite creation and deletion",
},
{
trigger: "messages <#channel|off>",
description: "enable logging for message updates",
},
{
trigger: "bans <#channel|off>",
description: "enable logging for bans updates",
},
{
trigger: "status",
description: "check logging setup status",
},
],
minArgsCount: 1,
},
slashCommand: {
enabled: true,
ephemeral: true,
options: [
{
name: "channels",
description: "enable logging for channel updates",
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: "channel",
description: "channel to send logs (leave empty to disable)",
type: ApplicationCommandOptionType.Channel,
required: false,
channelTypes: [ChannelType.GuildText],
},
],
},
{
name: "roles",
description: "enable logging for role updates",
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: "channel",
description: "channel to send logs (leave empty to disable)",
type: ApplicationCommandOptionType.Channel,
required: false,
channelTypes: [ChannelType.GuildText],
},
],
},
{
name: "members",
description: "enable logging for members updates",
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: "channel",
description: "channel to send logs (leave empty to disable)",
type: ApplicationCommandOptionType.Channel,
required: false,
channelTypes: [ChannelType.GuildText],
},
],
},
{
name: "voice",
description: "enable logging for voice channel updates",
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: "channel",
description: "channel to send logs (leave empty to disable)",
type: ApplicationCommandOptionType.Channel,
required: false,
channelTypes: [ChannelType.GuildText],
},
],
},
{
name: "emojis",
description: "enable logging for emojis updates",
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: "channel",
description: "channel to send logs (leave empty to disable)",
type: ApplicationCommandOptionType.Channel,
required: false,
channelTypes: [ChannelType.GuildText],
},
],
},
{
name: "invites",
description: "enable logging for invites updates",
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: "channel",
description: "channel to send logs (leave empty to disable)",
type: ApplicationCommandOptionType.Channel,
required: false,
channelTypes: [ChannelType.GuildText],
},
],
},
{
name: "messages",
description: "enable logging for messages updates",
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: "channel",
description: "channel to send logs (leave empty to disable)",
type: ApplicationCommandOptionType.Channel,
required: false,
channelTypes: [ChannelType.GuildText],
},
],
},
{
name: "status",
description: "check logging setup status",
type: ApplicationCommandOptionType.Subcommand,
},
{
name: "bans",
description: "enable logging for bans updates",
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: "channel",
description: "channel to send logs (leave empty to disable)",
type: ApplicationCommandOptionType.Channel,
required: false,
channelTypes: [ChannelType.GuildText],
},
],
},
],
},

async messageRun(message, args, data) {
const settings = data.settings;
const sub = args[0].toLowerCase();
const input = args[1]?.toLowerCase();
if (!subs.includes(sub)) return message.safeReply(`Invalid subcommands\nAvaliable Subcommands: ${subs.map((s) => `\`${s}\``).join(", ")}`)
let targetChannel;

if (input === "none" || input === "off" || input === "disable" || !input) targetChannel = null;
else {
if (message.mentions.channels.size === 0) return message.safeReply("Incorrect command usage");
targetChannel = message.mentions.channels.first();
}
let response;
if (sub === "status") {
response = getLogStatus(settings, message.client)
}
else {
response = await setChannel(targetChannel, settings, sub);
}
return message.safeReply(response);
},

async interactionRun(interaction, data) {
const sub = interaction.options.getSubcommand();
let channel = interaction.options.getChannel("channel");
if (!channel) channel = null;
let response;
if (sub === "status") {
response = getLogStatus(data.settings, interaction.client)
}
else {
response = await setChannel(channel, data.settings, sub);
}
return interaction.followUp(response);
},
};

async function setChannel(targetChannel, settings, sub) {
if (!targetChannel && !settings.logging[sub]) {
return "It is already disabled";
}

if (targetChannel && !targetChannel.canSendEmbeds()) {
return "Ugh! I cannot send logs to that channel? I need the `Write Messages` and `Embed Links` permissions in that channel";
}

settings.logging[sub] = targetChannel?.id;
await settings.save();
return `Configuration saved! **${parseSub(sub)} Updates** channel ${targetChannel ? `updated to ${targetChannel}` : "removed"}`;
}
/**
* @param {string} sub
*/
const parseSub = (sub) => {
return sub.charAt(0).toUpperCase() + sub.slice(1);
}

function getLogStatus(settings, client) {
const formatted = Object.entries(settings.logging).map(([k, v]) => {
if (!v) return `${parseSub(k)}: none`
const channel = client.channels.cache.get(v);
return `${parseSub(k)}: ${channel ? channel.toString() : "none"}`
}).join("\n")
return {
embeds: [
new EmbedBuilder()
.setTitle(`Logging Status`)
.setColor(`Green`)
.setDescription(formatted)
]
}
}
12 changes: 11 additions & 1 deletion src/database/schemas/Guild.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ const Schema = new mongoose.Schema({
enabled: Boolean,
},
modlog_channel: String,
logging: {
messages: String,
channels: String,
emojis: String,
members: String,
roles: String,
bans: String,
invites: String,
voice: String,
},
max_warn: {
action: {
type: String,
Expand Down Expand Up @@ -132,7 +142,7 @@ module.exports = {
const userDb = await getUser(owner);
await userDb.save();
})
.catch((ex) => {});
.catch((ex) => { });

// create a new guild model
guildData = new Model({
Expand Down
26 changes: 26 additions & 0 deletions src/events/bans/guildBanAdd.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const { getSettings } = require("@schemas/Guild");
const { EmbedBuilder, AuditLogEvent } = require("discord.js");

/**
* Log when someone gets banned
* @param {import("@structures/BotClient")} client
* @param {import("discord.js").GuildBan} ban
*/
module.exports = async (client, ban) => {

const settings = await getSettings(ban.guild)
if (!settings.logging?.bans) return;
const logChannel = client.channels.cache.get(settings.logging.bans);
if (!logChannel) return;
const auditLog = await ban.guild.fetchAuditLogs({ type: AuditLogEvent.MemberBanAdd, limit: 1 }).then((en) => en.entries.first())
const executor = auditLog.targetId === ban.user.id ? auditLog.executor : "Unknown"
const embed = new EmbedBuilder()
.setAuthor({ name: "Member Banned" })
.setTitle(`${ban.user} (${ban.user.username}) was banned!`)
.setColor("Red")
.setDescription(`Reason: ${auditLog.reason || "None"}`)
.setFooter({ text: `ID: ${ban.user.id} | Executor: ${executor?.username || "Unknown"}` })
.setThumbnail(ban.user.displayAvatarURL());

await logChannel.send({ embeds: [embed] })
}
26 changes: 26 additions & 0 deletions src/events/bans/guildBanRemove.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const { getSettings } = require("@schemas/Guild");
const { EmbedBuilder, AuditLogEvent } = require("discord.js");

/**
* Log when someone gets unbanned
* @param {import("@structures/BotClient")} client
* @param {import("discord.js").GuildBan} ban
*/
module.exports = async (client, ban) => {

const settings = await getSettings(ban.guild)
if (!settings.logging?.bans) return;
const logChannel = client.channels.cache.get(settings.logging.bans);
if (!logChannel) return;
const auditLog = await ban.guild.fetchAuditLogs({ type: AuditLogEvent.MemberBanRemove, limit: 1 }).then((en) => en.entries.first())
const executor = auditLog.targetId === ban.user.id ? auditLog.executor : "Unknown"
const embed = new EmbedBuilder()
.setAuthor({ name: "Member Unbanned" })
.setTitle(`${ban.user} (${ban.user.username}) was unbanned!`)
.setColor("Green")
.setDescription(`Ban Reason: ${ban.reason || "Not Available"}\n Unban Reason: ${auditLog.reason || "Not"}`)
.setFooter({ text: `ID: ${ban.user.id} | Executor: ${executor?.username || "Unknown"}` })
.setThumbnail(ban.user.displayAvatarURL());

await logChannel.send({ embeds: [embed] })
}
29 changes: 29 additions & 0 deletions src/events/channel/channelCreate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const { getSettings } = require("@schemas/Guild");
const { EmbedBuilder, AuditLogEvent } = require("discord.js");
/**
* @param {import('@src/structures').BotClient} client
* @param {import("discord.js").GuildBasedChannel} channel
*/
module.exports = async (client, channel) => {
const settings = await getSettings(channel.guild);
if (!settings.logging?.channels) return;
const logChannel = client.channels.cache.get(settings.logging.channels);
if (!logChannel) return;
const auditLog = await channel.guild.fetchAuditLogs({ type: AuditLogEvent.ChannelCreate, limit: 1 });
const entry = auditLog.entries.first();
const executor = entry.target.id === channel.id ? entry.executor : null;
const channelType = require("@helpers/channelTypes")(channel.type);
const embed = new EmbedBuilder()
.setAuthor({ name: "Channel Created" })
.setDescription(`Channel ${channel} was created`)
.addFields({
name: "Channel Type",
value: channelType,
}, {
name: "Executor",
value: executor ? executor.toString() : "Unknown",
})
.setTimestamp()
.setFooter({ text: `ID: ${channel.id}` });
logChannel.safeSend({ embeds: [embed] });
};
30 changes: 30 additions & 0 deletions src/events/channel/channelDelete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const { getSettings } = require("@schemas/Guild");
const { EmbedBuilder, AuditLogEvent } = require("discord.js");
/**
* @param {import('@src/structures').BotClient} client
* @param {import("discord.js").GuildBasedChannel} channel
*/
module.exports = async (client, channel) => {
const settings = await getSettings(channel.guild);
if (!settings.logging?.channels) return;
const logChannel = client.channels.cache.get(settings.logging.channels);
if (!logChannel) return;
const auditLog = await channel.guild.fetchAuditLogs({ type: AuditLogEvent.ChannelDelete, limit: 1 });
const entry = auditLog.entries.first();
const executor = entry.target.id === channel.id ? entry.executor : null;
const channelType = require("@helpers/channelTypes")(channel.type);
const embed = new EmbedBuilder()
.setAuthor({ name: "Channel Deleted" })
.setDescription(`Channel \`${channel.name}\` was Deleted`)
.addFields({
name: "Channel Type",
value: channelType,
}, {
name: "Executor",
value: executor ? executor.toString() : "Unknown",
})
.setTimestamp()
.setColor("Red")
.setFooter({ text: `ID: ${channel.id}` });
logChannel.safeSend({ embeds: [embed] });
};
Loading