Nightly: Partymode serverseitig Start/Stop Endpunkte + Panic stoppt global; Frontend triggert Party-Start/Stop
This commit is contained in:
parent
442c42ef23
commit
9bb402edd3
3 changed files with 107 additions and 2 deletions
|
|
@ -149,6 +149,9 @@ type GuildAudioState = {
|
||||||
currentVolume: number; // 0..1
|
currentVolume: number; // 0..1
|
||||||
};
|
};
|
||||||
const guildAudioState = new Map<string, GuildAudioState>();
|
const guildAudioState = new Map<string, GuildAudioState>();
|
||||||
|
// Partymode: serverseitige Steuerung (global pro Guild)
|
||||||
|
const partyTimers = new Map<string, NodeJS.Timeout>();
|
||||||
|
const partyActive = new Set<string>();
|
||||||
|
|
||||||
async function playFilePath(guildId: string, channelId: string, filePath: string, volume?: number, relativeKey?: string): Promise<void> {
|
async function playFilePath(guildId: string, channelId: string, filePath: string, volume?: number, relativeKey?: string): Promise<void> {
|
||||||
const guild = client.guilds.cache.get(guildId);
|
const guild = client.guilds.cache.get(guildId);
|
||||||
|
|
@ -862,12 +865,94 @@ app.post('/api/stop', (req: Request, res: Response) => {
|
||||||
const state = guildAudioState.get(guildId);
|
const state = guildAudioState.get(guildId);
|
||||||
if (!state) return res.status(404).json({ error: 'Kein aktiver Player' });
|
if (!state) return res.status(404).json({ error: 'Kein aktiver Player' });
|
||||||
state.player.stop(true);
|
state.player.stop(true);
|
||||||
|
// Partymode für diese Guild ebenfalls stoppen
|
||||||
|
try {
|
||||||
|
const t = partyTimers.get(guildId);
|
||||||
|
if (t) clearTimeout(t);
|
||||||
|
partyTimers.delete(guildId);
|
||||||
|
partyActive.delete(guildId);
|
||||||
|
} catch {}
|
||||||
return res.json({ ok: true });
|
return res.json({ ok: true });
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return res.status(500).json({ error: e?.message ?? 'Unbekannter Fehler' });
|
return res.status(500).json({ error: e?.message ?? 'Unbekannter Fehler' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- Partymode (serverseitig) ---
|
||||||
|
function schedulePartyPlayback(guildId: string, channelId: string) {
|
||||||
|
const MIN_DELAY = 30_000; // 30s
|
||||||
|
const MAX_EXTRA = 60_000; // +0..60s => 30..90s
|
||||||
|
|
||||||
|
const doPlay = async () => {
|
||||||
|
try {
|
||||||
|
// Dateien ermitteln (mp3/wav, inkl. Subfolder)
|
||||||
|
const rootEntries = fs.readdirSync(SOUNDS_DIR, { withFileTypes: true });
|
||||||
|
const pick: string[] = [];
|
||||||
|
for (const d of rootEntries) {
|
||||||
|
if (d.isFile()) {
|
||||||
|
const l = d.name.toLowerCase(); if (l.endsWith('.mp3') || l.endsWith('.wav')) pick.push(path.join(SOUNDS_DIR, d.name));
|
||||||
|
} else if (d.isDirectory()) {
|
||||||
|
const folderPath = path.join(SOUNDS_DIR, d.name);
|
||||||
|
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')) pick.push(path.join(folderPath, e.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pick.length === 0) return;
|
||||||
|
const filePath = pick[Math.floor(Math.random() * pick.length)];
|
||||||
|
await playFilePath(guildId, channelId, filePath);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Partymode play error:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loop = async () => {
|
||||||
|
if (!partyActive.has(guildId)) return;
|
||||||
|
await doPlay();
|
||||||
|
if (!partyActive.has(guildId)) return;
|
||||||
|
const delay = MIN_DELAY + Math.floor(Math.random() * MAX_EXTRA);
|
||||||
|
const t = setTimeout(loop, delay);
|
||||||
|
partyTimers.set(guildId, t);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start: sofort spielen und nächste planen
|
||||||
|
partyActive.add(guildId);
|
||||||
|
void loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.post('/api/party/start', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const { guildId, channelId } = req.body as { guildId?: string; channelId?: string };
|
||||||
|
if (!guildId || !channelId) return res.status(400).json({ error: 'guildId und channelId erforderlich' });
|
||||||
|
// vorhandenen Timer stoppen
|
||||||
|
const old = partyTimers.get(guildId); if (old) clearTimeout(old);
|
||||||
|
partyTimers.delete(guildId);
|
||||||
|
schedulePartyPlayback(guildId, channelId);
|
||||||
|
return res.json({ ok: true });
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error('party/start error', e);
|
||||||
|
return res.status(500).json({ error: e?.message ?? 'Unbekannter Fehler' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/api/party/stop', (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const { guildId } = req.body as { guildId?: string };
|
||||||
|
const id = String(guildId ?? '');
|
||||||
|
if (!id) return res.status(400).json({ error: 'guildId erforderlich' });
|
||||||
|
const t = partyTimers.get(id); if (t) clearTimeout(t);
|
||||||
|
partyTimers.delete(id);
|
||||||
|
partyActive.delete(id);
|
||||||
|
return res.json({ ok: true });
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error('party/stop error', e);
|
||||||
|
return res.status(500).json({ error: e?.message ?? 'Unbekannter Fehler' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Static Frontend ausliefern (Vite build)
|
// Static Frontend ausliefern (Vite build)
|
||||||
const webDistPath = path.resolve(__dirname, '../../web/dist');
|
const webDistPath = path.resolve(__dirname, '../../web/dist');
|
||||||
if (fs.existsSync(webDistPath)) {
|
if (fs.existsSync(webDistPath)) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { fetchChannels, fetchSounds, playSound, setVolumeLive, getVolume, adminStatus, adminLogin, adminLogout, adminDelete, adminRename, playUrl, fetchCategories, createCategory, assignCategories, assignBadges, clearBadges, updateCategory, deleteCategory } from './api';
|
import { fetchChannels, fetchSounds, playSound, setVolumeLive, getVolume, adminStatus, adminLogin, adminLogout, adminDelete, adminRename, playUrl, fetchCategories, createCategory, assignCategories, assignBadges, clearBadges, updateCategory, deleteCategory, partyStart, partyStop } from './api';
|
||||||
import type { VoiceChannelInfo, Sound, Category } from './types';
|
import type { VoiceChannelInfo, Sound, Category } from './types';
|
||||||
import { getCookie, setCookie } from './cookies';
|
import { getCookie, setCookie } from './cookies';
|
||||||
|
|
||||||
|
|
@ -243,9 +243,13 @@ export default function App() {
|
||||||
if (chaosMode) {
|
if (chaosMode) {
|
||||||
setChaosMode(false);
|
setChaosMode(false);
|
||||||
await stopChaosMode();
|
await stopChaosMode();
|
||||||
|
// serverseitig stoppen
|
||||||
|
if (selected) { const [guildId] = selected.split(':'); try { await partyStop(guildId); } catch {} }
|
||||||
} else {
|
} else {
|
||||||
setChaosMode(true);
|
setChaosMode(true);
|
||||||
await startChaosMode();
|
await startChaosMode();
|
||||||
|
// serverseitig starten
|
||||||
|
if (selected) { const [guildId, channelId] = selected.split(':'); try { await partyStart(guildId, channelId); } catch {} }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -310,7 +314,7 @@ export default function App() {
|
||||||
>
|
>
|
||||||
Partymode
|
Partymode
|
||||||
</button>
|
</button>
|
||||||
<button className="bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-6 rounded-lg transition duration-300" onClick={async () => { setChaosMode(false); await stopChaosMode(); }}>Panic</button>
|
<button className="bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-6 rounded-lg transition duration-300" onClick={async () => { setChaosMode(false); await stopChaosMode(); if(selected){ const [guildId]=selected.split(':'); try{ await partyStop(guildId);}catch{} } }}>Panic</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,22 @@ export async function playSound(soundName: string, guildId: string, channelId: s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function partyStart(guildId: string, channelId: string) {
|
||||||
|
const res = await fetch(`${API_BASE}/party/start`, {
|
||||||
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ guildId, channelId })
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error('Partymode Start fehlgeschlagen');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function partyStop(guildId: string) {
|
||||||
|
const res = await fetch(`${API_BASE}/party/stop`, {
|
||||||
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ guildId })
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error('Partymode Stop fehlgeschlagen');
|
||||||
|
}
|
||||||
|
|
||||||
export async function setVolumeLive(guildId: string, volume: number): Promise<void> {
|
export async function setVolumeLive(guildId: string, volume: number): Promise<void> {
|
||||||
const res = await fetch(`${API_BASE}/volume`, {
|
const res = await fetch(`${API_BASE}/volume`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue