Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e7d04378a | ||
|
|
15babcd75d | ||
|
|
e6f8e3c3c5 | ||
|
|
c46581a548 |
@@ -2224,6 +2224,7 @@ export default function Settings({ me }) {
|
||||
</span>
|
||||
<div className="radio-now-text">
|
||||
<strong>
|
||||
{radio.laeuft.pausiert && '⏸️ '}
|
||||
{radio.laeuft.art === 'youtube'
|
||||
? radio.laeuft.titel
|
||||
: radio.laeuft.sender}
|
||||
@@ -2321,6 +2322,14 @@ export default function Settings({ me }) {
|
||||
<option key={x.id} value={x.id}>{x.emoji || '📻'} {x.name}</option>
|
||||
))}
|
||||
</select>
|
||||
{/* Pause gibt es nur bei YouTube: ein Dauerstrom laeuft
|
||||
beim Sender weiter, waehrend hier die Leitung volllaeuft --
|
||||
beim Fortsetzen kaeme eine Konserve, die hinterherhinkt. */}
|
||||
<button className="btn btn-ghost"
|
||||
disabled={radioLaedt || radio.laeuft?.art !== 'youtube'}
|
||||
onClick={() => radioSteuern('pause')}>
|
||||
{radio.laeuft?.pausiert ? '▶️ Weiter' : '⏸️ Pause'}
|
||||
</button>
|
||||
<button className="btn btn-ghost" disabled={radioLaedt || !radio.laeuft}
|
||||
onClick={() => radioSteuern('skip')}>
|
||||
⏭️ Überspringen
|
||||
|
||||
+76
-17
@@ -28,6 +28,7 @@ import {
|
||||
} from '../db.js';
|
||||
import {
|
||||
laeuft, weiter, spielen, beiAenderung, tafelAktualisieren, tafelnMelden, radioQuelle,
|
||||
pausieren, verstrichen,
|
||||
} from './radio.js';
|
||||
import {
|
||||
aufloesen, ytdlpVorhanden, letzterFehler, TREFFER,
|
||||
@@ -95,9 +96,10 @@ export function musikEmbed(client, guildId) {
|
||||
|
||||
if (jetzt?.quelle?.art === 'youtube') {
|
||||
const t = jetzt.quelle.titel;
|
||||
const verstrichen = Math.floor((Date.now() - jetzt.seit) / 1000);
|
||||
const balken = fortschritt(verstrichen, t.dauer);
|
||||
embed.setDescription(`**${kurz(t.titel, 200)}**\n`
|
||||
// Pausen abgezogen — sonst behauptet der Balken, ein angehaltener
|
||||
// Titel liefe weiter, und läuft beim Fortsetzen voraus.
|
||||
const balken = fortschritt(verstrichen(jetzt), t.dauer);
|
||||
embed.setDescription(`${jetzt.pausiertSeit ? '⏸️ ' : ''}**${kurz(t.titel, 200)}**\n`
|
||||
+ `${t.kanal ? `_${kurz(t.kanal, 80)}_\n` : ''}`
|
||||
+ (balken ? `${balken}\n` : '🔴 _Livestream_\n')
|
||||
+ `in <#${jetzt.channelId}>`);
|
||||
@@ -107,7 +109,7 @@ export function musikEmbed(client, guildId) {
|
||||
// auch nicht als einer dargestellt.
|
||||
const heim = jetzt.heimStationId ? getStation(jetzt.heimStationId) : null;
|
||||
embed.setDescription(`📻 Gerade läuft das Radio${heim ? ` (**${heim.name}**)` : ''}.\n`
|
||||
+ 'Was hier eingereiht wird, kommt als Nächstes dran.');
|
||||
+ 'Was hier eingereiht wird, übernimmt sofort — danach geht es zurück auf den Sender.');
|
||||
} else {
|
||||
embed.setDescription(liste.length > 0
|
||||
? 'Nichts läuft. **Überspringen** startet die Warteschlange — '
|
||||
@@ -149,7 +151,7 @@ export function musikKomponenten(guildId) {
|
||||
const reihen = [];
|
||||
|
||||
if (liste.length > 0) {
|
||||
const optionen = (praefix) => liste.map((e, i) => {
|
||||
const optionen = () => liste.map((e, i) => {
|
||||
const o = new StringSelectMenuOptionBuilder()
|
||||
.setLabel(kurz(`${i + 1}. ${e.titel}`, 100))
|
||||
.setValue(String(e.id));
|
||||
@@ -171,13 +173,30 @@ export function musikKomponenten(guildId) {
|
||||
// an der Radio-Tafel: ein Knopf, der sich drücken lässt und nichts
|
||||
// bewirkt, sieht aus wie ein kaputter Knopf.
|
||||
const nichtsDa = liste.length === 0;
|
||||
const ytLaeuft = jetzt?.quelle?.art === 'youtube';
|
||||
const pausiert = Boolean(jetzt?.pausiertSeit);
|
||||
|
||||
// Zwei Knopfreihen statt einer: mit der Pause wären es sechs Knöpfe, und
|
||||
// Discord lässt nur fünf je Reihe zu. Platz ist da — zwei Auswahlmenüs
|
||||
// plus zwei Knopfreihen sind vier von fünf möglichen Reihen.
|
||||
//
|
||||
// Die Trennung ist ausserdem die sinnvollere: oben, was gerade klingt,
|
||||
// unten, was mit der Liste passiert.
|
||||
reihen.push(new ActionRowBuilder().addComponents(
|
||||
// Pause gibt es nur bei YouTube. Warum, steht bei `pausieren()` in
|
||||
// `radio.js` — kurz: einen Dauerstrom anzuhalten heisst, ihm
|
||||
// hinterherzuhinken.
|
||||
pausiert
|
||||
? knopf('musik_pause', 'Weiter', '▶️', ButtonStyle.Success)
|
||||
: knopf('musik_pause', 'Pause', '⏸️', ButtonStyle.Primary, !ytLaeuft),
|
||||
knopf('musik_skip', 'Überspringen', '⏭️', ButtonStyle.Primary,
|
||||
nichtsDa && !ytLaeuft),
|
||||
knopf('musik_heim', 'Zum Radio', '📻', ButtonStyle.Secondary,
|
||||
!ytLaeuft || !jetzt.heimStationId),
|
||||
));
|
||||
reihen.push(new ActionRowBuilder().addComponents(
|
||||
knopf('musik_add', 'Einreihen…', '▶️', ButtonStyle.Success),
|
||||
knopf('musik_skip', 'Überspringen', '⏭️', ButtonStyle.Primary,
|
||||
nichtsDa && jetzt?.quelle?.art !== 'youtube'),
|
||||
knopf('musik_leeren', 'Leeren', '🗑️', ButtonStyle.Danger, nichtsDa),
|
||||
knopf('musik_heim', 'Zum Radio', '📻', ButtonStyle.Secondary,
|
||||
!jetzt || jetzt.quelle?.art !== 'youtube' || !jetzt.heimStationId),
|
||||
knopf('musik_refresh', 'Aktualisieren', '🔄'),
|
||||
));
|
||||
return reihen;
|
||||
@@ -229,19 +248,55 @@ export function eintraegeAufnehmen(guildId, eintraege, wunschVon = null) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Läuft nichts, soll das Einreihen sofort Musik machen.
|
||||
* Nach dem Einreihen: soll das jetzt losgehen?
|
||||
*
|
||||
* Sonst legt jemand drei Titel in eine Liste, es passiert nichts, und der
|
||||
* naheliegende Schluss wäre „kaputt". Läuft dagegen schon etwas — Sender oder
|
||||
* Titel —, wird nicht dazwischengefunkt: die Liste kommt dran, wenn sie dran
|
||||
* ist.
|
||||
* Drei Fälle, und der mittlere ist der, den ich am 28.08.2026 falsch hatte:
|
||||
*
|
||||
* nichts läuft → die Liste fängt an. Sonst legt jemand drei Titel hinein,
|
||||
* es passiert nichts, und der naheliegende Schluss wäre
|
||||
* „kaputt".
|
||||
* Sender läuft → **der Titel übernimmt sofort.** „Das Radio ist die
|
||||
* Grundstellung, YouTube die Unterbrechung" — und eine
|
||||
* Unterbrechung, die wartet, ist keine. Hier stand vorher
|
||||
* „die Liste kommt dran, wenn sie dran ist", und das war
|
||||
* beim Sender schlicht falsch: ein Sender endet nie, also
|
||||
* gibt es kein `Idle`, das weiterschaltet. Der einzige
|
||||
* `Idle`, den es gibt, ist ein abgerissener Strom — und der
|
||||
* startet denselben Sender neu. Der Titel wartete also auf
|
||||
* einen Moment, der nie kommt. Im Betrieb gemeldet: Link
|
||||
* eingereiht, Bot blieb beim Radio.
|
||||
* Der Sender ist nicht verloren: er steht als `heimStation`
|
||||
* im laufenden Eintrag und kommt zurück, wenn die Liste
|
||||
* leer ist.
|
||||
* Titel läuft → nicht dazwischenfunken. Der Neue reiht sich ein, und
|
||||
* *hier* stimmt „kommt dran, wenn er dran ist" auch.
|
||||
*/
|
||||
export async function ggfAnwerfen(client, guildId, kanalId) {
|
||||
if (laeuft(guildId) || !kanalId) return false;
|
||||
const q = await weiter(client, guildId, { erzwungen: true, kanalId });
|
||||
const jetzt = laeuft(guildId);
|
||||
if (!uebernimmtSofort(jetzt)) return false;
|
||||
// Ohne eigene Angabe der Kanal, in dem der Bot schon steht.
|
||||
const ziel = jetzt?.channelId ?? kanalId;
|
||||
if (!ziel) return false;
|
||||
const q = await weiter(client, guildId, { erzwungen: true, kanalId: ziel });
|
||||
return Boolean(q);
|
||||
}
|
||||
|
||||
/**
|
||||
* Die Regel als eigener Satz — weil genau sie falsch war.
|
||||
*
|
||||
* Sie steckte als Bedingung mitten in `ggfAnwerfen` (`if (laeuft(guildId))
|
||||
* return false`) und war von aussen weder zu lesen noch zu prüfen. Hier hat
|
||||
* sie einen Namen und lässt sich mit allen drei Eingaben durchspielen.
|
||||
*
|
||||
* @param {object|null} jetzt Der laufende Eintrag, oder null
|
||||
* @returns {boolean} true = der neue Titel fängt sofort an
|
||||
*/
|
||||
export function uebernimmtSofort(jetzt) {
|
||||
// Nur ein laufender YouTube-Titel wird nicht unterbrochen. Ein Sender
|
||||
// schon — er ist die Grundstellung, kein Vorrang.
|
||||
return jetzt?.quelle?.art !== 'youtube';
|
||||
}
|
||||
|
||||
// ── Suchtreffer zwischenlagern ────────────────────────────────────────────
|
||||
//
|
||||
// Ein Discord-Auswahlmenü gibt nur die `value` zurück, und da passt keine
|
||||
@@ -300,7 +355,7 @@ async function zielKanal(interaction) {
|
||||
}
|
||||
|
||||
const IDS = ['musik_add', 'musik_skip', 'musik_leeren', 'musik_heim', 'musik_refresh',
|
||||
'musik_vor', 'musik_weg'];
|
||||
'musik_pause', 'musik_vor', 'musik_weg'];
|
||||
|
||||
/** Knöpfe, Menüs und das Eingabefenster der Musik-Tafel */
|
||||
export async function handleMusikInteraction(interaction) {
|
||||
@@ -402,6 +457,10 @@ export async function handleMusikInteraction(interaction) {
|
||||
// und das über den drei Sekunden liegt.
|
||||
await interaction.deferUpdate();
|
||||
await weiter(interaction.client, guildId, { erzwungen: true, kanalId: kanal.id });
|
||||
} else if (customId === 'musik_pause') {
|
||||
// Kein Aufschub nötig: Anhalten passiert im Speicher, ohne Netz.
|
||||
pausieren(guildId);
|
||||
await interaction.deferUpdate();
|
||||
} else if (customId === 'musik_leeren') {
|
||||
queueClear(guildId);
|
||||
await interaction.deferUpdate();
|
||||
|
||||
+100
-5
@@ -51,7 +51,7 @@ import {
|
||||
import { moduleEnabled } from '../modules.js';
|
||||
import {
|
||||
listStations, getStation, radioState, radioStates, setRadioState, clearRadioState, setSetting,
|
||||
queueNext, queueAdd, queueVor,
|
||||
queueNext, queueAdd, queueVor, queueStand,
|
||||
} from '../db.js';
|
||||
import { senderGrundstock } from './radio-sender.js';
|
||||
import { tonstrom, ytdlpPruefen, ytdlpVorhanden } from './youtube.js';
|
||||
@@ -177,8 +177,14 @@ export async function titelHolen(url, { timeoutMs = 8000, bloecke = 3 } = {}) {
|
||||
* ihn sonst endgültig beenden. Bei einer Rohrleitung wäre dieselbe Option
|
||||
* sinnlos — es gibt nichts, wohin sich neu verbinden liesse.
|
||||
*
|
||||
* `netz` entscheidet ausserdem, ob stdin offen ist, und das ist keine
|
||||
* Feinheit: bei `-i pipe:0` MUSS stdin eine Rohrleitung sein. Stand hier
|
||||
* `ignore` (wie es bis zum 28.08.2026 der Fall war), zeigt `pipe:0` auf
|
||||
* /dev/null — ffmpeg meldet dann „Invalid data found when processing input",
|
||||
* und `ff.stdin` ist `null`, woran der Aufbau der Kette vorher zerbrach.
|
||||
*
|
||||
* @param {string} eingabe Adresse oder `pipe:0`
|
||||
* @param {{netz: boolean}} opts
|
||||
* @param {{netz: boolean}} opts netz=true: ffmpeg holt selbst; false: es liest aus stdin
|
||||
*/
|
||||
function wandler(eingabe, { netz }) {
|
||||
const ff = spawn(FFMPEG, [
|
||||
@@ -195,7 +201,8 @@ function wandler(eingabe, { netz }) {
|
||||
'-f', 's16le', '-acodec', 'pcm_s16le',
|
||||
'-ar', '48000', '-ac', '2',
|
||||
'pipe:1',
|
||||
], { stdio: ['ignore', 'pipe', 'pipe'] });
|
||||
// Kein stdin bei einer Adresse, eine Rohrleitung bei `pipe:0`.
|
||||
], { stdio: [netz ? 'ignore' : 'pipe', 'pipe', 'pipe'] });
|
||||
// Ohne Zuhörer auf stderr läuft die Pipe voll und ffmpeg bleibt stehen
|
||||
ff.stderr.on('data', (d) => {
|
||||
const text = d.toString().trim();
|
||||
@@ -223,6 +230,17 @@ function tonquelle(quelle) {
|
||||
}
|
||||
const yt = tonstrom(quelle.titel.url);
|
||||
const ff = wandler('pipe:0', { netz: false });
|
||||
// Startet ein Prozess nicht, sind seine Rohrenden `null`. Ohne diese
|
||||
// Prüfung wirft schon das Anhängen der Fehlerbehandlung einen
|
||||
// TypeError — und der reisst die ganze Interaktion mit, statt dass
|
||||
// jemand „kein Ton" zu sehen bekommt.
|
||||
if (!yt.stdout || !ff.stdin) {
|
||||
yt.kill('SIGKILL');
|
||||
ff.kill('SIGKILL');
|
||||
throw new Error('Tonkette liess sich nicht aufbauen — '
|
||||
+ `yt-dlp: ${yt.stdout ? 'ok' : 'kein Ausgang'}, `
|
||||
+ `ffmpeg: ${ff.stdin ? 'ok' : 'kein Eingang'}.`);
|
||||
}
|
||||
yt.stdout.on('error', () => {});
|
||||
ff.stdin.on('error', () => {});
|
||||
yt.stdout.pipe(ff.stdin);
|
||||
@@ -485,17 +503,80 @@ export function lautstaerkeSetzen(guildId, wunsch) {
|
||||
return wert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wie lange läuft der aktuelle Titel schon — Pausen abgezogen.
|
||||
*
|
||||
* An einer Stelle, weil drei Stellen es brauchen: die Musik-Tafel, das Panel
|
||||
* und der Fortschrittsbalken. Wer das je einzeln rechnet, vergisst an einer
|
||||
* davon die Pause, und dann behauptet ein Balken, ein angehaltener Titel
|
||||
* liefe weiter.
|
||||
*
|
||||
* @returns {number} Sekunden
|
||||
*/
|
||||
export function verstrichen(eintrag) {
|
||||
if (!eintrag) return 0;
|
||||
const offen = eintrag.pausiertSeit ? Date.now() - eintrag.pausiertSeit : 0;
|
||||
const netto = Date.now() - eintrag.seit - (eintrag.pauseGesamt ?? 0) - offen;
|
||||
return Math.max(0, Math.floor(netto / 1000));
|
||||
}
|
||||
|
||||
/** Läuft gerade etwas, das angehalten ist? */
|
||||
export const istPausiert = (guildId) => Boolean(laufend.get(guildId)?.pausiertSeit);
|
||||
|
||||
/**
|
||||
* Anhalten und weiterlaufen lassen.
|
||||
*
|
||||
* **Nur für YouTube, und das ist kein Vergessen.** Ein Sender ist ein
|
||||
* Dauerstrom: hält man ihn an, läuft er beim Sender weiter, während sich
|
||||
* hier die Leitung füllt. Beim Fortsetzen käme dann nicht „dort, wo es
|
||||
* aufhörte", sondern eine Konserve, die immer weiter hinterherhinkt. Für
|
||||
* einen Sender heisst Pause deshalb Stopp — und dafür gibt es den
|
||||
* Stopp-Knopf.
|
||||
*
|
||||
* @param {string} guildId
|
||||
* @param {boolean|null} wunsch true/false, oder null zum Umschalten
|
||||
* @returns {boolean|null} der neue Zustand, oder null wenn nicht möglich
|
||||
*/
|
||||
export function pausieren(guildId, wunsch = null) {
|
||||
const s = laufend.get(guildId);
|
||||
if (!s || s.quelle?.art !== 'youtube') return null;
|
||||
const soll = wunsch === null ? !s.pausiertSeit : Boolean(wunsch);
|
||||
if (soll === Boolean(s.pausiertSeit)) return soll;
|
||||
if (soll) {
|
||||
// `true` schiebt Stille nach, statt die Verbindung stumm hängen zu
|
||||
// lassen — sonst hält Discord den Sprecher für abgestürzt.
|
||||
s.player.pause(true);
|
||||
s.pausiertSeit = Date.now();
|
||||
} else {
|
||||
s.pauseGesamt = (s.pauseGesamt ?? 0) + (Date.now() - s.pausiertSeit);
|
||||
s.pausiertSeit = null;
|
||||
s.player.unpause();
|
||||
}
|
||||
return soll;
|
||||
}
|
||||
|
||||
/** Alles abräumen — Verbindung, Spieler, ffmpeg */
|
||||
export function stoppen(guildId) {
|
||||
const s = laufend.get(guildId);
|
||||
let abgeraeumt;
|
||||
if (s) {
|
||||
s.player.stop(true);
|
||||
ketteAbraeumen(s);
|
||||
// Reihenfolge ist hier alles: **erst aus der Karte, dann anhalten.**
|
||||
//
|
||||
// `player.stop()` meldet den Leerlauf SYNCHRON — gemessen am
|
||||
// 28.08.2026 mit @discordjs/voice 0.19: der Idle-Melder läuft
|
||||
// mitten in `stop()`, noch bevor die nächste Zeile dran ist. Stand
|
||||
// `laufend.delete` danach, fand der Melder den Eintrag noch, hielt
|
||||
// das Ende für „Titel vorbei" und schaltete weiter — auf den
|
||||
// nächsten Titel oder zurück auf den Heimsender.
|
||||
//
|
||||
// Von aussen sah das so aus: der letzte verlässt den Kanal, der Bot
|
||||
// geht raus und steht sofort wieder drin. Genau so gemeldet.
|
||||
laufend.delete(guildId);
|
||||
// Sonst zählt der nächste Start ab dem alten Stand weiter und bricht
|
||||
// womöglich sofort wieder ab.
|
||||
fehlschlaege.delete(guildId);
|
||||
s.player.stop(true);
|
||||
ketteAbraeumen(s);
|
||||
// Die Statuszeile bleibt sonst stehen und behauptet stundenlang, es
|
||||
// liefe ein Lied, das längst vorbei ist. Leerer Text räumt sie ab.
|
||||
//
|
||||
@@ -657,6 +738,11 @@ export async function spielen(kanal, quelle) {
|
||||
// die Vorgabe ist leise, damit niemand erschrickt.
|
||||
resource.volume?.setVolume(radioLautstaerke() / 100);
|
||||
player.play(resource);
|
||||
// Der Spieler wird über Quellenwechsel hinweg wiederverwendet. Stand er
|
||||
// beim Umschalten auf Pause, bliebe der neue Titel stumm stehen — und der
|
||||
// neue Eintrag sagt „läuft". Zwei Aussagen, eine Wirklichkeit: hier wird
|
||||
// sie geradegezogen.
|
||||
player.unpause();
|
||||
verbindung.subscribe(player);
|
||||
|
||||
// Faengt der Ton wirklich an? Wenn ffmpeg fehlt oder der Sender nichts
|
||||
@@ -715,6 +801,10 @@ export async function spielen(kanal, quelle) {
|
||||
heimStationId: quelle.art === 'radio' ? quelle.station.id : (vorher?.heimStationId ?? null),
|
||||
titel: quelle.art === 'youtube' ? quelle.titel.titel : null,
|
||||
seit: Date.now(), tonLaeuft: null, client: kanal.client,
|
||||
// Angehalten seit wann, und wie viel Pause insgesamt schon drin war.
|
||||
// Ohne das zweite liefe der Fortschrittsbalken nach dem Fortsetzen
|
||||
// um die Pausenlänge voraus.
|
||||
pausiertSeit: null, pauseGesamt: 0,
|
||||
};
|
||||
laufend.set(guildId, eintrag);
|
||||
// Neuer Anlauf für die Statuszeile: vielleicht wurde das Recht inzwischen
|
||||
@@ -815,6 +905,11 @@ export async function weiter(client, guildId, { erzwungen = false, kanalId: star
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
// Der Wechsel gehört ins Log: von aussen sieht „YouTube uebernimmt"
|
||||
// genauso aus wie „nichts passiert", und der Unterschied ist genau
|
||||
// der, den es hier zu sehen gibt.
|
||||
console.log(`[radio] ${quelle.art === 'youtube' ? '▶️' : '📻'} ${quelleName(quelle)}`
|
||||
+ `${naechster ? ` (noch ${queueStand(guildId).n} in der Liste)` : ' — Warteschlange leer'}`);
|
||||
const tafel = radioState(guildId);
|
||||
setRadioState({
|
||||
guildId,
|
||||
|
||||
+17
-4
@@ -28,7 +28,7 @@ import {
|
||||
createService, updateService, listServices, deleteService,
|
||||
savePage, getPage, listPages, listPublishedPages, deletePage,
|
||||
lsState, lsModSizes, setLsModSize,
|
||||
listStations, createStation, updateStation, deleteStation,
|
||||
listStations, getStation, createStation, updateStation, deleteStation,
|
||||
queueList, queueRemove, queueClear, queueReorder, queueVor, queueStand,
|
||||
setRadioState, clearRadioState, radioState,
|
||||
} from '../db.js';
|
||||
@@ -40,7 +40,7 @@ import { lastResults, GAME_ICONS, spielName, spielListe, zugangStand } from '../
|
||||
import { istLsServer, modBasis } from '../bot/ls-farm.js';
|
||||
import {
|
||||
titelHolen, laeuft, spielen, stoppen, weiter, radioQuelle,
|
||||
lautstaerkeSetzen, ffmpegVorhanden, tafelnMelden, titelMerken,
|
||||
lautstaerkeSetzen, ffmpegVorhanden, tafelnMelden, titelMerken, pausieren, verstrichen,
|
||||
} from '../bot/radio.js';
|
||||
import { einreihen, eintraegeAufnehmen, ggfAnwerfen } from '../bot/radio-musik.js';
|
||||
import {
|
||||
@@ -469,8 +469,10 @@ export function registerApiRoutes(app, client) {
|
||||
kanal: t?.kanal ?? null,
|
||||
// Sekunden statt eines Zeitstempels: der Browser muesste sonst
|
||||
// gegen die Uhr des Servers rechnen, und die beiden gehen selten
|
||||
// gleich.
|
||||
verstrichen: Math.floor((Date.now() - jetzt.seit) / 1000),
|
||||
// gleich. `verstrichen()` zieht Pausen ab -- dieselbe Rechnung
|
||||
// wie in der Discord-Tafel, nicht eine zweite daneben.
|
||||
verstrichen: verstrichen(jetzt),
|
||||
pausiert: Boolean(jetzt.pausiertSeit),
|
||||
dauer: t ? t.dauer : null,
|
||||
kanalId: jetzt.channelId,
|
||||
kanalName: kanal?.name ?? null,
|
||||
@@ -540,6 +542,17 @@ export function registerApiRoutes(app, client) {
|
||||
// das am Ergebnis sehen.
|
||||
await tafelnMelden(client, guildId);
|
||||
return { ok: true, lautstaerke: wert, laeuft: radioStand(guild) };
|
||||
} else if (aktion === 'pause') {
|
||||
// Gibt null zurueck, wenn nichts oder ein Sender laeuft --
|
||||
// dann ist Pause nicht vorgesehen, und das wird gesagt statt
|
||||
// still nichts zu tun.
|
||||
const neu = pausieren(guildId, b.wert === undefined ? null : Boolean(b.wert));
|
||||
if (neu === null) {
|
||||
return reply.code(400).send({
|
||||
error: 'Pause gibt es nur bei YouTube -- ein Sender laesst sich nicht anhalten, '
|
||||
+ 'nur stoppen.',
|
||||
});
|
||||
}
|
||||
} else if (aktion === 'skip') {
|
||||
const kanalId = zielId();
|
||||
if (!kanalId) return reply.code(400).send({ error: 'Kein Sprachkanal.' });
|
||||
|
||||
Reference in New Issue
Block a user