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:
Chris Ham
2025-08-16 18:07:07 -07:00
parent 18350ee878
commit 61a376cfbb
5 changed files with 535 additions and 124 deletions

View File

@@ -13,22 +13,24 @@ class ConfigManager {
*/
loadFileConfig() {
const configPath = path.join(__dirname, "..", "..", "config.json");
if (!fs.existsSync(configPath)) {
console.warn("config.json not found, using environment variables only");
return {
discord: {
token: process.env.DISCORD_TOKEN,
adminUserId: process.env.ADMIN_USER_ID,
}
},
};
}
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
// Validate required fields
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;
@@ -46,19 +48,30 @@ class ConfigManager {
*/
getBotConfig() {
const fileConfig = this.fileConfig;
const dbConfig = this.databaseService ? this.databaseService.getBotConfiguration() : {};
const dbConfig = this.databaseService
? this.databaseService.getBotConfiguration()
: {};
return {
// Use file config as fallback, database as primary
botName: dbConfig.botName || fileConfig.botName || 'GHBot',
debug: dbConfig.debug !== undefined ? dbConfig.debug : (fileConfig.debug || false),
botName: dbConfig.botName || fileConfig.botName || "GHBot",
debug:
dbConfig.debug !== undefined
? dbConfig.debug
: fileConfig.debug || false,
discord: {
token: fileConfig.discord?.token || process.env.DISCORD_TOKEN,
adminUserId: dbConfig.adminUserId || fileConfig.discord?.adminUserId || process.env.ADMIN_USER_ID,
activities: dbConfig.activities || fileConfig.discord?.activities || ['Playing sounds', 'Serving facts'],
blacklistedUsers: dbConfig.blacklistedUsers || fileConfig.discord?.blacklistedUsers || [],
master: fileConfig.discord?.master !== false, // Default to true
}
adminUserId:
dbConfig.adminUserId ||
fileConfig.discord?.adminUserId ||
process.env.ADMIN_USER_ID,
activities: dbConfig.activities ||
fileConfig.discord?.activities || ["Playing sounds", "Serving facts"],
blacklistedUsers:
dbConfig.blacklistedUsers ||
fileConfig.discord?.blacklistedUsers ||
[],
},
};
}
@@ -75,19 +88,19 @@ class ConfigManager {
// Fallback to file config for backward compatibility
if (this.fileConfig.discord?.guilds) {
const guilds = Array.isArray(this.fileConfig.discord.guilds)
? this.fileConfig.discord.guilds
const guilds = Array.isArray(this.fileConfig.discord.guilds)
? 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 {
id: guildId,
name: 'Unknown Guild',
internalName: 'Unknown Guild',
prefix: '!',
name: "Unknown Guild",
internalName: "Unknown Guild",
prefix: "!",
enableSfx: true,
allowedSfxChannels: null,
sfxVolume: 0.5,
@@ -107,10 +120,10 @@ class ConfigManager {
// Fallback to file config
if (this.fileConfig.discord?.guilds) {
const guilds = Array.isArray(this.fileConfig.discord.guilds)
? this.fileConfig.discord.guilds
const guilds = Array.isArray(this.fileConfig.discord.guilds)
? this.fileConfig.discord.guilds
: Object.values(this.fileConfig.discord.guilds);
return guilds;
}