diff --git a/server/src/index.ts b/server/src/index.ts index bf916b0..d4981e7 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -606,7 +606,19 @@ app.post('/api/play-url', async (req: Request, res: Response) => { const guild = client.guilds.cache.get(guildId); if (!guild) return res.status(404).json({ error: 'Guild nicht gefunden' }); let state = guildAudioState.get(guildId); - if (!state) return res.status(400).json({ error: 'Bitte zuerst einen Sound abspielen, um die Verbindung herzustellen' }); + if (!state) { + const channel = guild.channels.cache.get(channelId); + if (!channel || (channel.type !== ChannelType.GuildVoice && channel.type !== ChannelType.GuildStageVoice)) { + return res.status(400).json({ error: 'Ungültiger Voice-Channel' }); + } + const connection = joinVoiceChannel({ channelId, guildId, adapterCreator: guild.voiceAdapterCreator as any, selfDeaf: false, selfMute: false }); + const player = createAudioPlayer({ behaviors: { noSubscriber: NoSubscriberBehavior.Play } }); + connection.subscribe(player); + state = { connection, player, guildId, channelId, currentVolume: getPersistedVolume(guildId) }; + guildAudioState.set(guildId, state); + state.connection = await ensureConnectionReady(connection, channelId, guildId, guild); + attachVoiceLifecycle(state, guild); + } const useVolume = typeof volume === 'number' ? Math.max(0, Math.min(1, volume)) : state.currentVolume ?? 1; diff --git a/web/src/App.tsx b/web/src/App.tsx index 50831ca..51a654c 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -133,7 +133,7 @@ export default function App() { )} -
+
Rainbow Chaos
+
+ +
- setMediaUrl(e.target.value)} placeholder="YouTube/Instagram/MP3 URL..." /> + setMediaUrl(e.target.value)} + onKeyDown={async (e) => { + if (e.key === 'Enter') { + if (!selected) { setError('Bitte Voice-Channel wählen'); return; } + const [guildId, channelId] = selected.split(':'); + try { await playUrl(mediaUrl, guildId, channelId, volume); } + catch (err: any) { setError(err?.message || 'Play-URL fehlgeschlagen'); } + } + }} + placeholder="YouTube/Instagram/MP3 URL..." + />
- {!isAdmin && ( +
+ + {!isAdmin && ( +
setAdminPwd(e.target.value)} placeholder="Admin Passwort" />
- )} -
+
+ )} {/* Admin Toolbar */} {isAdmin && ( diff --git a/web/src/styles.css b/web/src/styles.css index 4d19b02..335d2e8 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -88,7 +88,10 @@ header p { opacity: .8; } } .badge { align-self: flex-start; background: rgba(255,255,255,.1); border: 1px solid rgba(255,255,255,.18); padding: 6px 10px; border-radius: 999px; font-size: 13px; } -.controls { display: grid; grid-template-columns: 1fr minmax(240px, 300px) 220px 180px minmax(260px, 1fr); gap: 12px; align-items: center; margin-bottom: 18px; } +.controls { display: grid; grid-template-columns: 1fr minmax(240px, 320px) 260px 200px; gap: 12px; align-items: center; margin-bottom: 12px; } +.controls.row2 { grid-template-columns: minmax(400px, 1fr); } +.controls.row3 { grid-template-columns: 1fr; } +.controls.glass { padding: 18px; } .controls.glass { backdrop-filter: saturate(140%) blur(20px); background: linear-gradient(135deg, rgba(255,255,255,.14), rgba(255,255,255,.06));