Files
ghbot/src/commands/slash/soundboard.js
2025-08-16 22:21:10 -07:00

261 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const {
SlashCommandBuilder,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
EmbedBuilder,
MessageFlags,
} = require("discord.js");
const fs = require("fs");
const path = require("path");
const sfxManager = require("../../services/sfxManager");
// Parse categories from README.md
function getSfxCategories() {
try {
const sfxReadmePath = path.join(
__dirname,
"..",
"..",
"..",
"sfx",
"README.md"
);
if (!fs.existsSync(sfxReadmePath)) {
return null;
}
const content = fs.readFileSync(sfxReadmePath, "utf-8");
const categories = {};
// Parse categories and their sounds
const lines = content.split("\n");
let currentCategory = null;
for (const line of lines) {
const headerMatch = line.match(/^\*\*([^*]+)\*\*$/);
if (headerMatch) {
currentCategory = headerMatch[1];
categories[currentCategory] = [];
} else if (currentCategory && line.trim() && !line.startsWith("```")) {
// Parse comma-separated sounds from the line
const sounds = line
.split(",")
.map((s) => s.trim())
.filter((s) => s.length > 0);
categories[currentCategory].push(...sounds);
}
}
return categories;
} catch (error) {
console.error("Error parsing SFX categories:", error);
return null;
}
}
module.exports = {
data: new SlashCommandBuilder()
.setName("soundboard")
.setDescription("Interactive soundboard with categorized buttons"),
async execute(interaction, guildConfig) {
// Check if user is in a voice channel
if (!interaction.member.voice.channel) {
return interaction.reply({
content: "You need to be in a voice channel to use the soundboard!",
flags: [MessageFlags.Ephemeral],
});
}
const categories = getSfxCategories();
if (!categories) {
return interaction.reply({
content:
"Soundboard not available - SFX categories could not be loaded.",
flags: [MessageFlags.Ephemeral],
});
}
// Create category selection buttons (4 per row for better layout)
const categoryNames = Object.keys(categories);
const rows = [];
for (let i = 0; i < categoryNames.length; i += 4) {
const row = new ActionRowBuilder();
const categoriesInRow = categoryNames.slice(i, i + 4);
for (const category of categoriesInRow) {
const button = new ButtonBuilder()
.setCustomId(
`soundboard_category_${category
.toLowerCase()
.replace(/\s+/g, "_")
.replace(/&/g, "and")}`
)
.setLabel(
category.length > 80 ? category.substring(0, 77) + "..." : category
)
.setStyle(ButtonStyle.Primary);
row.addComponents(button);
}
if (row.components.length > 0) {
rows.push(row);
}
}
const embed = new EmbedBuilder()
.setTitle("🎛️ Interactive Soundboard")
.setDescription("Choose a category to browse sound effects:")
.setColor(0x21c629);
await interaction.reply({
embeds: [embed],
components: rows,
});
},
async handleCategorySelection(interaction, guildConfig) {
const customId = interaction.customId;
let categoryKey,
page = 0;
if (customId.includes("_page_")) {
// Handle pagination: soundboard_category_general_page_1
const parts = customId
.replace("soundboard_category_", "")
.split("_page_");
categoryKey = parts[0]
.replace(/_/g, " ")
.replace(/and/g, "&")
.toUpperCase();
page = parseInt(parts[1]) || 0;
} else {
// Handle initial category selection
categoryKey = customId
.replace("soundboard_category_", "")
.replace(/_/g, " ")
.replace(/and/g, "&")
.toUpperCase();
}
const categories = getSfxCategories();
if (!categories || !categories[categoryKey]) {
return interaction.reply({
content: "Category not found!",
flags: [MessageFlags.Ephemeral],
});
}
const allSounds = categories[categoryKey].filter((sound) =>
sfxManager.hasSfx(sound)
);
const soundsPerPage = 16; // 4 sounds per row × 4 rows = 16 sounds per page
const totalPages = Math.ceil(allSounds.length / soundsPerPage);
const startIndex = page * soundsPerPage;
const sounds = allSounds.slice(startIndex, startIndex + soundsPerPage);
const rows = [];
let buttonCount = 0;
// Create sound buttons (4 per row, 4 rows for sounds + 1 for navigation = 16 sound buttons max)
for (let i = 0; i < sounds.length && rows.length < 4; i += 4) {
const row = new ActionRowBuilder();
const soundsInRow = sounds.slice(i, i + 4);
for (const sound of soundsInRow) {
if (buttonCount >= 16) break; // Leave room for navigation row
const button = new ButtonBuilder()
.setCustomId(`soundboard_play_${sound}`)
.setLabel(sound.length > 80 ? sound.substring(0, 77) + "..." : sound)
.setStyle(ButtonStyle.Secondary);
row.addComponents(button);
buttonCount++;
}
if (row.components.length > 0) {
rows.push(row);
}
}
// Add navigation row with back button and pagination if needed
const navRow = new ActionRowBuilder();
// Add previous page button if not on first page
if (page > 0) {
const prevButton = new ButtonBuilder()
.setCustomId(
`soundboard_category_${categoryKey
.toLowerCase()
.replace(/\s+/g, "_")}_page_${page - 1}`
)
.setLabel("« Previous")
.setStyle(ButtonStyle.Secondary)
.setEmoji("◀️");
navRow.addComponents(prevButton);
}
// Add back to categories button
const backButton = new ButtonBuilder()
.setCustomId("soundboard_back")
.setLabel("Back to Categories")
.setStyle(ButtonStyle.Primary)
.setEmoji("🔙");
navRow.addComponents(backButton);
// Add next page button if there are more pages
if (page < totalPages - 1) {
const nextButton = new ButtonBuilder()
.setCustomId(
`soundboard_category_${categoryKey
.toLowerCase()
.replace(/\s+/g, "_")}_page_${page + 1}`
)
.setLabel("Next »")
.setStyle(ButtonStyle.Secondary)
.setEmoji("▶️");
navRow.addComponents(nextButton);
}
rows.push(navRow);
// Show pagination info
const paginationNote =
totalPages > 1
? `\n\n*Page ${page + 1} of ${totalPages} (${
allSounds.length
} total sounds)*`
: "";
const embed = new EmbedBuilder()
.setTitle(`${categoryKey} Soundboard`)
.setDescription(`Choose a sound effect to play:${paginationNote}`)
.setColor(0x21c629)
.setFooter({ text: "Click a sound button to play it" });
await interaction.update({
embeds: [embed],
components: rows,
});
},
async handleSoundPlay(interaction, guildConfig) {
const soundName = interaction.customId.replace("soundboard_play_", "");
// Use the reusable SFX playing method
await sfxManager.playSfxInteraction(
interaction,
soundName,
guildConfig,
"soundboard"
);
},
};