Modernize role management system with slash commands and role IDs
- Convert role management from prefix to slash commands (/role add/remove/list) - Update database schema to store role IDs as JSON arrays instead of regex patterns - Add /config roles command for administrators to manage allowed roles - Simplify database schema by reusing allowed_roles_for_request field as JSON - Add database reset script (pnpm reset-db) for easy testing and migration - Update config format to only support array format (no backward compatibility) Role Management Features: - /role add <role> - Self-assign roles with dropdown selection - /role remove <role> - Remove roles with dropdown selection - /role list - Show available self-assignable roles - /config roles add/remove/list/clear - Administrator role management Technical Improvements: - Role ID based matching (more reliable than name-based regex) - Type-safe role selection with Discord's native role picker - Permission hierarchy validation - Rich embed responses with proper error handling - Ephemeral responses for clean chat experience 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,7 @@
|
|||||||
"restart": "docker compose restart",
|
"restart": "docker compose restart",
|
||||||
"logs": "docker compose logs -f",
|
"logs": "docker compose logs -f",
|
||||||
"boom": "pnpm stop && pnpm build && pnpm start",
|
"boom": "pnpm stop && pnpm build && pnpm start",
|
||||||
|
"reset-db": "pnpm stop && rm -f data/ghbot.db data/ghbot.db-shm data/ghbot.db-wal && echo 'Database reset complete. Run pnpm start to re-seed from config.json'",
|
||||||
"image:build": "docker build -t ghbot:${VERSION:-latest} .",
|
"image:build": "docker build -t ghbot:${VERSION:-latest} .",
|
||||||
"image:run": "docker run -d --name ghbot --restart always ghbot:${VERSION:-latest}"
|
"image:run": "docker run -d --name ghbot --restart always ghbot:${VERSION:-latest}"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,6 +2,29 @@ const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits, MessageFlags } =
|
|||||||
const configManager = require('../../config/config');
|
const configManager = require('../../config/config');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
async formatAllowedRoles(guild, guildConfig) {
|
||||||
|
const databaseService = configManager.databaseService;
|
||||||
|
if (!databaseService) return 'Database unavailable';
|
||||||
|
|
||||||
|
const allowedRoleIds = databaseService.getAllowedRoleIds(guild.id);
|
||||||
|
|
||||||
|
if (allowedRoleIds.length === 0) {
|
||||||
|
return 'None configured';
|
||||||
|
}
|
||||||
|
|
||||||
|
const roles = [];
|
||||||
|
for (const roleId of allowedRoleIds) {
|
||||||
|
try {
|
||||||
|
const role = await guild.roles.fetch(roleId);
|
||||||
|
if (role) roles.push(role.name);
|
||||||
|
} catch (error) {
|
||||||
|
roles.push(`<deleted role: ${roleId}>`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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')
|
||||||
@@ -77,10 +100,21 @@ module.exports = {
|
|||||||
.addSubcommand(subcommand =>
|
.addSubcommand(subcommand =>
|
||||||
subcommand
|
subcommand
|
||||||
.setName('roles')
|
.setName('roles')
|
||||||
.setDescription('Set roles that users can self-assign')
|
.setDescription('Manage self-assignable roles')
|
||||||
.addStringOption(option =>
|
.addStringOption(option =>
|
||||||
option.setName('pattern')
|
option.setName('action')
|
||||||
.setDescription('Role pattern (pipe-separated, e.g., "streamer|vip|member")')
|
.setDescription('Action to perform')
|
||||||
|
.setRequired(true)
|
||||||
|
.addChoices(
|
||||||
|
{ name: 'Add role to list', value: 'add' },
|
||||||
|
{ name: 'Remove role from list', value: 'remove' },
|
||||||
|
{ name: 'Clear all roles', value: 'clear' },
|
||||||
|
{ name: 'Show current roles', value: 'list' }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.addRoleOption(option =>
|
||||||
|
option.setName('role')
|
||||||
|
.setDescription('The role to add or remove (not needed for list/clear)')
|
||||||
.setRequired(false)
|
.setRequired(false)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@@ -107,7 +141,7 @@ module.exports = {
|
|||||||
{ name: 'Fun Facts', value: guildConfig.enableFunFacts ? '✅ Enabled' : '❌ Disabled', inline: true },
|
{ name: 'Fun Facts', value: guildConfig.enableFunFacts ? '✅ Enabled' : '❌ Disabled', inline: true },
|
||||||
{ name: 'Ham Facts', value: guildConfig.enableHamFacts ? '✅ Enabled' : '❌ Disabled', inline: true },
|
{ name: 'Ham Facts', value: guildConfig.enableHamFacts ? '✅ Enabled' : '❌ Disabled', inline: true },
|
||||||
{ name: 'Allowed SFX Channels', value: guildConfig.allowedSfxChannels || 'All channels', inline: false },
|
{ name: 'Allowed SFX Channels', value: guildConfig.allowedSfxChannels || 'All channels', inline: false },
|
||||||
{ name: 'Allowed Roles', value: guildConfig.allowedRolesForRequest || 'None configured', inline: false },
|
{ 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' });
|
||||||
|
|
||||||
@@ -158,11 +192,79 @@ module.exports = {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'roles':
|
case 'roles':
|
||||||
const rolePattern = interaction.options.getString('pattern');
|
const action = interaction.options.getString('action');
|
||||||
newConfig.allowedRolesForRequest = rolePattern || null;
|
const role = interaction.options.getRole('role');
|
||||||
updateMessage = rolePattern
|
|
||||||
? `Self-assignable roles set to: \`${rolePattern}\``
|
if (action === 'list') {
|
||||||
: 'Self-assignable roles cleared';
|
const allowedRoleIds = databaseService.getAllowedRoleIds(interaction.guild.id);
|
||||||
|
|
||||||
|
if (allowedRoleIds.length === 0) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: '❌ No self-assignable roles are currently configured.',
|
||||||
|
flags: [MessageFlags.Ephemeral]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get role objects from IDs
|
||||||
|
const roles = [];
|
||||||
|
for (const roleId of allowedRoleIds) {
|
||||||
|
try {
|
||||||
|
const roleObj = await interaction.guild.roles.fetch(roleId);
|
||||||
|
if (roleObj) roles.push(roleObj);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Role ${roleId} not found in guild ${interaction.guild.id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle('📋 Self-Assignable Roles Configuration')
|
||||||
|
.setDescription(roles.length > 0 ? 'Currently configured roles:' : 'No valid roles found.')
|
||||||
|
.setColor(0x21c629)
|
||||||
|
.addFields(roles.length > 0 ? {
|
||||||
|
name: 'Allowed Roles',
|
||||||
|
value: roles.map(r => `• ${r}`).join('\n'),
|
||||||
|
inline: false
|
||||||
|
} : {
|
||||||
|
name: 'Status',
|
||||||
|
value: 'No roles configured or all configured roles have been deleted.',
|
||||||
|
inline: false
|
||||||
|
});
|
||||||
|
|
||||||
|
return interaction.reply({ embeds: [embed] });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'clear') {
|
||||||
|
databaseService.updateAllowedRoleIds(interaction.guild.id, []);
|
||||||
|
updateMessage = 'Self-assignable roles list cleared';
|
||||||
|
updated = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!role) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: '❌ You must specify a role for add/remove actions.',
|
||||||
|
flags: [MessageFlags.Ephemeral]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if bot can manage this role
|
||||||
|
if (!interaction.guild.members.me.permissions.has('ManageRoles') ||
|
||||||
|
role.position >= interaction.guild.members.me.roles.highest.position) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: `❌ I cannot manage the **${role.name}** role due to permission hierarchy.`,
|
||||||
|
flags: [MessageFlags.Ephemeral]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'add') {
|
||||||
|
databaseService.addAllowedRole(interaction.guild.id, role.id);
|
||||||
|
updateMessage = `Added **${role.name}** to self-assignable roles`;
|
||||||
|
} else if (action === 'remove') {
|
||||||
|
databaseService.removeAllowedRole(interaction.guild.id, role.id);
|
||||||
|
updateMessage = `Removed **${role.name}** from self-assignable roles`;
|
||||||
|
}
|
||||||
|
|
||||||
|
updated = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
182
src/commands/slash/role.js
Normal file
182
src/commands/slash/role.js
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
const { SlashCommandBuilder, EmbedBuilder, MessageFlags } = require('discord.js');
|
||||||
|
const configManager = require('../../config/config');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('role')
|
||||||
|
.setDescription('Manage your roles')
|
||||||
|
.addSubcommand(subcommand =>
|
||||||
|
subcommand
|
||||||
|
.setName('add')
|
||||||
|
.setDescription('Add a role to yourself')
|
||||||
|
.addRoleOption(option =>
|
||||||
|
option.setName('role')
|
||||||
|
.setDescription('The role to add')
|
||||||
|
.setRequired(true)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.addSubcommand(subcommand =>
|
||||||
|
subcommand
|
||||||
|
.setName('remove')
|
||||||
|
.setDescription('Remove a role from yourself')
|
||||||
|
.addRoleOption(option =>
|
||||||
|
option.setName('role')
|
||||||
|
.setDescription('The role to remove')
|
||||||
|
.setRequired(true)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.addSubcommand(subcommand =>
|
||||||
|
subcommand
|
||||||
|
.setName('list')
|
||||||
|
.setDescription('Show available self-assignable roles')
|
||||||
|
),
|
||||||
|
|
||||||
|
async execute(interaction, guildConfig) {
|
||||||
|
const subcommand = interaction.options.getSubcommand();
|
||||||
|
const databaseService = configManager.databaseService;
|
||||||
|
|
||||||
|
if (!databaseService) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: '❌ Database service not available.',
|
||||||
|
flags: [MessageFlags.Ephemeral]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get allowed role IDs for this guild
|
||||||
|
const allowedRoleIds = databaseService.getAllowedRoleIds(interaction.guild.id);
|
||||||
|
|
||||||
|
if (subcommand === 'list') {
|
||||||
|
if (allowedRoleIds.length === 0) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: '❌ No roles are currently available for self-assignment on this server.',
|
||||||
|
flags: [MessageFlags.Ephemeral]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get role objects from IDs
|
||||||
|
const roles = [];
|
||||||
|
for (const roleId of allowedRoleIds) {
|
||||||
|
try {
|
||||||
|
const role = await interaction.guild.roles.fetch(roleId);
|
||||||
|
if (role) {
|
||||||
|
roles.push(role);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Role ${roleId} not found in guild ${interaction.guild.id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (roles.length === 0) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: '❌ No valid self-assignable roles found. The configured roles may have been deleted.',
|
||||||
|
flags: [MessageFlags.Ephemeral]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle('📋 Available Self-Assignable Roles')
|
||||||
|
.setDescription('You can add or remove these roles using `/role add` or `/role remove`:')
|
||||||
|
.setColor(0x21c629)
|
||||||
|
.addFields({
|
||||||
|
name: 'Available Roles',
|
||||||
|
value: roles.map(role => `• ${role}`).join('\n'),
|
||||||
|
inline: false
|
||||||
|
})
|
||||||
|
.setFooter({ text: 'Use /role add or /role remove to manage your roles' });
|
||||||
|
|
||||||
|
return interaction.reply({
|
||||||
|
embeds: [embed],
|
||||||
|
flags: [MessageFlags.Ephemeral]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle add/remove subcommands
|
||||||
|
const targetRole = interaction.options.getRole('role');
|
||||||
|
|
||||||
|
// Check if the role is in the allowed list
|
||||||
|
if (!allowedRoleIds.includes(targetRole.id)) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: `❌ **${targetRole.name}** is not available for self-assignment. Use \`/role list\` to see available roles.`,
|
||||||
|
flags: [MessageFlags.Ephemeral]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if bot can manage this role
|
||||||
|
if (!interaction.guild.members.me.permissions.has('ManageRoles') ||
|
||||||
|
targetRole.position >= interaction.guild.members.me.roles.highest.position) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: `❌ I don't have permission to manage the **${targetRole.name}** role. Please contact an administrator.`,
|
||||||
|
flags: [MessageFlags.Ephemeral]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (subcommand === 'add') {
|
||||||
|
// Check if user already has the role
|
||||||
|
if (interaction.member.roles.cache.has(targetRole.id)) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: `❌ You already have the **${targetRole.name}** role.`,
|
||||||
|
flags: [MessageFlags.Ephemeral]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.member.roles.add(targetRole, 'User requested via slash command');
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle('✅ Role Added')
|
||||||
|
.setDescription(`Successfully added the **${targetRole.name}** role to your account.`)
|
||||||
|
.setColor(0x00ff00)
|
||||||
|
.setFooter({ text: 'Use /role remove to remove roles' });
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [embed],
|
||||||
|
flags: [MessageFlags.Ephemeral]
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Added role ${targetRole.name} to ${interaction.user.username} in ${interaction.guild.name}`);
|
||||||
|
|
||||||
|
} else if (subcommand === 'remove') {
|
||||||
|
// Check if user has the role
|
||||||
|
if (!interaction.member.roles.cache.has(targetRole.id)) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: `❌ You don't have the **${targetRole.name}** role.`,
|
||||||
|
flags: [MessageFlags.Ephemeral]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.member.roles.remove(targetRole, 'User requested via slash command');
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle('✅ Role Removed')
|
||||||
|
.setDescription(`Successfully removed the **${targetRole.name}** role from your account.`)
|
||||||
|
.setColor(0x00ff00)
|
||||||
|
.setFooter({ text: 'Use /role add to add roles' });
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [embed],
|
||||||
|
flags: [MessageFlags.Ephemeral]
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Removed role ${targetRole.name} from ${interaction.user.username} in ${interaction.guild.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error managing role ${targetRole.name}:`, error);
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle('❌ Role Management Error')
|
||||||
|
.setDescription(`I encountered an error managing the **${targetRole.name}** role. Please contact an administrator.`)
|
||||||
|
.setColor(0xff0000)
|
||||||
|
.addFields({
|
||||||
|
name: 'Possible Issues',
|
||||||
|
value: '• Bot lacks Manage Roles permission\n• Role is higher than bot\'s highest role\n• Role is managed by an integration',
|
||||||
|
inline: false
|
||||||
|
});
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [embed],
|
||||||
|
flags: [MessageFlags.Ephemeral]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -20,7 +20,7 @@ class ConfigManager {
|
|||||||
discord: {
|
discord: {
|
||||||
token: process.env.DISCORD_TOKEN,
|
token: process.env.DISCORD_TOKEN,
|
||||||
adminUserId: process.env.ADMIN_USER_ID,
|
adminUserId: process.env.ADMIN_USER_ID,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +28,9 @@ class ConfigManager {
|
|||||||
|
|
||||||
// Validate required fields
|
// Validate required fields
|
||||||
if (!config.discord?.token && !process.env.DISCORD_TOKEN) {
|
if (!config.discord?.token && !process.env.DISCORD_TOKEN) {
|
||||||
throw new Error("Discord token is required in config.json or DISCORD_TOKEN environment variable");
|
throw new Error(
|
||||||
|
"Discord token is required in config.json or DISCORD_TOKEN environment variable"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
@@ -46,19 +48,30 @@ class ConfigManager {
|
|||||||
*/
|
*/
|
||||||
getBotConfig() {
|
getBotConfig() {
|
||||||
const fileConfig = this.fileConfig;
|
const fileConfig = this.fileConfig;
|
||||||
const dbConfig = this.databaseService ? this.databaseService.getBotConfiguration() : {};
|
const dbConfig = this.databaseService
|
||||||
|
? this.databaseService.getBotConfiguration()
|
||||||
|
: {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Use file config as fallback, database as primary
|
// Use file config as fallback, database as primary
|
||||||
botName: dbConfig.botName || fileConfig.botName || 'GHBot',
|
botName: dbConfig.botName || fileConfig.botName || "GHBot",
|
||||||
debug: dbConfig.debug !== undefined ? dbConfig.debug : (fileConfig.debug || false),
|
debug:
|
||||||
|
dbConfig.debug !== undefined
|
||||||
|
? dbConfig.debug
|
||||||
|
: fileConfig.debug || false,
|
||||||
discord: {
|
discord: {
|
||||||
token: fileConfig.discord?.token || process.env.DISCORD_TOKEN,
|
token: fileConfig.discord?.token || process.env.DISCORD_TOKEN,
|
||||||
adminUserId: dbConfig.adminUserId || fileConfig.discord?.adminUserId || process.env.ADMIN_USER_ID,
|
adminUserId:
|
||||||
activities: dbConfig.activities || fileConfig.discord?.activities || ['Playing sounds', 'Serving facts'],
|
dbConfig.adminUserId ||
|
||||||
blacklistedUsers: dbConfig.blacklistedUsers || fileConfig.discord?.blacklistedUsers || [],
|
fileConfig.discord?.adminUserId ||
|
||||||
master: fileConfig.discord?.master !== false, // Default to true
|
process.env.ADMIN_USER_ID,
|
||||||
}
|
activities: dbConfig.activities ||
|
||||||
|
fileConfig.discord?.activities || ["Playing sounds", "Serving facts"],
|
||||||
|
blacklistedUsers:
|
||||||
|
dbConfig.blacklistedUsers ||
|
||||||
|
fileConfig.discord?.blacklistedUsers ||
|
||||||
|
[],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,15 +92,15 @@ class ConfigManager {
|
|||||||
? this.fileConfig.discord.guilds
|
? this.fileConfig.discord.guilds
|
||||||
: Object.values(this.fileConfig.discord.guilds);
|
: Object.values(this.fileConfig.discord.guilds);
|
||||||
|
|
||||||
return guilds.find(g => g.id === guildId);
|
return guilds.find((g) => g.id === guildId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return default config for new guilds
|
// Return default config for new guilds
|
||||||
return {
|
return {
|
||||||
id: guildId,
|
id: guildId,
|
||||||
name: 'Unknown Guild',
|
name: "Unknown Guild",
|
||||||
internalName: 'Unknown Guild',
|
internalName: "Unknown Guild",
|
||||||
prefix: '!',
|
prefix: "!",
|
||||||
enableSfx: true,
|
enableSfx: true,
|
||||||
allowedSfxChannels: null,
|
allowedSfxChannels: null,
|
||||||
sfxVolume: 0.5,
|
sfxVolume: 0.5,
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
const Database = require('better-sqlite3');
|
const Database = require("better-sqlite3");
|
||||||
const path = require('path');
|
const path = require("path");
|
||||||
|
|
||||||
class DatabaseService {
|
class DatabaseService {
|
||||||
constructor() {
|
constructor() {
|
||||||
// Store database in data directory
|
// Store database in data directory
|
||||||
const dbPath = path.join(__dirname, '..', '..', 'data', 'ghbot.db');
|
const dbPath = path.join(__dirname, "..", "..", "data", "ghbot.db");
|
||||||
this.db = new Database(dbPath);
|
this.db = new Database(dbPath);
|
||||||
|
|
||||||
// Enable WAL mode for better concurrent access
|
// Enable WAL mode for better concurrent access
|
||||||
this.db.pragma('journal_mode = WAL');
|
this.db.pragma("journal_mode = WAL");
|
||||||
|
|
||||||
this.initializeTables();
|
this.initializeTables();
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ class DatabaseService {
|
|||||||
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,
|
||||||
allowed_roles_for_request TEXT,
|
allowed_roles_for_request TEXT DEFAULT '[]',
|
||||||
is_active BOOLEAN DEFAULT true,
|
is_active BOOLEAN DEFAULT true,
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
@@ -73,7 +73,7 @@ class DatabaseService {
|
|||||||
('blacklisted_users', '[]')
|
('blacklisted_users', '[]')
|
||||||
`);
|
`);
|
||||||
|
|
||||||
console.log('Database tables initialized');
|
console.log("Database tables initialized");
|
||||||
|
|
||||||
// Prepare statements after tables are created
|
// Prepare statements after tables are created
|
||||||
this.prepareStatements();
|
this.prepareStatements();
|
||||||
@@ -87,10 +87,14 @@ class DatabaseService {
|
|||||||
*/
|
*/
|
||||||
runMigrations() {
|
runMigrations() {
|
||||||
// Check if we need to seed from config file
|
// Check if we need to seed from config file
|
||||||
const guildCount = this.db.prepare('SELECT COUNT(*) as count FROM guilds').get().count;
|
const guildCount = this.db
|
||||||
|
.prepare("SELECT COUNT(*) as count FROM guilds")
|
||||||
|
.get().count;
|
||||||
|
|
||||||
if (guildCount === 0) {
|
if (guildCount === 0) {
|
||||||
console.log('No guilds found in database, checking for config file to seed...');
|
console.log(
|
||||||
|
"No guilds found in database, checking for config file to seed..."
|
||||||
|
);
|
||||||
this.seedFromConfigFile();
|
this.seedFromConfigFile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,48 +104,55 @@ class DatabaseService {
|
|||||||
*/
|
*/
|
||||||
seedFromConfigFile() {
|
seedFromConfigFile() {
|
||||||
try {
|
try {
|
||||||
const fs = require('fs');
|
const fs = require("fs");
|
||||||
const path = require('path');
|
const path = require("path");
|
||||||
|
|
||||||
const configPath = path.join(__dirname, '..', '..', 'config.json');
|
const configPath = path.join(__dirname, "..", "..", "config.json");
|
||||||
|
|
||||||
if (!fs.existsSync(configPath)) {
|
if (!fs.existsSync(configPath)) {
|
||||||
console.log('No config.json file found, skipping seed');
|
console.log("No config.json file found, skipping seed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
||||||
|
|
||||||
if (!config.discord?.guilds) {
|
if (!config.discord?.guilds) {
|
||||||
console.log('No guilds found in config.json, skipping seed');
|
console.log("No guilds found in config.json, skipping seed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle both array and object formats for backward compatibility
|
// Expect guilds to be an array
|
||||||
const guilds = Array.isArray(config.discord.guilds)
|
const guilds = config.discord.guilds;
|
||||||
? config.discord.guilds
|
|
||||||
: Object.values(config.discord.guilds);
|
if (!Array.isArray(guilds)) {
|
||||||
|
console.log("Config guilds must be an array format, skipping seed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let seededCount = 0;
|
let seededCount = 0;
|
||||||
|
|
||||||
for (const guild of guilds) {
|
for (const guild of guilds) {
|
||||||
if (!guild.id) {
|
if (!guild.id) {
|
||||||
console.warn('Skipping guild with missing ID:', guild);
|
console.warn("Skipping guild with missing ID:", guild);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert old config format to new format
|
// Convert config format to database format
|
||||||
const guildConfig = {
|
const guildConfig = {
|
||||||
id: guild.id,
|
id: guild.id,
|
||||||
name: guild.internalName || guild.name || 'Unknown Guild',
|
name: guild.internalName || guild.name || "Unknown Guild",
|
||||||
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,
|
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,
|
||||||
allowedRolesForRequest: guild.allowedRolesForRequest || null,
|
allowedRolesForRequest: Array.isArray(guild.allowedRolesForRequest)
|
||||||
|
? guild.allowedRolesForRequest.filter(
|
||||||
|
(id) => id && id.trim() !== ""
|
||||||
|
)
|
||||||
|
: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Insert guild configuration
|
// Insert guild configuration
|
||||||
@@ -152,11 +163,16 @@ class DatabaseService {
|
|||||||
for (const event of guild.scheduledEvents) {
|
for (const event of guild.scheduledEvents) {
|
||||||
if (event.id && event.schedule) {
|
if (event.id && event.schedule) {
|
||||||
try {
|
try {
|
||||||
console.log(`Importing scheduled event: ${event.id} for guild ${guild.id}`);
|
console.log(
|
||||||
|
`Importing scheduled event: ${event.id} for guild ${guild.id}`
|
||||||
|
);
|
||||||
this.addScheduledEvent(guild.id, event);
|
this.addScheduledEvent(guild.id, event);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`Skipping scheduled event ${event.id} for guild ${guild.id}:`, error.message);
|
console.warn(
|
||||||
console.warn('Event object:', JSON.stringify(event, null, 2));
|
`Skipping scheduled event ${event.id} for guild ${guild.id}:`,
|
||||||
|
error.message
|
||||||
|
);
|
||||||
|
console.warn("Event object:", JSON.stringify(event, null, 2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,29 +181,42 @@ class DatabaseService {
|
|||||||
seededCount++;
|
seededCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`✅ Successfully seeded database with ${seededCount} guild(s) from config.json`);
|
console.log(
|
||||||
|
`✅ Successfully seeded database with ${seededCount} guild(s) from config.json`
|
||||||
|
);
|
||||||
|
|
||||||
// Update bot configuration in database from file config
|
// Update bot configuration in database from file config
|
||||||
if (config.botName) {
|
if (config.botName) {
|
||||||
this.setBotConfig('bot_name', config.botName);
|
this.setBotConfig("bot_name", config.botName);
|
||||||
}
|
}
|
||||||
if (config.debug !== undefined) {
|
if (config.debug !== undefined) {
|
||||||
this.setBotConfig('debug', config.debug.toString());
|
this.setBotConfig("debug", config.debug.toString());
|
||||||
}
|
}
|
||||||
if (config.discord?.adminUserId) {
|
if (config.discord?.adminUserId) {
|
||||||
this.setBotConfig('admin_user_id', config.discord.adminUserId);
|
this.setBotConfig("admin_user_id", config.discord.adminUserId);
|
||||||
}
|
}
|
||||||
if (config.discord?.activities && Array.isArray(config.discord.activities)) {
|
if (
|
||||||
this.setBotConfig('activities', JSON.stringify(config.discord.activities));
|
config.discord?.activities &&
|
||||||
|
Array.isArray(config.discord.activities)
|
||||||
|
) {
|
||||||
|
this.setBotConfig(
|
||||||
|
"activities",
|
||||||
|
JSON.stringify(config.discord.activities)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (config.discord?.blacklistedUsers && Array.isArray(config.discord.blacklistedUsers)) {
|
if (
|
||||||
this.setBotConfig('blacklisted_users', JSON.stringify(config.discord.blacklistedUsers));
|
config.discord?.blacklistedUsers &&
|
||||||
|
Array.isArray(config.discord.blacklistedUsers)
|
||||||
|
) {
|
||||||
|
this.setBotConfig(
|
||||||
|
"blacklisted_users",
|
||||||
|
JSON.stringify(config.discord.blacklistedUsers)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ Bot configuration updated from config.json');
|
console.log("✅ Bot configuration updated from config.json");
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error seeding database from config file:', error);
|
console.error("Error seeding database from config file:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,13 +226,17 @@ class DatabaseService {
|
|||||||
prepareStatements() {
|
prepareStatements() {
|
||||||
this.statements = {
|
this.statements = {
|
||||||
// Guild operations
|
// Guild operations
|
||||||
getGuild: this.db.prepare('SELECT * FROM guilds WHERE id = ? AND is_active = true'),
|
getGuild: this.db.prepare(
|
||||||
getAllGuilds: this.db.prepare('SELECT * FROM guilds WHERE is_active = true'),
|
"SELECT * FROM guilds WHERE id = ? AND is_active = true"
|
||||||
|
),
|
||||||
|
getAllGuilds: this.db.prepare(
|
||||||
|
"SELECT * FROM guilds WHERE is_active = true"
|
||||||
|
),
|
||||||
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, allowed_sfx_channels, sfx_volume,
|
||||||
enable_fun_facts, enable_ham_facts, allowed_roles_for_request, is_active, updated_at, removed_at)
|
enable_fun_facts, enable_ham_facts, allowed_roles_for_request)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, true, CURRENT_TIMESTAMP, NULL)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
`),
|
`),
|
||||||
updateGuild: this.db.prepare(`
|
updateGuild: this.db.prepare(`
|
||||||
UPDATE guilds SET
|
UPDATE guilds SET
|
||||||
@@ -220,19 +253,25 @@ class DatabaseService {
|
|||||||
UPDATE guilds SET is_active = true, removed_at = NULL, updated_at = CURRENT_TIMESTAMP
|
UPDATE guilds SET is_active = true, removed_at = NULL, updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`),
|
`),
|
||||||
hardDeleteGuild: this.db.prepare('DELETE FROM guilds WHERE id = ?'),
|
hardDeleteGuild: this.db.prepare("DELETE FROM guilds WHERE id = ?"),
|
||||||
|
|
||||||
// Scheduled events
|
// Scheduled events
|
||||||
getScheduledEvents: this.db.prepare('SELECT * FROM scheduled_events WHERE guild_id = ? AND enabled = true'),
|
getScheduledEvents: this.db.prepare(
|
||||||
|
"SELECT * FROM scheduled_events WHERE guild_id = ? AND enabled = true"
|
||||||
|
),
|
||||||
insertScheduledEvent: this.db.prepare(`
|
insertScheduledEvent: this.db.prepare(`
|
||||||
INSERT OR REPLACE INTO scheduled_events
|
INSERT OR REPLACE INTO scheduled_events
|
||||||
(guild_id, event_id, schedule, channel_id, message, ping_role_id, enabled)
|
(guild_id, event_id, schedule, channel_id, message, ping_role_id, enabled)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
`),
|
`),
|
||||||
deleteScheduledEvent: this.db.prepare('DELETE FROM scheduled_events WHERE guild_id = ? AND event_id = ?'),
|
deleteScheduledEvent: this.db.prepare(
|
||||||
|
"DELETE FROM scheduled_events WHERE guild_id = ? AND event_id = ?"
|
||||||
|
),
|
||||||
|
|
||||||
// Bot config
|
// Bot config
|
||||||
getBotConfig: this.db.prepare('SELECT value FROM bot_config WHERE key = ?'),
|
getBotConfig: this.db.prepare(
|
||||||
|
"SELECT value FROM bot_config WHERE key = ?"
|
||||||
|
),
|
||||||
setBotConfig: this.db.prepare(`
|
setBotConfig: this.db.prepare(`
|
||||||
INSERT OR REPLACE INTO bot_config (key, value, updated_at)
|
INSERT OR REPLACE INTO bot_config (key, value, updated_at)
|
||||||
VALUES (?, ?, CURRENT_TIMESTAMP)
|
VALUES (?, ?, CURRENT_TIMESTAMP)
|
||||||
@@ -259,7 +298,9 @@ class DatabaseService {
|
|||||||
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),
|
||||||
allowedRolesForRequest: guild.allowed_roles_for_request,
|
allowedRolesForRequest: this.parseRoleIds(
|
||||||
|
guild.allowed_roles_for_request
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,7 +310,7 @@ class DatabaseService {
|
|||||||
*/
|
*/
|
||||||
getAllGuildConfigs() {
|
getAllGuildConfigs() {
|
||||||
const guilds = this.statements.getAllGuilds.all();
|
const guilds = this.statements.getAllGuilds.all();
|
||||||
return guilds.map(guild => ({
|
return guilds.map((guild) => ({
|
||||||
id: guild.id,
|
id: guild.id,
|
||||||
name: guild.name,
|
name: guild.name,
|
||||||
internalName: guild.internal_name,
|
internalName: guild.internal_name,
|
||||||
@@ -291,7 +332,9 @@ class DatabaseService {
|
|||||||
upsertGuildConfig(guildConfig, isReactivation = false) {
|
upsertGuildConfig(guildConfig, isReactivation = false) {
|
||||||
if (isReactivation) {
|
if (isReactivation) {
|
||||||
// Check if guild exists but is inactive
|
// Check if guild exists but is inactive
|
||||||
const existingGuild = this.db.prepare('SELECT * FROM guilds WHERE id = ?').get(guildConfig.id);
|
const existingGuild = this.db
|
||||||
|
.prepare("SELECT * FROM guilds WHERE id = ?")
|
||||||
|
.get(guildConfig.id);
|
||||||
if (existingGuild && !existingGuild.is_active) {
|
if (existingGuild && !existingGuild.is_active) {
|
||||||
// Reactivate existing guild and update its info
|
// Reactivate existing guild and update its info
|
||||||
this.statements.reactivateGuild.run(guildConfig.id);
|
this.statements.reactivateGuild.run(guildConfig.id);
|
||||||
@@ -308,7 +351,9 @@ class DatabaseService {
|
|||||||
existingGuild.allowed_roles_for_request,
|
existingGuild.allowed_roles_for_request,
|
||||||
guildConfig.id
|
guildConfig.id
|
||||||
);
|
);
|
||||||
console.log(`Guild reactivated with existing configuration: ${guildConfig.name} (${guildConfig.id})`);
|
console.log(
|
||||||
|
`Guild reactivated with existing configuration: ${guildConfig.name} (${guildConfig.id})`
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -318,16 +363,18 @@ class DatabaseService {
|
|||||||
guildConfig.id,
|
guildConfig.id,
|
||||||
guildConfig.name,
|
guildConfig.name,
|
||||||
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.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,
|
||||||
guildConfig.allowedRolesForRequest || null
|
JSON.stringify(guildConfig.allowedRolesForRequest || [])
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Guild configuration saved: ${guildConfig.name} (${guildConfig.id})`);
|
console.log(
|
||||||
|
`Guild configuration saved: ${guildConfig.name} (${guildConfig.id})`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -360,7 +407,9 @@ class DatabaseService {
|
|||||||
* @returns {Object|null}
|
* @returns {Object|null}
|
||||||
*/
|
*/
|
||||||
getGuildConfigIncludingInactive(guildId) {
|
getGuildConfigIncludingInactive(guildId) {
|
||||||
const guild = this.db.prepare('SELECT * FROM guilds WHERE id = ?').get(guildId);
|
const guild = this.db
|
||||||
|
.prepare("SELECT * FROM guilds WHERE id = ?")
|
||||||
|
.get(guildId);
|
||||||
if (!guild) return null;
|
if (!guild) return null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -388,9 +437,9 @@ class DatabaseService {
|
|||||||
const events = this.statements.getScheduledEvents.all(guildId);
|
const events = this.statements.getScheduledEvents.all(guildId);
|
||||||
|
|
||||||
// Parse schedule strings back to objects/strings for node-schedule
|
// Parse schedule strings back to objects/strings for node-schedule
|
||||||
return events.map(event => ({
|
return events.map((event) => ({
|
||||||
...event,
|
...event,
|
||||||
schedule: this.parseSchedule(event.schedule)
|
schedule: this.parseSchedule(event.schedule),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,9 +465,10 @@ class DatabaseService {
|
|||||||
*/
|
*/
|
||||||
addScheduledEvent(guildId, event) {
|
addScheduledEvent(guildId, event) {
|
||||||
// Store schedule as JSON string to preserve object format and timezone
|
// Store schedule as JSON string to preserve object format and timezone
|
||||||
const scheduleString = typeof event.schedule === 'string'
|
const scheduleString =
|
||||||
? event.schedule
|
typeof event.schedule === "string"
|
||||||
: JSON.stringify(event.schedule);
|
? event.schedule
|
||||||
|
: JSON.stringify(event.schedule);
|
||||||
|
|
||||||
this.statements.insertScheduledEvent.run(
|
this.statements.insertScheduledEvent.run(
|
||||||
guildId,
|
guildId,
|
||||||
@@ -464,11 +514,13 @@ class DatabaseService {
|
|||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
getBotConfiguration() {
|
getBotConfiguration() {
|
||||||
const botName = this.getBotConfig('bot_name') || 'GHBot';
|
const botName = this.getBotConfig("bot_name") || "GHBot";
|
||||||
const debug = this.getBotConfig('debug') === 'true';
|
const debug = this.getBotConfig("debug") === "true";
|
||||||
const adminUserId = this.getBotConfig('admin_user_id') || '';
|
const adminUserId = this.getBotConfig("admin_user_id") || "";
|
||||||
const activities = JSON.parse(this.getBotConfig('activities') || '[]');
|
const activities = JSON.parse(this.getBotConfig("activities") || "[]");
|
||||||
const blacklistedUsers = JSON.parse(this.getBotConfig('blacklisted_users') || '[]');
|
const blacklistedUsers = JSON.parse(
|
||||||
|
this.getBotConfig("blacklisted_users") || "[]"
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
botName,
|
botName,
|
||||||
@@ -479,6 +531,67 @@ class DatabaseService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse role IDs from JSON string
|
||||||
|
* @param {string} roleIdsJson
|
||||||
|
* @returns {Array<string>}
|
||||||
|
*/
|
||||||
|
parseRoleIds(roleIdsJson) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(roleIdsJson || "[]");
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get allowed role IDs for a guild
|
||||||
|
* @param {string} guildId
|
||||||
|
* @returns {Array<string>}
|
||||||
|
*/
|
||||||
|
getAllowedRoleIds(guildId) {
|
||||||
|
const guild = this.statements.getGuild.get(guildId);
|
||||||
|
return guild ? this.parseRoleIds(guild.allowed_roles_for_request) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a role ID to the allowed list
|
||||||
|
* @param {string} guildId
|
||||||
|
* @param {string} roleId
|
||||||
|
*/
|
||||||
|
addAllowedRole(guildId, roleId) {
|
||||||
|
const currentRoles = this.getAllowedRoleIds(guildId);
|
||||||
|
if (!currentRoles.includes(roleId)) {
|
||||||
|
currentRoles.push(roleId);
|
||||||
|
this.updateAllowedRoleIds(guildId, currentRoles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a role ID from the allowed list
|
||||||
|
* @param {string} guildId
|
||||||
|
* @param {string} roleId
|
||||||
|
*/
|
||||||
|
removeAllowedRole(guildId, roleId) {
|
||||||
|
const currentRoles = this.getAllowedRoleIds(guildId);
|
||||||
|
const updatedRoles = currentRoles.filter((id) => id !== roleId);
|
||||||
|
if (updatedRoles.length !== currentRoles.length) {
|
||||||
|
this.updateAllowedRoleIds(guildId, updatedRoles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update allowed role IDs for a guild
|
||||||
|
* @param {string} guildId
|
||||||
|
* @param {Array<string>} roleIds
|
||||||
|
*/
|
||||||
|
updateAllowedRoleIds(guildId, roleIds) {
|
||||||
|
const updateStmt = this.db.prepare(
|
||||||
|
"UPDATE guilds SET allowed_roles_for_request = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND is_active = true"
|
||||||
|
);
|
||||||
|
updateStmt.run(JSON.stringify(roleIds), guildId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close database connection
|
* Close database connection
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user