Add MP3 URL import and analytics widgets

This commit is contained in:
Bot 2026-03-01 18:56:37 +01:00
parent e200087a73
commit 5a41b6a622
5 changed files with 380 additions and 19 deletions

View file

@ -557,6 +557,83 @@ app.get('/api/health', (_req: Request, res: Response) => {
res.json({ ok: true, totalPlays: persistedState.totalPlays ?? 0, categories: (persistedState.categories ?? []).length });
});
type ListedSound = {
fileName: string;
name: string;
folder: string;
relativePath: string;
};
function listAllSounds(): ListedSound[] {
const rootEntries = fs.readdirSync(SOUNDS_DIR, { withFileTypes: true });
const rootFiles: ListedSound[] = rootEntries
.filter((d) => {
if (!d.isFile()) return false;
const n = d.name.toLowerCase();
return n.endsWith('.mp3') || n.endsWith('.wav');
})
.map((d) => ({
fileName: d.name,
name: path.parse(d.name).name,
folder: '',
relativePath: d.name,
}));
const folderItems: ListedSound[] = [];
const subFolders = rootEntries.filter((d) => d.isDirectory());
for (const dirent of subFolders) {
const folderName = dirent.name;
const folderPath = path.join(SOUNDS_DIR, folderName);
const entries = fs.readdirSync(folderPath, { withFileTypes: true });
for (const e of entries) {
if (!e.isFile()) continue;
const n = e.name.toLowerCase();
if (!(n.endsWith('.mp3') || n.endsWith('.wav'))) continue;
folderItems.push({
fileName: e.name,
name: path.parse(e.name).name,
folder: folderName,
relativePath: path.join(folderName, e.name),
});
}
}
return [...rootFiles, ...folderItems].sort((a, b) => a.name.localeCompare(b.name));
}
app.get('/api/analytics', (_req: Request, res: Response) => {
try {
const allItems = listAllSounds();
const byKey = new Map<string, ListedSound>();
for (const it of allItems) {
byKey.set(it.relativePath, it);
if (!byKey.has(it.fileName)) byKey.set(it.fileName, it);
}
const mostPlayed = Object.entries(persistedState.plays ?? {})
.map(([rel, count]) => {
const item = byKey.get(rel);
if (!item) return null;
return {
name: item.name,
relativePath: item.relativePath,
count: Number(count) || 0,
};
})
.filter((x): x is { name: string; relativePath: string; count: number } => !!x)
.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name))
.slice(0, 10);
res.json({
totalSounds: allItems.length,
totalPlays: persistedState.totalPlays ?? 0,
mostPlayed,
});
} catch (e: any) {
res.status(500).json({ error: e?.message ?? 'Analytics konnten nicht geladen werden' });
}
});
// --- Admin Auth ---
type AdminPayload = { iat: number; exp: number };
function b64url(input: Buffer | string): string {
@ -1280,28 +1357,34 @@ app.listen(PORT, () => {
});
// --- Medien-URL abspielen ---
// Unterstützt: direkte MP3- oder WAV-URL (Download und Ablage)
// Unterstützt: direkte MP3-URL (Download und Ablage)
app.post('/api/play-url', async (req: Request, res: Response) => {
try {
const { url, guildId, channelId, volume } = req.body as { url?: string; guildId?: string; channelId?: string; volume?: number };
if (!url || !guildId || !channelId) return res.status(400).json({ error: 'url, guildId, channelId erforderlich' });
const lower = url.toLowerCase();
if (lower.endsWith('.mp3') || lower.endsWith('.wav')) {
const fileName = path.basename(new URL(url).pathname);
const dest = path.join(SOUNDS_DIR, fileName);
const r = await fetch(url);
if (!r.ok) return res.status(400).json({ error: 'Download fehlgeschlagen' });
const buf = Buffer.from(await r.arrayBuffer());
fs.writeFileSync(dest, buf);
try {
await playFilePath(guildId, channelId, dest, volume, path.basename(dest));
} catch {
return res.status(500).json({ error: 'Abspielen fehlgeschlagen' });
}
return res.json({ ok: true, saved: path.basename(dest) });
let parsed: URL;
try {
parsed = new URL(url);
} catch {
return res.status(400).json({ error: 'Ungültige URL' });
}
return res.status(400).json({ error: 'Nur MP3- oder WAV-Links werden unterstützt.' });
const pathname = parsed.pathname.toLowerCase();
if (!pathname.endsWith('.mp3')) {
return res.status(400).json({ error: 'Nur direkte MP3-Links werden unterstützt.' });
}
const fileName = path.basename(parsed.pathname);
const dest = path.join(SOUNDS_DIR, fileName);
const r = await fetch(url);
if (!r.ok) return res.status(400).json({ error: 'Download fehlgeschlagen' });
const buf = Buffer.from(await r.arrayBuffer());
fs.writeFileSync(dest, buf);
try {
await playFilePath(guildId, channelId, dest, volume, path.basename(dest));
} catch {
return res.status(500).json({ error: 'Abspielen fehlgeschlagen' });
}
return res.json({ ok: true, saved: path.basename(dest) });
} catch (e: any) {
console.error('play-url error:', e);
return res.status(500).json({ error: e?.message ?? 'Unbekannter Fehler' });