Skip to content

Commit 3312ccb

Browse files
Copilotzerebos
andauthored
feat: make action log events individually configurable
Agent-Logs-Url: https://github.com/BetterDiscord/BetterDiscordBot/sessions/7f9c7049-cf46-4e7c-b033-be83da127060 Co-authored-by: zerebos <6865942+zerebos@users.noreply.github.com>
1 parent 9db9e57 commit 3312ccb

3 files changed

Lines changed: 85 additions & 33 deletions

File tree

src/commands/moderation.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
import {ChannelType, ChatInputCommandInteraction, InteractionContextType, PermissionFlagsBits, SlashCommandBuilder} from "discord.js";
22
import {guildDB} from "../db";
33
import Messages from "../util/messages";
4+
import type {ActionLogEvent} from "../types";
5+
6+
7+
const ACTION_LOG_EVENT_CHOICES: {name: string; value: ActionLogEvent;}[] = [
8+
{name: "Message Deleted", value: "message_delete"},
9+
{name: "Message Edited", value: "message_edit"},
10+
{name: "Member Banned", value: "member_ban"},
11+
{name: "Member Unbanned", value: "member_unban"},
12+
{name: "Nickname Changed", value: "nickname_change"},
13+
{name: "Role Changes", value: "role_change"},
14+
];
415

516

617

@@ -42,6 +53,16 @@ export default {
4253
opt.setName("channel").setDescription("Where to log server action events?").setRequired(false)
4354
.addChannelTypes(ChannelType.GuildText)
4455
)
56+
)
57+
.addSubcommand(
58+
c => c.setName("actionlogevents").setDescription("Enable or disable individual action log event types.")
59+
.addStringOption(opt =>
60+
opt.setName("event").setDescription("The event type to configure.").setRequired(true)
61+
.addChoices(...ACTION_LOG_EVENT_CHOICES)
62+
)
63+
.addBooleanOption(opt =>
64+
opt.setName("enable").setDescription("Enable or disable this event type.").setRequired(false)
65+
)
4566
),
4667

4768
async execute(interaction: ChatInputCommandInteraction<"cached">) {
@@ -51,6 +72,7 @@ export default {
5172
if (command === "modlog") return await this.modlog(interaction);
5273
if (command === "joinleave") return await this.joinleave(interaction);
5374
if (command === "actionlog") return await this.actionlog(interaction);
75+
if (command === "actionlogevents") return await this.actionlogevents(interaction);
5476
},
5577

5678

@@ -128,4 +150,25 @@ export default {
128150
}
129151
await interaction.reply(Messages.success(targetChannel ? `Action log set to <#${targetChannel.id}>!` : "Action log has been unset!", {ephemeral: true}));
130152
},
153+
154+
155+
async actionlogevents(interaction: ChatInputCommandInteraction<"cached">) {
156+
const event = interaction.options.getString("event", true) as ActionLogEvent;
157+
const toEnable = interaction.options.getBoolean("enable");
158+
const current = await guildDB.get(interaction.guild.id) ?? {};
159+
160+
// No value supplied → report current state
161+
if (toEnable === null) {
162+
const isEnabled = current.actionlogEvents?.[event] !== false;
163+
const label = ACTION_LOG_EVENT_CHOICES.find(c => c.value === event)?.name ?? event;
164+
return await interaction.reply(Messages.info(`**${label}** is currently ${isEnabled ? "enabled" : "disabled"}.`, {ephemeral: true}));
165+
}
166+
167+
current.actionlogEvents ??= {};
168+
current.actionlogEvents[event] = toEnable;
169+
await guildDB.set(interaction.guild.id, current);
170+
171+
const label = ACTION_LOG_EVENT_CHOICES.find(c => c.value === event)?.name ?? event;
172+
await interaction.reply(Messages.success(`**${label}** has been ${toEnable ? "enabled" : "disabled"}.`, {ephemeral: true}));
173+
},
131174
};

src/events/actionlog.ts

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import {EmbedBuilder, Events, type Guild, type GuildBan, type GuildMember, type Message, type PartialGuildMember, type PartialMessage} from "discord.js";
22
import {guildDB} from "../db";
33
import Colors from "../util/colors";
4+
import type {ActionLogEvent} from "../types";
45

56

