Add MP3 URL import and analytics widgets
This commit is contained in:
parent
e200087a73
commit
5a41b6a622
5 changed files with 380 additions and 19 deletions
|
|
@ -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' });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue