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>
This commit is contained in:
@@ -51,6 +51,17 @@ export async function execute(interaction) {
|
|||||||
});
|
});
|
||||||
return;
|
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
|
// Ohne ffmpeg wuerde der Bot sich hinsetzen und schweigen. Lieber gleich
|
||||||
|
|||||||
+72
-7
@@ -136,6 +136,70 @@ function tonquelle(url) {
|
|||||||
return ff;
|
return ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Adapter zwischen Discord-Verbindung und Sprachbibliothek — mit Protokoll.
|
||||||
|
*
|
||||||
|
* Ein Beitritt zum Sprachkanal ist ein Dreischritt, und `signalling` heisst
|
||||||
|
* nur „einer davon fehlt", ohne zu sagen welcher:
|
||||||
|
*
|
||||||
|
* 1. Der Bot bittet Discord über die bestehende Verbindung um den Beitritt.
|
||||||
|
* 2. Discord antwortet mit dem eigenen Sprachstatus (darin die Sitzungs-ID).
|
||||||
|
* 3. Discord nennt den Sprachserver (Adresse und Zugangswort).
|
||||||
|
*
|
||||||
|
* Fehlt 1, ist die Verbindung zu Discord nicht bereit. Fehlt 2, kommen die
|
||||||
|
* Sprachstatus-Meldungen nicht an — dann fehlt der Intent oder der eigene
|
||||||
|
* Mitgliedseintrag. Fehlt 3, hat Discord den Beitritt abgelehnt, praktisch
|
||||||
|
* immer wegen fehlender Rechte im Kanal.
|
||||||
|
*
|
||||||
|
* Ohne diese drei Häkchen bleibt nur Raten, und Raten hat hier schon zweimal
|
||||||
|
* nicht gereicht.
|
||||||
|
*/
|
||||||
|
function beobachteterAdapter(guild, schritte) {
|
||||||
|
const echt = guild.voiceAdapterCreator;
|
||||||
|
return (methods) => {
|
||||||
|
const gepatcht = {
|
||||||
|
...methods,
|
||||||
|
onVoiceStateUpdate: (daten) => {
|
||||||
|
schritte.eigenerStatus = true;
|
||||||
|
return methods.onVoiceStateUpdate(daten);
|
||||||
|
},
|
||||||
|
onVoiceServerUpdate: (daten) => {
|
||||||
|
schritte.sprachserver = true;
|
||||||
|
return methods.onVoiceServerUpdate(daten);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const adapter = echt(gepatcht);
|
||||||
|
return {
|
||||||
|
...adapter,
|
||||||
|
sendPayload: (daten) => {
|
||||||
|
const ok = adapter.sendPayload(daten);
|
||||||
|
schritte.angefragt = ok;
|
||||||
|
return ok;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Aus den drei Häkchen wird ein Satz, der sagt, was zu tun ist */
|
||||||
|
export function beitrittsDiagnose(schritte) {
|
||||||
|
if (schritte.angefragt === false) {
|
||||||
|
return 'Die Verbindung zu Discord war nicht bereit — der Beitritt ging gar nicht erst raus.';
|
||||||
|
}
|
||||||
|
if (!schritte.eigenerStatus && !schritte.sprachserver) {
|
||||||
|
return 'Discord hat auf den Beitritt nicht geantwortet. Das ist fast immer eine '
|
||||||
|
+ 'fehlende Berechtigung **Verbinden** im Sprachkanal selbst.';
|
||||||
|
}
|
||||||
|
if (!schritte.sprachserver) {
|
||||||
|
return 'Discord kennt den Beitritt, nennt aber keinen Sprachserver — '
|
||||||
|
+ 'darf der Bot in diesem Kanal sprechen?';
|
||||||
|
}
|
||||||
|
if (!schritte.eigenerStatus) {
|
||||||
|
return 'Der Sprachserver steht, aber der eigene Sprachstatus kam nie an — '
|
||||||
|
+ 'dem Bot fehlt der Intent **GuildVoiceStates**.';
|
||||||
|
}
|
||||||
|
return 'Der Austausch mit dem Sprachserver kam nicht zu Ende — ausgehendes UDP dicht?';
|
||||||
|
}
|
||||||
|
|
||||||
/** Läuft gerade etwas auf diesem Server? */
|
/** Läuft gerade etwas auf diesem Server? */
|
||||||
export const laeuft = (guildId) => laufend.get(guildId) ?? null;
|
export const laeuft = (guildId) => laufend.get(guildId) ?? null;
|
||||||
|
|
||||||
@@ -161,13 +225,16 @@ export async function spielen(kanal, station) {
|
|||||||
const guildId = kanal.guild.id;
|
const guildId = kanal.guild.id;
|
||||||
const vorher = laufend.get(guildId);
|
const vorher = laufend.get(guildId);
|
||||||
|
|
||||||
|
// Wird nur gefüllt, wenn wirklich neu verbunden wird
|
||||||
|
const schritte = { angefragt: null, eigenerStatus: false, sprachserver: false };
|
||||||
|
|
||||||
let verbindung = getVoiceConnection(guildId);
|
let verbindung = getVoiceConnection(guildId);
|
||||||
if (!verbindung || verbindung.joinConfig.channelId !== kanal.id) {
|
if (!verbindung || verbindung.joinConfig.channelId !== kanal.id) {
|
||||||
verbindung?.destroy();
|
verbindung?.destroy();
|
||||||
verbindung = joinVoiceChannel({
|
verbindung = joinVoiceChannel({
|
||||||
channelId: kanal.id,
|
channelId: kanal.id,
|
||||||
guildId,
|
guildId,
|
||||||
adapterCreator: kanal.guild.voiceAdapterCreator,
|
adapterCreator: beobachteterAdapter(kanal.guild, schritte),
|
||||||
selfDeaf: true, // der Bot muss nicht hören, nur senden
|
selfDeaf: true, // der Bot muss nicht hören, nur senden
|
||||||
});
|
});
|
||||||
// Discord verschiebt Sprachserver im Betrieb; ohne dieses Zurückholen
|
// Discord verschiebt Sprachserver im Betrieb; ohne dieses Zurückholen
|
||||||
@@ -213,12 +280,10 @@ export async function spielen(kanal, station) {
|
|||||||
const steckt = verbindung.state.status;
|
const steckt = verbindung.state.status;
|
||||||
verbindung.destroy();
|
verbindung.destroy();
|
||||||
laufend.delete(guildId);
|
laufend.delete(guildId);
|
||||||
const grund = steckt === VoiceConnectionStatus.Connecting
|
const grund = beitrittsDiagnose(schritte);
|
||||||
? 'Der Sprachserver antwortet nicht auf UDP — ausgehendes UDP ist vermutlich gesperrt.'
|
console.error(`[radio] Beitritt steckt bei "${steckt}" — `
|
||||||
: steckt === VoiceConnectionStatus.Signalling
|
+ `angefragt: ${schritte.angefragt}, eigener Status: ${schritte.eigenerStatus}, `
|
||||||
? 'Discord nennt keinen Sprachserver — fehlen dem Bot Rechte im Kanal?'
|
+ `Sprachserver: ${schritte.sprachserver}`);
|
||||||
: `Die Verbindung blieb bei „${steckt}" stehen.`;
|
|
||||||
console.error(`[radio] Verbindung steckt bei "${steckt}": ${grund}`);
|
|
||||||
throw new Error(`Sprachverbindung kam nicht zustande. ${grund}`);
|
throw new Error(`Sprachverbindung kam nicht zustande. ${grund}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user