Files
d4rkbot/src/bot/commands/radio.js
T
D4rkst3randClaude Opus 5 bbb56e1276
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
Radio: den Beitritt in drei Schritte zerlegen statt "signalling" zu melden
"signalling" sagt nur, dass etwas fehlt, nicht was. Ein Beitritt zum
Sprachkanal ist ein Dreischritt:

  1. Der Bot bittet Discord ueber die bestehende Verbindung um den Beitritt.
  2. Discord antwortet mit dem eigenen Sprachstatus (darin die Sitzungs-ID).
  3. Discord nennt den Sprachserver.

Der Adapter zwischen Verbindung und Sprachbibliothek haelt jetzt fest,
welcher davon stattgefunden hat. Aus den drei Haekchen wird ein Satz, der
sagt, was zu tun ist — fehlende Berechtigung, fehlender Intent, oder ein
Netz, das UDP nicht rauslaesst. Fuenf Faelle, fuenf verschiedene Antworten;
der Test besteht darauf, dass keine zwei gleich lauten.

Dazu eine Pruefung vorweg, die genau dasselbe Bild erzeugt haette: ein
voller Sprachkanal. Discord nimmt die Anfrage entgegen und antwortet dann
einfach nicht mehr — das sieht aus wie ein Rechteproblem. Wer Mitglieder
verschieben darf, kommt am Limit vorbei, sonst nicht.

Nachgesehen und ausgeschlossen: discord.js 14.27 verdrahtet beide
Sprachereignisse (VOICE_STATE_UPDATE ueber die Action, VOICE_SERVER_UPDATE
ueber den Handler), und GuildVoiceStates steht in den Intents.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 12:09:59 +02:00

113 lines
4.5 KiB
JavaScript

// /radio — Steuertafel im Kanal öffnen.
//
// Der Befehl nimmt bewusst keinen Sendernamen entgegen: getippt werden müsste
// er auf dem Handy erst recht, und die Auswahl steht ohnehin im Menü. Ein
// Aufruf, eine Tafel, umschalten per Klick.
import { SlashCommandBuilder, MessageFlags } from 'discord.js';
import { moduleEnabled } from '../../modules.js';
import { listStations, getStation, setRadioState } from '../../db.js';
import { spielen, laeuft, tafelEmbed, tafelKomponenten, titelHolen, ffmpegVorhanden } from '../radio.js';
export const data = new SlashCommandBuilder()
.setName('radio')
.setDescription('Radio im Sprachkanal — Sender wählen und umschalten');
export async function execute(interaction) {
if (!moduleEnabled('radio')) {
await interaction.reply({
content: '❌ Radio ist ausgeschaltet — im Panel unter **Community → Radio** einschalten.',
flags: MessageFlags.Ephemeral,
});
return;
}
const stationen = listStations();
if (stationen.length === 0) {
await interaction.reply({
content: '❌ Es sind noch keine Sender eingetragen — im Panel unter **Community → Radio**.',
flags: MessageFlags.Ephemeral,
});
return;
}
const kanal = interaction.member?.voice?.channel;
const schonAn = laeuft(interaction.guild.id);
if (!kanal && !schonAn) {
await interaction.reply({
content: '❌ Geh erst in einen Sprachkanal.',
flags: MessageFlags.Ephemeral,
});
return;
}
// Darf der Bot da überhaupt rein? Sonst hängt der Verbindungsaufbau, bis
// die Interaktion abgelaufen ist, und der Nutzer sieht nur „Fehler".
if (kanal) {
const rechte = kanal.permissionsFor(interaction.guild.members.me);
if (!rechte?.has('Connect') || !rechte?.has('Speak')) {
await interaction.reply({
content: `❌ Ich darf in <#${kanal.id}> nicht rein oder nicht sprechen.`,
flags: MessageFlags.Ephemeral,
});
return;
}
// Ein voller Kanal sieht aus wie ein Rechteproblem: Discord nimmt die
// Beitrittsanfrage entgegen und antwortet einfach nicht mehr. Nur wer
// Mitglieder verschieben darf, kommt am Limit vorbei.
if (kanal.userLimit > 0 && kanal.members.size >= kanal.userLimit && !rechte.has('MoveMembers')) {
await interaction.reply({
content: `❌ <#${kanal.id}> ist voll (${kanal.members.size}/${kanal.userLimit}). `
+ 'Entweder Platz machen oder mir das Recht **Mitglieder verschieben** geben.',
flags: MessageFlags.Ephemeral,
});
return;
}
}
// Ohne ffmpeg wuerde der Bot sich hinsetzen und schweigen. Lieber gleich
// sagen, woran es liegt, als jemanden im stillen Kanal warten lassen.
if (ffmpegVorhanden() === false) {
await interaction.reply({
content: '❌ Auf dem Server fehlt **ffmpeg** — ohne das kann der Bot keinen Ton '
+ 'umwandeln. Das Image muss neu gebaut werden.',
flags: MessageFlags.Ephemeral,
});
return;
}
// Verbinden dauert länger als die drei Sekunden, die Discord gibt
await interaction.deferReply();
let station = schonAn ? getStation(schonAn.stationId) : null;
let titel = schonAn?.titel ?? null;
if (!schonAn) {
station = stationen[0];
try {
await spielen(kanal, station);
} catch (error) {
console.error('[radio]', error);
await interaction.editReply(`❌ ${error.message || 'Verbindung in den Sprachkanal fehlgeschlagen.'}`);
return;
}
({ titel } = await titelHolen(station.url));
const s = laeuft(interaction.guild.id);
if (s) s.titel = titel;
}
const jetzt = laeuft(interaction.guild.id);
const nachricht = await interaction.editReply({
embeds: [tafelEmbed(interaction.client, station, titel, kanal?.id ?? schonAn?.channelId,
{ tonLaeuft: jetzt?.tonLaeuft, ffmpeg: ffmpegVorhanden() })],
components: tafelKomponenten(stationen, station?.id),
});
// Die Tafel merken, damit der Titel-Melder sie später bearbeiten kann
setRadioState({
guildId: interaction.guild.id,
channelId: kanal?.id ?? schonAn.channelId,
stationId: station?.id ?? null,
panelChannelId: nachricht.channelId ?? interaction.channelId,
panelMessageId: nachricht.id,
});
}