Remove allowedSfxChannels functionality - allow SFX in all channels

- Remove allowedSfxChannels from database schema and all code
- Remove channel checking logic from all SFX commands (!sfx, /sfx, /soundboard)
- Remove /config sfxchannels subcommand
- Update config.json and example to remove channel restrictions
- Simplify SFX system to work in any channel with bot access

Benefits:
- Better user experience - no confusing channel restrictions
- Simpler configuration - fewer settings to manage
- Cleaner codebase - reduced complexity
- Universal access - SFX works anywhere the bot can send messages

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Chris Ham
2025-08-16 21:45:42 -07:00
parent e9b3b630b6
commit bcefe03c50
12 changed files with 218 additions and 293 deletions

View File

@@ -81,7 +81,7 @@ A modern Discord bot built with Discord.js v14 that provides sound effects, text
# Production mode with Docker Compose # Production mode with Docker Compose
pnpm start pnpm start
# Production mode with local Node.js # Production mode with local Node.js
pnpm start:prod pnpm start:prod
``` ```
@@ -228,19 +228,24 @@ pnpm image:run
The bot uses **SQLite database** for persistent guild configurations. Configuration can be managed in three ways: The bot uses **SQLite database** for persistent guild configurations. Configuration can be managed in three ways:
#### 1. Automatic Registration (Recommended for Public Bot) #### 1. Automatic Registration (Recommended for Public Bot)
When the bot is added to a new server, it automatically: When the bot is added to a new server, it automatically:
- Creates default configuration with sensible settings - Creates default configuration with sensible settings
- Sends a welcome message explaining features - Sends a welcome message explaining features
- Registers slash commands for the server - Registers slash commands for the server
#### 2. Live Configuration via Slash Commands #### 2. Live Configuration via Slash Commands
Administrators can use `/config` commands to modify settings in real-time: Administrators can use `/config` commands to modify settings in real-time:
- `/config show` - View current server settings - `/config show` - View current server settings
- `/config prefix <prefix>` - Change command prefix - `/config prefix <prefix>` - Change command prefix
- `/config sfx <true/false>` - Enable/disable sound effects - `/config sfx <true/false>` - Enable/disable sound effects
- And more (see Configuration Management section above) - And more (see Configuration Management section above)
#### 3. Seed Data from config.json (Optional) #### 3. Seed Data from config.json (Optional)
For initial deployment or migrating existing servers, create `config.json`: For initial deployment or migrating existing servers, create `config.json`:
```json ```json
@@ -256,7 +261,6 @@ For initial deployment or migrating existing servers, create `config.json`:
"internalName": "My Server", "internalName": "My Server",
"prefix": "!", "prefix": "!",
"enableSfx": true, "enableSfx": true,
"allowedSfxChannels": "general|voice-chat",
"sfxVolume": 0.5, "sfxVolume": 0.5,
"enableFunFacts": true, "enableFunFacts": true,
"enableHamFacts": true, "enableHamFacts": true,
@@ -317,7 +321,7 @@ help,commands|Available commands: !sfx, !funfact, !hamfact
"pingRoleId": "ROLE_ID" "pingRoleId": "ROLE_ID"
}, },
{ {
"id": "weekly-reminder", "id": "weekly-reminder",
"schedule": "0 10 * * 1", "schedule": "0 10 * * 1",
"channelId": "CHANNEL_ID", "channelId": "CHANNEL_ID",
"message": "Happy Monday!" "message": "Happy Monday!"
@@ -326,6 +330,7 @@ help,commands|Available commands: !sfx, !funfact, !hamfact
``` ```
**Schedule Formats Supported:** **Schedule Formats Supported:**
- **Object format**: `{"hour": 9, "minute": 30, "tz": "America/Los_Angeles"}` (with timezone) - **Object format**: `{"hour": 9, "minute": 30, "tz": "America/Los_Angeles"}` (with timezone)
- **Cron format**: `"0 9 * * *"` (standard cron expression) - **Cron format**: `"0 9 * * *"` (standard cron expression)

View File

@@ -1,2 +1,3 @@
f|is for :frog: f|is for :frog:
imelly|Didn't know this was a political channel. WTF, you don't have sites where you can vomit that garbage out for the peanut gallery? Not everyone holds your same position. Why do that? Don't assume everyone is a socialist. Knock it off. I didn't join here to listen to your political BS. I am not paying a subscription to listen to you people pontificate about your political hangups with normal people. Unsubscribed, unfollowed. -iMellyGurl imelly|Didn't know this was a political channel. WTF, you don't have sites where you can vomit that garbage out for the peanut gallery? Not everyone holds your same position. Why do that? Don't assume everyone is a socialist. Knock it off. I didn't join here to listen to your political BS. I am not paying a subscription to listen to you people pontificate about your political hangups with normal people. Unsubscribed, unfollowed. -iMellyGurl
dance|*┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛ ┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛ ┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛ ┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛*

View File

@@ -10,9 +10,9 @@
"id": "GUILD ID", "id": "GUILD ID",
"prefix": "!", "prefix": "!",
"enableSfx": true, "enableSfx": true,
"allowedSfxChannels": "piped|list|of-valid-channels",
"sfxVolume": 0.5, "sfxVolume": 0.5,
"passes": 2, "passes": 2,
"allowedRolesForRequest": ["DISCORD ROLE ID"],
"enableFunFacts": true, "enableFunFacts": true,
"enableHamFacts": true, "enableHamFacts": true,
"scheduledEvents": [ "scheduledEvents": [
@@ -34,17 +34,14 @@
"id": "SECOND GUILD ID", "id": "SECOND GUILD ID",
"prefix": "!", "prefix": "!",
"enableSfx": true, "enableSfx": true,
"allowedSfxChannels": "piped|list|of-valid-channels",
"sfxVolume": 0.5, "sfxVolume": 0.5,
"passes": 2, "passes": 2,
"enableFunFacts": true, "enableFunFacts": true,
"enableHamFacts": true "enableHamFacts": true
} }
], ],
"activities": [ "activities": ["Chardee MacDennis", "The Nightman Cometh", "Charlie Work"],
"that gum you like"
],
"blacklistedUsers": ["IGNORE COMMANDS FROM THESE DISCORD USER IDS"] "blacklistedUsers": ["IGNORE COMMANDS FROM THESE DISCORD USER IDS"]
}, },
"debug": false "debug": false
} }