6-
async function getLogChannel(guild: Guild) {
7+
async function getLogChannel(guild: Guild, event: ActionLogEvent) {
78
const settings = await guildDB.get(guild.id);
89
if (!settings?.actionlog) return null;
10+
// An event is enabled unless explicitly set to false
11+
if (settings.actionlogEvents?.[event] === false) return null;
912
const channel = guild.channels.cache.get(settings.actionlog);
1013
if (!channel?.isTextBased()) return null;
1114
return channel;
@@ -19,7 +22,7 @@ export default [
1922
async execute(message: Message | PartialMessage) {
2023
if (!message.inGuild() || message.author?.bot) return;
2124

22-
const logChannel = await getLogChannel(message.guild);
25+
const logChannel = await getLogChannel(message.guild, "message_delete");
2326
if (!logChannel) return;
2427

2528
const embed = new EmbedBuilder()
@@ -44,7 +47,7 @@ export default [
4447
if (!newMessage.inGuild() || !newMessage.author || newMessage.author.bot) return;
4548
if (oldMessage.content === newMessage.content) return;
4649

47-
const logChannel = await getLogChannel(newMessage.guild);
50+
const logChannel = await getLogChannel(newMessage.guild, "message_edit");
4851
if (!logChannel) return;
4952

5053
const embed = new EmbedBuilder()
@@ -67,7 +70,7 @@ export default [
6770
name: Events.GuildBanAdd,
6871

6972
async execute(ban: GuildBan) {
70-
const logChannel = await getLogChannel(ban.guild);
73+
const logChannel = await getLogChannel(ban.guild, "member_ban");
7174
if (!logChannel) return;
7275

7376
const embed = new EmbedBuilder()
@@ -88,7 +91,7 @@ export default [
8891
name: Events.GuildBanRemove,
8992

9093
async execute(ban: GuildBan) {
91-
const logChannel = await getLogChannel(ban.guild);
94+
const logChannel = await getLogChannel(ban.guild, "member_unban");
9295
if (!logChannel) return;
9396

9497
const embed = new EmbedBuilder()
@@ -108,46 +111,49 @@ export default [
108111
name: Events.GuildMemberUpdate,
109112

110113
async execute(oldMember: GuildMember | PartialGuildMember, newMember: GuildMember) {
111-
const logChannel = await getLogChannel(newMember.guild);
112-
if (!logChannel) return;
113-
114114
// Nickname changes
115115
if (oldMember.nickname !== newMember.nickname) {
116-
const embed = new EmbedBuilder()
117-
.setColor(Colors.Info)
118-
.setTitle("Nickname Changed")
119-
.addFields(
120-
{name: "User", value: `<@${newMember.user.id}> (${newMember.user.tag})`, inline: true},
121-
{name: "Before", value: oldMember.nickname ?? "*None*", inline: true},
122-
{name: "After", value: newMember.nickname ?? "*None*", inline: true}
123-
)
124-
.setFooter({text: `User ID: ${newMember.user.id}`})
125-
.setTimestamp();
126-
127-
await logChannel.send({embeds: [embed]}).catch(console.error);
128-
}
129-
130-
// Role changes — only reliable when the old member was fully cached
131-
if (!oldMember.partial) {
132-
const addedRoles = newMember.roles.cache.filter(r => !oldMember.roles.cache.has(r.id));
133-
const removedRoles = oldMember.roles.cache.filter(r => !newMember.roles.cache.has(r.id));
134-
135-
if (addedRoles.size || removedRoles.size) {
116+
const logChannel = await getLogChannel(newMember.guild, "nickname_change");
117+
if (logChannel) {
136118
const embed = new EmbedBuilder()
137119
.setColor(Colors.Info)
138-
.setTitle("Member Roles Updated")
120+
.setTitle("Nickname Changed")
139121
.addFields(
140-
{name: "User", value: `<@${newMember.user.id}> (${newMember.user.tag})`, inline: true}
122+
{name: "User", value: `<@${newMember.user.id}> (${newMember.user.tag})`, inline: true},
123+
{name: "Before", value: oldMember.nickname ?? "*None*", inline: true},
124+
{name: "After", value: newMember.nickname ?? "*None*", inline: true}
141125
)
142126
.setFooter({text: `User ID: ${newMember.user.id}`})
143127
.setTimestamp();
144128

145-
if (addedRoles.size) embed.addFields({name: "Roles Added", value: addedRoles.map(r => `<@&${r.id}>`).join(", ")});
146-
if (removedRoles.size) embed.addFields({name: "Roles Removed", value: removedRoles.map(r => `<@&${r.id}>`).join(", ")});
147-
148129
await logChannel.send({embeds: [embed]}).catch(console.error);
149130
}
150131
}
132+
133+
// Role changes — only reliable when the old member was fully cached
134+
if (!oldMember.partial) {
135+
const addedRoles = newMember.roles.cache.filter(r => !oldMember.roles.cache.has(r.id));
136+
const removedRoles = oldMember.roles.cache.filter(r => !newMember.roles.cache.has(r.id));
137+
138+
if (addedRoles.size || removedRoles.size) {
139+
const logChannel = await getLogChannel(newMember.guild, "role_change");
140+
if (logChannel) {
141+
const embed = new EmbedBuilder()
142+
.setColor(Colors.Info)
143+
.setTitle("Member Roles Updated")
144+
.addFields(
145+
{name: "User", value: `<@${newMember.user.id}> (${newMember.user.tag})`, inline: true}
146+
)
147+
.setFooter({text: `User ID: ${newMember.user.id}`})
148+
.setTimestamp();
149+
150+
if (addedRoles.size) embed.addFields({name: "Roles Added", value: addedRoles.map(r => `<@&${r.id}>`).join(", ")});
151+
if (removedRoles.size) embed.addFields({name: "Roles Removed", value: removedRoles.map(r => `<@&${r.id}>`).join(", ")});
152+
153+
await logChannel.send({embeds: [embed]}).catch(console.error);
154+
}
155+
}
156+
}
151157
},
152158
},
153159
];

src/types/base.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,12 @@ export interface GuildSettings {
4343
modlog?: string;
4444
joinleave?: string;
4545
actionlog?: string;
46+
actionlogEvents?: Partial<Record<ActionLogEvent, boolean>>;
4647
autoresponder?: boolean;
4748
}
4849

50+
export type ActionLogEvent = "message_delete" | "message_edit" | "member_ban" | "member_unban" | "nickname_change" | "role_change";
51+
4952
export interface ReactionRole {
5053
messageId: string;
5154
channelId: string;

0 commit comments

Comments
 (0)