/radio holt den Bot in den Sprachkanal und postet eine Tafel: Auswahlmenue mit allen Sendern, Stopp, und ein Embed mit dem laufenden Titel, das sich alle 30 Sekunden selbst nachfuehrt. Der Titel kommt aus dem Icecast-Strom selbst (Icy-MetaData). Bewusst so und nicht ueber eine Sender-API: das funktioniert bei jedem Strom, den jemand eintraegt, und nicht nur bei den drei, die ich kenne. Leere Titel-Bloecke heissen "unveraendert", deshalb werden bis zu drei gelesen. Die Tonkette braucht keine Opus-Bibliothek: ffmpeg holt das MP3 und gibt direkt Ogg/Opus aus, prism-media packt nur noch aus. Gemessen: 600 Pakete in fuenf Sekunden. Sonst muesste @discordjs/opus mit ins Image, und das will gebaut werden. Neu im Image ist nur ffmpeg. 44 RauteMusik-Sender sind ab Werk eingetragen — dieselben, die im LS25 und ETS als Bordradio laufen. Die Liste ist nicht abgeschrieben, sondern gemessen: jede Adresse einzeln angefragt, der Name kommt aus icy-name. Doppelgaenger sind raus (deutschrap-charts und wackenradio liefern denselben Strom wie deutschrap und metal). Eingetragen wird einmal, solange die Liste leer ist — wer loescht, behaelt es geloescht. Ein Auswahlmenue fasst nur 25 Eintraege. Bei 44 Sendern haette ein Deckel die letzten 19 stillschweigend verschluckt; stattdessen mehrere Menues, benannt nach ihrem ersten und letzten Sender. Der Bot geht raus, wenn der Kanal leer ist, und kommt nach einem Neustart in den Kanal zurueck — sonst beendet jeder Deploy die Musik endgueltig. Kein YouTube. Genau daran sind Groovy und Rythm gestorben, und was davon uebrig ist, ist ein Dauerlauf gegen kaputte Extraktoren. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
74 lines
3.0 KiB
React
74 lines
3.0 KiB
React
// Übersicht aller Slash-Befehle des Bots.
|
|
import { useEffect } from 'react';
|
|
import { IconLock } from '../icons.jsx';
|
|
import { useT } from '../i18n.jsx';
|
|
|
|
// Befehlsname bleibt wie er ist — nur die Erklärung wird übersetzt.
|
|
const GROUPS = [
|
|
['commands.g.everyone', false, [
|
|
['/rank', 'commands.rank'],
|
|
['/wunsch idee:…', 'commands.wunsch'],
|
|
['/bug titel:… beschreibung:…', 'commands.bug'],
|
|
['/geburtstag setzen tag:… monat:…', 'commands.bdaySet'],
|
|
['/geburtstag entfernen', 'commands.bdayDel'],
|
|
['/remind dauer:… text:…', 'commands.remind'],
|
|
['/tag name:…', 'commands.cmdTag'],
|
|
['/ping', 'commands.ping'],
|
|
['/umfrage frage:… a:… b:…', 'commands.umfrage'],
|
|
['/radio', 'commands.radio'],
|
|
]],
|
|
['commands.g.mod', true, [
|
|
['/warn user:… grund:…', 'commands.warn'],
|
|
['/warns user:…', 'commands.warns'],
|
|
['/timeout user:… dauer:…', 'commands.timeout'],
|
|
['/purge anzahl:…', 'commands.purge'],
|
|
]],
|
|
['commands.g.setup', true, [
|
|
['/raid status | an | aus', 'commands.raid'],
|
|
['/giveaway preis:… dauer:… gewinner:…', 'commands.giveaway'],
|
|
['/ticket-setup', 'commands.ticketSetup'],
|
|
['/playtester-setup', 'commands.playtesterSetup'],
|
|
['/devlog-backfill', 'commands.devlogBackfill'],
|
|
['/galerie-backfill', 'commands.galerieBackfill'],
|
|
]],
|
|
];
|
|
|
|
export default function BotCommands() {
|
|
const { t } = useT();
|
|
useEffect(() => { window.scrollTo(0, 0); }, []);
|
|
|
|
return (
|
|
<>
|
|
<section className="page-header">
|
|
<div className="page-bg-text">BEFEHLE</div>
|
|
<div className="page-header-grid" aria-hidden="true" />
|
|
<div className="page-header-content">
|
|
<div className="page-tag">{t('commands.tag')}</div>
|
|
<h1 className="page-title">{t('commands.title')}</h1>
|
|
<p className="page-subtitle">{t('commands.subtitle')}</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="content">
|
|
{GROUPS.map(([groupKey, restricted, commands]) => (
|
|
<div className="roadmap-group" key={groupKey}>
|
|
<h2 className="settings-title">
|
|
// {t(groupKey)}
|
|
{restricted && <IconLock size={13} />}
|
|
</h2>
|
|
<div className="cmd-list">
|
|
{commands.map(([name, descKey]) => (
|
|
<div className="cmd-row" key={name}>
|
|
<code className="cmd-name">{name}</code>
|
|
<span className="cmd-desc">{t(descKey)}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
))}
|
|
<p className="notice">{t('commands.note')}</p>
|
|
</section>
|
|
</>
|
|
);
|
|
}
|