Files
dj-spangebob/src/commands/soundboard.ts
Patryk Koreń ad87d3fbb8 Add an overlay soundboard and fix playback command bugs.
Stop was advancing the queue, history overwrote the day file, and slash commands often never replied; soundboard clips now mix on top of the current track.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-13 17:03:02 +02:00

133 lines
4.5 KiB
TypeScript

import {
AutocompleteInteraction,
CacheType,
ChatInputCommandInteraction,
SlashCommandBuilder,
} from 'discord.js';
import {
addSound,
buildSoundboardMessage,
listSounds,
playById,
removeSound,
} from '../soundboard';
const name = "soundboard"
function register() {
return new SlashCommandBuilder()
.setName(name)
.setDescription('Soundboard — dźwięki na wierzchu muzyki')
.addSubcommand((sub) => sub
.setName("add")
.setDescription("Dodaj dźwięk (max 10s)")
.addAttachmentOption((option) => option
.setName("file")
.setDescription("Krótki plik audio")
.setRequired(true))
.addStringOption((option) => option
.setName("description")
.setDescription("Krótki opis na przycisku")
.setRequired(true)
.setMaxLength(40))
.addStringOption((option) => option
.setName("emoji")
.setDescription("Emoji do przycisku")
.setRequired(true)
.setMaxLength(32)))
.addSubcommand((sub) => sub
.setName("list")
.setDescription("Pokaż soundboard do klikania"))
.addSubcommand((sub) => sub
.setName("play")
.setDescription("Puść dźwięk z listy")
.addStringOption((option) => option
.setName("sound")
.setDescription("Który dźwięk")
.setRequired(true)
.setAutocomplete(true)))
.addSubcommand((sub) => sub
.setName("remove")
.setDescription("Usuń dźwięk")
.addStringOption((option) => option
.setName("sound")
.setDescription("Który dźwięk")
.setRequired(true)
.setAutocomplete(true)))
}
export async function handleSoundboardAutocomplete(interaction: AutocompleteInteraction<CacheType>) {
const focused = interaction.options.getFocused().toLowerCase();
const choices = listSounds()
.filter((sound) =>
sound.description.toLowerCase().includes(focused) ||
sound.emoji.includes(focused) ||
sound.id.startsWith(focused)
)
.slice(0, 25)
.map((sound) => ({
name: `${sound.emoji} ${sound.description}`.slice(0, 100),
value: sound.id,
}));
await interaction.respond(choices);
}
async function execute(interaction: ChatInputCommandInteraction<CacheType>) {
const sub = interaction.options.getSubcommand();
try {
if (sub === "add") {
await interaction.deferReply();
const file = interaction.options.getAttachment("file", true);
const description = interaction.options.getString("description", true);
const emoji = interaction.options.getString("emoji", true);
const sound = await addSound({
attachment: file,
description,
emoji,
user: interaction.user.username,
});
await interaction.editReply(`Dodane: ${sound.emoji} **${sound.description}**`);
return;
}
if (sub === "list") {
await interaction.reply(buildSoundboardMessage(0));
return;
}
if (sub === "play") {
await interaction.deferReply({ ephemeral: true });
const id = interaction.options.getString("sound", true);
const sound = await playById(id, interaction);
await interaction.editReply(`${sound.emoji} ${sound.description}`);
return;
}
if (sub === "remove") {
await interaction.deferReply({ ephemeral: true });
const id = interaction.options.getString("sound", true);
const removed = removeSound(id);
if (!removed) {
await interaction.editReply("Nie ma takiego dźwięku");
return;
}
await interaction.editReply(`Usunięte: ${removed.emoji} ${removed.description}`);
return;
}
} catch (error) {
console.error(error);
const message = error instanceof Error ? error.message : "Coś poszło nie tak :/";
if (interaction.deferred || interaction.replied) {
await interaction.editReply(message);
} else {
await interaction.reply({ content: message, ephemeral: true });
}
}
}
export default {
name,
register,
execute
}