View File

@@ -1,10 +0,0 @@
module.exports = {
name: 'dance',
description: 'Make the bot dance!',
async execute(message, args, guildConfig) {
await message.channel.send(
'*┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛ ┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛ ┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛ ┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛*'
);
}
};

View File

@@ -1,74 +0,0 @@
module.exports = {
name: 'role',
description: 'Add or remove allowed roles',
async execute(message, args, guildConfig) {
// Check if there are allowed roles configured
if (!guildConfig.allowedRolesForRequest || guildConfig.allowedRolesForRequest.length === 0) {
return message.reply('No roles are currently allowed to be added/removed by members.');
}
// Show usage if no arguments
if (args.length === 0) {
return message.reply(
`Usage: ${guildConfig.prefix}role {add|remove} {${guildConfig.allowedRolesForRequest}}`
);
}
const action = args[0]?.toLowerCase();
const roleName = args.slice(1).join(' ');
// Validate action
if (!['add', 'remove'].includes(action)) {
return message.reply(
`You must use add/remove after the role command! *e.g. ${guildConfig.prefix}role add <rolename>*`
);
}
// Validate role name
if (!roleName) {
return message.reply(
`Usage: ${guildConfig.prefix}role {add|remove} {${guildConfig.allowedRolesForRequest}}`
);
}
// Check if role is in the allowed list
const allowedRoles = guildConfig.allowedRolesForRequest.split('|');
const roleRegex = new RegExp(guildConfig.allowedRolesForRequest, 'i');
if (!roleRegex.test(roleName)) {
return message.reply(
`**${roleName}** is not a valid role name! The roles allowed for request are: ${allowedRoles.join(', ')}`
);
}
// Find the role in the guild (case-sensitive search)
const role = message.guild.roles.cache.find(r =>
r.name.toLowerCase() === roleName.toLowerCase()
);
if (!role) {
return message.reply(`${roleName} is not a role on this server!`);
}
try {
if (action === 'add') {
await message.member.roles.add(role, 'User requested');
await message.react('👍');
console.log(`Added role ${role.name} to ${message.author.username}`);
} else if (action === 'remove') {
await message.member.roles.remove(role, 'User requested');
await message.react('👍');
console.log(`Removed role ${role.name} from ${message.author.username}`);
}
} catch (error) {
console.error(`Error managing role ${role.name}:`, error);
await message.react('⚠️');
// Send error message if we can't react
if (!message.reactions.cache.has('⚠️')) {
await message.reply('I encountered an error managing that role. Make sure I have the proper permissions!');
}
}
}
};

View File

