diff --git a/server/src/index.ts b/server/src/index.ts index df33d9d..9ec1249 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -2,6 +2,7 @@ import path from 'node:path'; import fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import express, { Request, Response } from 'express'; +import multer from 'multer'; import cors from 'cors'; import crypto from 'node:crypto'; import { Client, GatewayIntentBits, Partials, ChannelType, Events, type Message } from 'discord.js'; @@ -1028,6 +1029,36 @@ app.post('/api/play-url', async (req: Request, res: Response) => { } }); +// --- Datei-Upload (Admin): MP3/WAV per HTTP hochladen --- +const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 100 * 1024 * 1024 } }); +app.post('/api/upload', requireAdmin, upload.single('file'), async (req: Request, res: Response) => { + try { + const file = (req as any).file as Express.Multer.File | undefined; + const folderRaw = String((req.body as any)?.folder ?? '').trim(); + if (!file) return res.status(400).json({ error: 'file erforderlich' }); + const orig = file.originalname || 'upload'; + const lower = orig.toLowerCase(); + const ext = lower.endsWith('.mp3') ? '.mp3' : lower.endsWith('.wav') ? '.wav' : ''; + if (!ext) return res.status(400).json({ error: 'Nur MP3 oder WAV erlaubt' }); + const base = path.parse(orig).name.replace(/[<>:"/\\|?*\x00-\x1f]/g, '_'); + const safeFolder = folderRaw && !folderRaw.includes('..') ? folderRaw : ''; + const targetDir = path.join(SOUNDS_DIR, safeFolder); + fs.mkdirSync(targetDir, { recursive: true }); + let targetPath = path.join(targetDir, `${base}${ext}`); + let i = 2; + while (fs.existsSync(targetPath)) { + targetPath = path.join(targetDir, `${base}-${i}${ext}`); + i += 1; + } + fs.writeFileSync(targetPath, file.buffer); + const rel = path.relative(SOUNDS_DIR, targetPath).replace(/\\/g, '/'); + return res.json({ ok: true, relativePath: rel, fileName: path.basename(targetPath) }); + } catch (e: any) { + console.error('upload error:', e); + return res.status(500).json({ error: e?.message ?? 'Unbekannter Fehler' }); + } +}); + diff --git a/web/src/App.tsx b/web/src/App.tsx index a7a2f90..bf7280b 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import ReactDOM from 'react-dom'; -import { fetchChannels, fetchSounds, playSound, setVolumeLive, getVolume, adminStatus, adminLogin, adminLogout, adminDelete, adminRename, playUrl, fetchCategories, createCategory, assignCategories, clearBadges, updateCategory, deleteCategory, partyStart, partyStop, subscribeEvents } from './api'; +import { fetchChannels, fetchSounds, playSound, setVolumeLive, getVolume, adminStatus, adminLogin, adminLogout, adminDelete, adminRename, playUrl, fetchCategories, createCategory, assignCategories, clearBadges, updateCategory, deleteCategory, partyStart, partyStop, subscribeEvents, uploadFile } from './api'; import type { VoiceChannelInfo, Sound, Category } from './types'; import { getCookie, setCookie } from './cookies'; @@ -299,7 +299,18 @@ export default function App() {
🥦
)} -
+
{ e.preventDefault(); }} onDrop={async (e)=>{ + try{ + e.preventDefault(); + if(!isAdmin){ setError('Login required for upload'); return; } + const files = Array.from(e.dataTransfer?.files || []).filter(f=>/\.(mp3|wav)$/i.test(f.name)); + if(files.length===0){ setInfo('Drop MP3/WAV files to upload'); return; } + for(const f of files){ await uploadFile(f); } + setInfo(`${files.length} file(s) uploaded`); + const resp = await fetchSounds(query, activeFolder === '__favs__' ? '__all__' : activeFolder, activeCategoryId || undefined); + setSounds(resp.items); setTotal(resp.total); setFolders(resp.folders); + }catch(err:any){ setError(err?.message||'Upload failed'); setInfo(null); } + }}>

diff --git a/web/src/api.ts b/web/src/api.ts index 2e487a6..0b5edc3 100644 --- a/web/src/api.ts +++ b/web/src/api.ts @@ -184,6 +184,22 @@ export async function playUrl(url: string, guildId: string, channelId: string, v } } +export async function uploadFile(file: File, folder?: string) { + const form = new FormData(); + form.append('file', file); + if (folder) form.append('folder', folder); + const res = await fetch(`${API_BASE}/upload`, { + method: 'POST', + credentials: 'include', + body: form + }); + if (!res.ok) { + const data = await res.json().catch(()=>({})); + throw new Error(data?.error || 'Upload failed'); + } + return res.json(); +} +