import React, { useEffect, useMemo, useState } from 'react'; import { fetchChannels, fetchSounds, playSound, setVolumeLive } from './api'; import type { VoiceChannelInfo, Sound } from './types'; import { getCookie, setCookie } from './cookies'; export default function App() { const [sounds, setSounds] = useState([]); const [total, setTotal] = useState(0); const [folders, setFolders] = useState>([]); const [activeFolder, setActiveFolder] = useState('__all__'); const [channels, setChannels] = useState([]); const [query, setQuery] = useState(''); const [selected, setSelected] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [volume, setVolume] = useState(1); const [favs, setFavs] = useState>({}); useEffect(() => { (async () => { try { const c = await fetchChannels(); setChannels(c); const stored = localStorage.getItem('selectedChannel'); if (stored && c.find(x => `${x.guildId}:${x.channelId}` === stored)) { setSelected(stored); } else if (c[0]) { setSelected(`${c[0].guildId}:${c[0].channelId}`); } } catch (e: any) { setError(e?.message || 'Fehler beim Laden der Channels'); } })(); }, []); useEffect(() => { (async () => { try { const folderParam = activeFolder === '__favs__' ? '__all__' : activeFolder; const s = await fetchSounds(query, folderParam); setSounds(s.items); setTotal(s.total); setFolders(s.folders); } catch (e: any) { setError(e?.message || 'Fehler beim Laden der Sounds'); } })(); }, [activeFolder, query]); // Favoriten aus Cookie laden useEffect(() => { const c = getCookie('favs'); if (c) { try { setFavs(JSON.parse(c)); } catch {} } }, []); // Favoriten persistieren useEffect(() => { try { setCookie('favs', JSON.stringify(favs)); } catch {} }, [favs]); useEffect(() => { if (selected) localStorage.setItem('selectedChannel', selected); }, [selected]); const filtered = useMemo(() => { const q = query.trim().toLowerCase(); if (!q) return sounds; return sounds.filter((s) => s.name.toLowerCase().includes(q)); }, [sounds, query]); const favCount = useMemo(() => Object.values(favs).filter(Boolean).length, [favs]); async function handlePlay(name: string, rel?: string) { setError(null); if (!selected) return setError('Bitte einen Voice-Channel auswählen'); const [guildId, channelId] = selected.split(':'); try { setLoading(true); await playSound(name, guildId, channelId, volume, rel); } catch (e: any) { setError(e?.message || 'Play fehlgeschlagen'); } finally { setLoading(false); } } return (

Discord Soundboard

Schicke dem Bot per privater Nachricht eine .mp3 — neue Sounds erscheinen automatisch.

Geladene Sounds: {total}
setQuery(e.target.value)} placeholder="Nach Sounds suchen..." aria-label="Suche" />
{ const v = parseFloat(e.target.value); setVolume(v); if (selected) { const [guildId] = selected.split(':'); try { await setVolumeLive(guildId, v); } catch {} } }} aria-label="Lautstärke" />
{folders.length > 0 && ( )} {error &&
{error}
}
{(activeFolder === '__favs__' ? filtered.filter((s) => !!favs[s.relativePath ?? s.fileName]) : filtered).map((s) => { const key = `${s.relativePath ?? s.fileName}`; const isFav = !!favs[key]; return (
); })} {filtered.length === 0 &&
Keine Sounds gefunden.
}
{/* footer counter entfällt, da oben sichtbar */}
); } function handlePlayWithPathFactory(play: (name: string, rel?: string) => Promise) { return (s: Sound & { relativePath?: string }) => play(s.name, s.relativePath); }