@@ -41,14 +41,6 @@ module.exports = {
}, },
async execute(message, args, guildConfig) { async execute(message, args, guildConfig) {
// Check if SFX is allowed in this channel
if (guildConfig.allowedSfxChannels) {
const allowedChannels = new RegExp(guildConfig.allowedSfxChannels);
if (!allowedChannels.test(message.channel.name)) {
return;
}
}
const sfxName = args[0]; const sfxName = args[0];
// If no SFX specified, show the list // If no SFX specified, show the list

View File

@@ -1,15 +1,20 @@
const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits, MessageFlags } = require('discord.js'); const {
const configManager = require('../../config/config'); SlashCommandBuilder,
EmbedBuilder,
PermissionFlagsBits,
MessageFlags,
} = require("discord.js");
const configManager = require("../../config/config");
module.exports = { module.exports = {
async formatAllowedRoles(guild, guildConfig) { async formatAllowedRoles(guild, guildConfig) {
const databaseService = configManager.databaseService; const databaseService = configManager.databaseService;
if (!databaseService) return 'Database unavailable'; if (!databaseService) return "Database unavailable";
const allowedRoleIds = databaseService.getAllowedRoleIds(guild.id); const allowedRoleIds = databaseService.getAllowedRoleIds(guild.id);
if (allowedRoleIds.length === 0) { if (allowedRoleIds.length === 0) {
return 'None configured'; return "None configured";
} }
const roles = []; const roles = [];
@@ -22,99 +27,104 @@ module.exports = {
} }
} }
return roles.length > 0 ? roles.join(', ') : 'None configured'; return roles.length > 0 ? roles.join(", ") : "None configured";
}, },
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('config') .setName("config")
.setDescription('Manage server configuration') .setDescription("Manage server configuration")
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator) .setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.addSubcommand(subcommand => .addSubcommand((subcommand) =>
subcommand subcommand
.setName('show') .setName("show")
.setDescription('Show current server configuration') .setDescription("Show current server configuration")
) )
.addSubcommand(subcommand => .addSubcommand((subcommand) =>
subcommand subcommand
.setName('prefix') .setName("prefix")
.setDescription('Set the command prefix') .setDescription("Set the command prefix")
.addStringOption(option => .addStringOption((option) =>
option.setName('new_prefix') option
.setDescription('The new command prefix') .setName("new_prefix")
.setDescription("The new command prefix")
.setRequired(true) .setRequired(true)
.setMaxLength(5) .addChoices(
{ name: "!", value: "!" },
{ name: "$", value: "$" },
{ name: "%", value: "%" },
{ name: "^", value: "^" },
{ name: "&", value: "&" }
)
) )
) )
.addSubcommand(subcommand => .addSubcommand((subcommand) =>
subcommand subcommand
.setName('sfx') .setName("sfx")
.setDescription('Enable or disable sound effects') .setDescription("Enable or disable sound effects")
.addBooleanOption(option => .addBooleanOption((option) =>
option.setName('enabled') option
.setDescription('Enable sound effects') .setName("enabled")
.setDescription("Enable sound effects")
.setRequired(true) .setRequired(true)
) )
) )
.addSubcommand(subcommand => .addSubcommand((subcommand) =>
subcommand subcommand
.setName('volume') .setName("volume")
.setDescription('Set sound effects volume') .setDescription("Set sound effects volume")
.addNumberOption(option => .addNumberOption((option) =>
option.setName('level') option
.setDescription('Volume level (0.1 to 1.0)') .setName("level")
.setDescription("Volume level (0.1 to 1.0)")
.setRequired(true) .setRequired(true)
.setMinValue(0.1) .setMinValue(0.1)
.setMaxValue(1.0) .setMaxValue(1.0)
) )
) )
.addSubcommand(subcommand => .addSubcommand((subcommand) =>
subcommand subcommand
.setName('funfacts') .setName("funfacts")
.setDescription('Enable or disable fun facts') .setDescription("Enable or disable fun facts")
.addBooleanOption(option => .addBooleanOption((option) =>
option.setName('enabled') option
.setDescription('Enable fun facts') .setName("enabled")
.setDescription("Enable fun facts")
.setRequired(true) .setRequired(true)
) )
) )
.addSubcommand(subcommand => .addSubcommand((subcommand) =>
subcommand subcommand
.setName('hamfacts') .setName("hamfacts")
.setDescription('Enable or disable ham facts') .setDescription("Enable or disable ham facts")
.addBooleanOption(option => .addBooleanOption((option) =>
option.setName('enabled') option
.setDescription('Enable ham facts') .setName("enabled")
.setDescription("Enable ham facts")
.setRequired(true) .setRequired(true)
) )
) )
.addSubcommand(subcommand => .addSubcommand((subcommand) =>
subcommand subcommand
.setName('sfxchannels') .setName("roles")
.setDescription('Set allowed channels for sound effects (regex pattern)') .setDescription("Manage self-assignable roles")
.addStringOption(option => .addStringOption((option) =>
option.setName('pattern') option
.setDescription('Channel name pattern (leave empty to allow all channels)') .setName("action")
.setRequired(false) .setDescription("Action to perform")
)
)
.addSubcommand(subcommand =>
subcommand
.setName('roles')
.setDescription('Manage self-assignable roles')
.addStringOption(option =>
option.setName('action')
.setDescription('Action to perform')
.setRequired(true) .setRequired(true)
.addChoices( .addChoices(
{ name: 'Add role to list', value: 'add' }, { name: "Add role to list", value: "add" },
{ name: 'Remove role from list', value: 'remove' }, { name: "Remove role from list", value: "remove" },
{ name: 'Clear all roles', value: 'clear' }, { name: "Clear all roles", value: "clear" },
{ name: 'Show current roles', value: 'list' } { name: "Show current roles", value: "list" }
) )
) )
.addStringOption(option => .addStringOption((option) =>
option.setName('role') option
.setDescription('The role to add or remove (not needed for list/clear)') .setName("role")
.setDescription(
"The role to add or remove (not needed for list/clear)"
)
.setRequired(false) .setRequired(false)
.setAutocomplete(true) .setAutocomplete(true)
) )
@@ -125,84 +135,100 @@ module.exports = {
const databaseService = configManager.databaseService; const databaseService = configManager.databaseService;
if (!databaseService) { if (!databaseService) {
return interaction.reply({ return interaction.reply({
content: '❌ Database service not available.', content: "❌ Database service not available.",
flags: [MessageFlags.Ephemeral] flags: [MessageFlags.Ephemeral],
}); });
} }
if (subcommand === 'show') { if (subcommand === "show") {
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setTitle(`⚙️ Configuration for ${interaction.guild.name}`) .setTitle(`⚙️ Configuration for ${interaction.guild.name}`)
.setColor(0x21c629) .setColor(0x21c629)
.addFields([ .addFields([
{ name: 'Prefix', value: `\`${guildConfig.prefix}\``, inline: true }, { name: "Prefix", value: `\`${guildConfig.prefix}\``, inline: true },
{ name: 'SFX Enabled', value: guildConfig.enableSfx ? '✅ Yes' : '❌ No', inline: true }, {
{ name: 'SFX Volume', value: `${Math.round(guildConfig.sfxVolume * 100)}%`, inline: true }, name: "SFX Enabled",
{ name: 'Fun Facts', value: guildConfig.enableFunFacts ? '✅ Enabled' : '❌ Disabled', inline: true }, value: guildConfig.enableSfx ? "✅ Yes" : "❌ No",
{ name: 'Ham Facts', value: guildConfig.enableHamFacts ? '✅ Enabled' : '❌ Disabled', inline: true }, inline: true,
{ name: 'Allowed SFX Channels', value: guildConfig.allowedSfxChannels || 'All channels', inline: false }, },
{ name: 'Self-Assignable Roles', value: await this.formatAllowedRoles(interaction.guild, guildConfig), inline: false }, {
name: "SFX Volume",
value: `${Math.round(guildConfig.sfxVolume * 100)}%`,
inline: true,
},
{
name: "Fun Facts",
value: guildConfig.enableFunFacts ? "✅ Enabled" : "❌ Disabled",
inline: true,
},
{
name: "Ham Facts",
value: guildConfig.enableHamFacts ? "✅ Enabled" : "❌ Disabled",
inline: true,
},
{
name: "Self-Assignable Roles",
value: await this.formatAllowedRoles(
interaction.guild,
guildConfig
),
inline: false,
},
]) ])
.setFooter({ text: 'Use /config commands to modify settings' }); .setFooter({ text: "Use /config commands to modify settings" });
return interaction.reply({ embeds: [embed] }); return interaction.reply({ embeds: [embed] });
} }
// Handle configuration updates // Handle configuration updates
const newConfig = { ...guildConfig }; const newConfig = { ...guildConfig };
let updateMessage = ''; let updateMessage = "";
switch (subcommand) { switch (subcommand) {
case 'prefix': case "prefix":
const newPrefix = interaction.options.getString('new_prefix'); const newPrefix = interaction.options.getString("new_prefix");
newConfig.prefix = newPrefix; newConfig.prefix = newPrefix;
updateMessage = `Command prefix updated to \`${newPrefix}\``; updateMessage = `Command prefix updated to \`${newPrefix}\``;
break; break;
case 'sfx': case "sfx":
const sfxEnabled = interaction.options.getBoolean('enabled'); const sfxEnabled = interaction.options.getBoolean("enabled");
newConfig.enableSfx = sfxEnabled; newConfig.enableSfx = sfxEnabled;
updateMessage = `Sound effects ${sfxEnabled ? 'enabled' : 'disabled'}`; updateMessage = `Sound effects ${sfxEnabled ? "enabled" : "disabled"}`;
break; break;
case 'volume': case "volume":
const volume = interaction.options.getNumber('level'); const volume = interaction.options.getNumber("level");
newConfig.sfxVolume = volume; newConfig.sfxVolume = volume;
updateMessage = `SFX volume set to ${Math.round(volume * 100)}%`; updateMessage = `SFX volume set to ${Math.round(volume * 100)}%`;
break; break;
case 'funfacts': case "funfacts":
const funfactsEnabled = interaction.options.getBoolean('enabled'); const funfactsEnabled = interaction.options.getBoolean("enabled");
newConfig.enableFunFacts = funfactsEnabled; newConfig.enableFunFacts = funfactsEnabled;
updateMessage = `Fun facts ${funfactsEnabled ? 'enabled' : 'disabled'}`; updateMessage = `Fun facts ${funfactsEnabled ? "enabled" : "disabled"}`;
break; break;
case 'hamfacts': case "hamfacts":
const hamfactsEnabled = interaction.options.getBoolean('enabled'); const hamfactsEnabled = interaction.options.getBoolean("enabled");
newConfig.enableHamFacts = hamfactsEnabled; newConfig.enableHamFacts = hamfactsEnabled;
updateMessage = `Ham facts ${hamfactsEnabled ? 'enabled' : 'disabled'}`; updateMessage = `Ham facts ${hamfactsEnabled ? "enabled" : "disabled"}`;
break; break;
case 'sfxchannels': case "roles":
const channelPattern = interaction.options.getString('pattern'); const action = interaction.options.getString("action");
newConfig.allowedSfxChannels = channelPattern || null; const roleName = interaction.options.getString("role");
updateMessage = channelPattern
? `SFX channels restricted to pattern: \`${channelPattern}\`` if (action === "list") {
: 'SFX allowed in all channels'; const allowedRoleIds = databaseService.getAllowedRoleIds(
break; interaction.guild.id
);
case 'roles':
const action = interaction.options.getString('action');
const roleName = interaction.options.getString('role');
if (action === 'list') {
const allowedRoleIds = databaseService.getAllowedRoleIds(interaction.guild.id);
if (allowedRoleIds.length === 0) { if (allowedRoleIds.length === 0) {
return interaction.reply({ return interaction.reply({
content: '❌ No self-assignable roles are currently configured.', content: "❌ No self-assignable roles are currently configured.",
flags: [MessageFlags.Ephemeral] flags: [MessageFlags.Ephemeral],
}); });
} }
@@ -213,66 +239,79 @@ module.exports = {
const roleObj = await interaction.guild.roles.fetch(roleId); const roleObj = await interaction.guild.roles.fetch(roleId);
if (roleObj) roles.push(roleObj); if (roleObj) roles.push(roleObj);
} catch (error) { } catch (error) {
console.warn(`Role ${roleId} not found in guild ${interaction.guild.id}`); console.warn(
`Role ${roleId} not found in guild ${interaction.guild.id}`
);
} }
} }
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setTitle('📋 Self-Assignable Roles Configuration') .setTitle("📋 Self-Assignable Roles Configuration")
.setDescription(roles.length > 0 ? 'Currently configured roles:' : 'No valid roles found.') .setDescription(
roles.length > 0
? "Currently configured roles:"
: "No valid roles found."
)
.setColor(0x21c629) .setColor(0x21c629)
.addFields(roles.length > 0 ? { .addFields(
name: 'Allowed Roles', roles.length > 0
value: roles.map(r => `${r}`).join('\n'), ? {
inline: false name: "Allowed Roles",
} : { value: roles.map((r) => `${r}`).join("\n"),
name: 'Status', inline: false,
value: 'No roles configured or all configured roles have been deleted.', }
inline: false : {
}); name: "Status",
value:
"No roles configured or all configured roles have been deleted.",
inline: false,
}
);
return interaction.reply({ embeds: [embed] }); return interaction.reply({ embeds: [embed] });
} }
if (action === 'clear') { if (action === "clear") {
databaseService.updateAllowedRoleIds(interaction.guild.id, []); databaseService.updateAllowedRoleIds(interaction.guild.id, []);
updateMessage = 'Self-assignable roles list cleared'; updateMessage = "Self-assignable roles list cleared";
updated = true; updated = true;
break; break;
} }
if (!roleName) { if (!roleName) {
return interaction.reply({ return interaction.reply({
content: '❌ You must specify a role for add/remove actions.', content: "❌ You must specify a role for add/remove actions.",
flags: [MessageFlags.Ephemeral] flags: [MessageFlags.Ephemeral],
}); });
} }
// Find the role by name // Find the role by name
const role = interaction.guild.roles.cache.find(r => const role = interaction.guild.roles.cache.find(
r.name.toLowerCase() === roleName.toLowerCase() (r) => r.name.toLowerCase() === roleName.toLowerCase()
); );
if (!role) { if (!role) {
return interaction.reply({ return interaction.reply({
content: `❌ Role **${roleName}** not found on this server.`, content: `❌ Role **${roleName}** not found on this server.`,
flags: [MessageFlags.Ephemeral] flags: [MessageFlags.Ephemeral],
}); });
} }
// Check if bot can manage this role // Check if bot can manage this role
if (!interaction.guild.members.me.permissions.has('ManageRoles') || if (
role.position >= interaction.guild.members.me.roles.highest.position) { !interaction.guild.members.me.permissions.has("ManageRoles") ||
role.position >= interaction.guild.members.me.roles.highest.position
) {
return interaction.reply({ return interaction.reply({
content: `❌ I cannot manage the **${role.name}** role due to permission hierarchy.`, content: `❌ I cannot manage the **${role.name}** role due to permission hierarchy.`,
flags: [MessageFlags.Ephemeral] flags: [MessageFlags.Ephemeral],
}); });
} }
if (action === 'add') { if (action === "add") {
databaseService.addAllowedRole(interaction.guild.id, role.id); databaseService.addAllowedRole(interaction.guild.id, role.id);
updateMessage = `Added **${role.name}** to self-assignable roles`; updateMessage = `Added **${role.name}** to self-assignable roles`;
} else if (action === 'remove') { } else if (action === "remove") {
databaseService.removeAllowedRole(interaction.guild.id, role.id); databaseService.removeAllowedRole(interaction.guild.id, role.id);
updateMessage = `Removed **${role.name}** from self-assignable roles`; updateMessage = `Removed **${role.name}** from self-assignable roles`;
} }
@@ -280,64 +319,69 @@ module.exports = {
// Don't set updated = true here since we're calling database methods directly // Don't set updated = true here since we're calling database methods directly
// Skip the upsertGuildConfig call at the end // Skip the upsertGuildConfig call at the end
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setTitle('✅ Configuration Updated') .setTitle("✅ Configuration Updated")
.setColor(0x00ff00) .setColor(0x00ff00)
.setDescription(updateMessage) .setDescription(updateMessage)
.setFooter({ text: 'Use /config show to see all settings' }); .setFooter({ text: "Use /config show to see all settings" });
await interaction.reply({ embeds: [embed] }); await interaction.reply({ embeds: [embed] });
console.log(`Role configuration updated for ${interaction.guild.name}: ${action} ${role.name}`); console.log(
`Role configuration updated for ${interaction.guild.name}: ${action} ${role.name}`
);
return; return;
} }
// Update configuration in database // Update configuration in database
databaseService.upsertGuildConfig(newConfig); databaseService.upsertGuildConfig(newConfig);
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setTitle('✅ Configuration Updated') .setTitle("✅ Configuration Updated")
.setColor(0x00ff00) .setColor(0x00ff00)
.setDescription(updateMessage) .setDescription(updateMessage)
.setFooter({ text: 'Use /config show to see all settings' }); .setFooter({ text: "Use /config show to see all settings" });
await interaction.reply({ embeds: [embed] }); await interaction.reply({ embeds: [embed] });
console.log(`Configuration updated for ${interaction.guild.name}: ${subcommand} by @${interaction.user.username}`); console.log(
`Configuration updated for ${interaction.guild.name}: ${subcommand} by @${interaction.user.username}`
);
}, },
async autocomplete(interaction, guildConfig) { async autocomplete(interaction, guildConfig) {
const subcommand = interaction.options.getSubcommand(); const subcommand = interaction.options.getSubcommand();
// Only handle autocomplete for roles subcommand // Only handle autocomplete for roles subcommand
if (subcommand !== 'roles') { if (subcommand !== "roles") {
return interaction.respond([]); return interaction.respond([]);
} }
const action = interaction.options.getString('action'); const action = interaction.options.getString("action");
const focusedValue = interaction.options.getFocused().toLowerCase(); const focusedValue = interaction.options.getFocused().toLowerCase();
const databaseService = configManager.databaseService; const databaseService = configManager.databaseService;
if (!databaseService) { if (!databaseService) {
return interaction.respond([]); return interaction.respond([]);
} }
// Get currently allowed role IDs // Get currently allowed role IDs
const allowedRoleIds = databaseService.getAllowedRoleIds(interaction.guild.id); const allowedRoleIds = databaseService.getAllowedRoleIds(
interaction.guild.id
);
let availableRoles = []; let availableRoles = [];
if (action === 'add') { if (action === "add") {
// For add: show roles NOT currently in the allowed list // For add: show roles NOT currently in the allowed list
const allRoles = interaction.guild.roles.cache const allRoles = interaction.guild.roles.cache.filter(
.filter(role => (role) =>
!role.managed && // Exclude bot/integration roles !role.managed && // Exclude bot/integration roles
role.id !== interaction.guild.id && // Exclude @everyone role.id !== interaction.guild.id && // Exclude @everyone
!allowedRoleIds.includes(role.id) && // Exclude already allowed roles !allowedRoleIds.includes(role.id) && // Exclude already allowed roles
role.position < interaction.guild.members.me.roles.highest.position // Only manageable roles role.position < interaction.guild.members.me.roles.highest.position // Only manageable roles
); );
availableRoles = Array.from(allRoles.values()); availableRoles = Array.from(allRoles.values());
} else if (action === "remove") {
} else if (action === 'remove') {
// For remove: show roles currently in the allowed list // For remove: show roles currently in the allowed list
for (const roleId of allowedRoleIds) { for (const roleId of allowedRoleIds) {
try { try {
@@ -353,13 +397,13 @@ module.exports = {
// Filter based on what user has typed and limit to 25 // Filter based on what user has typed and limit to 25
const filtered = availableRoles const filtered = availableRoles
.filter(role => role.name.toLowerCase().includes(focusedValue)) .filter((role) => role.name.toLowerCase().includes(focusedValue))
.slice(0, 25) .slice(0, 25)
.map(role => ({ .map((role) => ({
name: role.name, name: role.name,
value: role.name value: role.name,
})); }));
await interaction.respond(filtered); await interaction.respond(filtered);
} },
}; };

View File

@@ -14,17 +14,6 @@ module.exports = {
), ),
async execute(interaction, guildConfig) { async execute(interaction, guildConfig) {
// Check if SFX is allowed in this channel
if (guildConfig.allowedSfxChannels) {
const allowedChannels = new RegExp(guildConfig.allowedSfxChannels);
if (!allowedChannels.test(interaction.channel.name)) {
return interaction.reply({
content: 'Sound effects are not allowed in this channel!',
flags: [MessageFlags.Ephemeral]
});
}
}
const sfxName = interaction.options.getString('sound'); const sfxName = interaction.options.getString('sound');
// Check if user is in a voice channel // Check if user is in a voice channel

View File

@@ -62,17 +62,6 @@ module.exports = {
.setDescription("Interactive soundboard with categorized buttons"), .setDescription("Interactive soundboard with categorized buttons"),
async execute(interaction, guildConfig) { async execute(interaction, guildConfig) {
// Check if SFX is allowed in this channel
if (guildConfig.allowedSfxChannels) {
const allowedChannels = new RegExp(guildConfig.allowedSfxChannels);
if (!allowedChannels.test(interaction.channel.name)) {
return interaction.reply({
content: "Sound effects are not allowed in this channel!",
flags: [MessageFlags.Ephemeral],
});
}
}
// Check if user is in a voice channel // Check if user is in a voice channel
if (!interaction.member.voice.channel) { if (!interaction.member.voice.channel) {
return interaction.reply({ return interaction.reply({

View File

@@ -102,7 +102,6 @@ class ConfigManager {
internalName: "Unknown Guild", internalName: "Unknown Guild",
prefix: "!", prefix: "!",
enableSfx: true, enableSfx: true,
allowedSfxChannels: null,
sfxVolume: 0.5, sfxVolume: 0.5,
enableFunFacts: true, enableFunFacts: true,
enableHamFacts: true, enableHamFacts: true,

View File

@@ -269,7 +269,6 @@ Use \`/config show\` to view all settings or \`/config\` commands to modify them
internalName: guild.name, internalName: guild.name,
prefix: "!", prefix: "!",
enableSfx: true, enableSfx: true,
allowedSfxChannels: null, // Allow in all channels by default
sfxVolume: 0.5, sfxVolume: 0.5,
enableFunFacts: true, enableFunFacts: true,
enableHamFacts: true, enableHamFacts: true,

View File

@@ -25,7 +25,6 @@ class DatabaseService {
internal_name TEXT, internal_name TEXT,
prefix TEXT DEFAULT '!', prefix TEXT DEFAULT '!',
enable_sfx BOOLEAN DEFAULT true, enable_sfx BOOLEAN DEFAULT true,
allowed_sfx_channels TEXT,
sfx_volume REAL DEFAULT 0.5, sfx_volume REAL DEFAULT 0.5,
enable_fun_facts BOOLEAN DEFAULT true, enable_fun_facts BOOLEAN DEFAULT true,
enable_ham_facts BOOLEAN DEFAULT true, enable_ham_facts BOOLEAN DEFAULT true,
@@ -69,7 +68,7 @@ class DatabaseService {
('bot_name', 'GHBot'), ('bot_name', 'GHBot'),
('debug', 'false'), ('debug', 'false'),
('admin_user_id', ''), ('admin_user_id', ''),
('activities', '["Playing sounds", "Serving facts"]'), ('activities', '["Chardee MacDennis", "The Nightman Cometh", "Charlie Work"]'),
('blacklisted_users', '[]') ('blacklisted_users', '[]')
`); `);
@@ -144,7 +143,6 @@ class DatabaseService {
internalName: guild.internalName || guild.name || "Unknown Guild", internalName: guild.internalName || guild.name || "Unknown Guild",
prefix: guild.prefix || "!", prefix: guild.prefix || "!",
enableSfx: guild.enableSfx !== false, enableSfx: guild.enableSfx !== false,
allowedSfxChannels: guild.allowedSfxChannels || null,
sfxVolume: guild.sfxVolume || 0.5, sfxVolume: guild.sfxVolume || 0.5,
enableFunFacts: guild.enableFunFacts !== false, enableFunFacts: guild.enableFunFacts !== false,
enableHamFacts: guild.enableHamFacts !== false, enableHamFacts: guild.enableHamFacts !== false,
@@ -234,14 +232,14 @@ class DatabaseService {
), ),
insertGuild: this.db.prepare(` insertGuild: this.db.prepare(`
INSERT OR REPLACE INTO guilds INSERT OR REPLACE INTO guilds
(id, name, internal_name, prefix, enable_sfx, allowed_sfx_channels, sfx_volume, (id, name, internal_name, prefix, enable_sfx, sfx_volume,
enable_fun_facts, enable_ham_facts, allowed_roles_for_request) enable_fun_facts, enable_ham_facts, allowed_roles_for_request)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`), `),
updateGuild: this.db.prepare(` updateGuild: this.db.prepare(`
UPDATE guilds SET UPDATE guilds SET
name = ?, internal_name = ?, prefix = ?, enable_sfx = ?, name = ?, internal_name = ?, prefix = ?, enable_sfx = ?,
allowed_sfx_channels = ?, sfx_volume = ?, enable_fun_facts = ?, sfx_volume = ?, enable_fun_facts = ?,
enable_ham_facts = ?, allowed_roles_for_request = ?, updated_at = CURRENT_TIMESTAMP enable_ham_facts = ?, allowed_roles_for_request = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND is_active = true WHERE id = ? AND is_active = true
`), `),
@@ -294,7 +292,6 @@ class DatabaseService {
internalName: guild.internal_name, internalName: guild.internal_name,
prefix: guild.prefix, prefix: guild.prefix,
enableSfx: Boolean(guild.enable_sfx), enableSfx: Boolean(guild.enable_sfx),
allowedSfxChannels: guild.allowed_sfx_channels,
sfxVolume: guild.sfx_volume, sfxVolume: guild.sfx_volume,
enableFunFacts: Boolean(guild.enable_fun_facts), enableFunFacts: Boolean(guild.enable_fun_facts),
enableHamFacts: Boolean(guild.enable_ham_facts), enableHamFacts: Boolean(guild.enable_ham_facts),
@@ -316,7 +313,6 @@ class DatabaseService {
internalName: guild.internal_name, internalName: guild.internal_name,
prefix: guild.prefix, prefix: guild.prefix,
enableSfx: Boolean(guild.enable_sfx), enableSfx: Boolean(guild.enable_sfx),
allowedSfxChannels: guild.allowed_sfx_channels,
sfxVolume: guild.sfx_volume, sfxVolume: guild.sfx_volume,
enableFunFacts: Boolean(guild.enable_fun_facts), enableFunFacts: Boolean(guild.enable_fun_facts),
enableHamFacts: Boolean(guild.enable_ham_facts), enableHamFacts: Boolean(guild.enable_ham_facts),
@@ -365,7 +361,6 @@ class DatabaseService {
guildConfig.internalName || guildConfig.name, guildConfig.internalName || guildConfig.name,
guildConfig.prefix || "!", guildConfig.prefix || "!",
guildConfig.enableSfx !== false ? 1 : 0, guildConfig.enableSfx !== false ? 1 : 0,
guildConfig.allowedSfxChannels || null,
guildConfig.sfxVolume || 0.5, guildConfig.sfxVolume || 0.5,
guildConfig.enableFunFacts !== false ? 1 : 0, guildConfig.enableFunFacts !== false ? 1 : 0,
guildConfig.enableHamFacts !== false ? 1 : 0, guildConfig.enableHamFacts !== false ? 1 : 0,
@@ -418,7 +413,6 @@ class DatabaseService {
internalName: guild.internal_name, internalName: guild.internal_name,
prefix: guild.prefix, prefix: guild.prefix,
enableSfx: Boolean(guild.enable_sfx), enableSfx: Boolean(guild.enable_sfx),
allowedSfxChannels: guild.allowed_sfx_channels,
sfxVolume: guild.sfx_volume, sfxVolume: guild.sfx_volume,
enableFunFacts: Boolean(guild.enable_fun_facts), enableFunFacts: Boolean(guild.enable_fun_facts),
enableHamFacts: Boolean(guild.enable_ham_facts), enableHamFacts: Boolean(guild.enable_ham_facts),