Fix multi channel happenings
This commit is contained in:
@@ -18,45 +18,64 @@ export interface AudioServiceOptions {
|
|||||||
tts: TTSRegistry;
|
tts: TTSRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SUMMON_LOCK_MS = 60_000;
|
||||||
|
|
||||||
|
export interface MoveOptions {
|
||||||
|
summon?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export class AudioService {
|
export class AudioService {
|
||||||
readonly player: AudioPlayer = createAudioPlayer();
|
readonly player: AudioPlayer = createAudioPlayer();
|
||||||
readonly queue: AudioQueue = new AudioQueue(this.player);
|
readonly queue: AudioQueue = new AudioQueue(this.player);
|
||||||
|
|
||||||
private joinedChannels: VoiceBasedChannel[] = [];
|
private currentChannel: VoiceBasedChannel | undefined;
|
||||||
private connections = new Map<VoiceBasedChannel, VoiceConnection>();
|
private currentConnection: VoiceConnection | undefined;
|
||||||
|
private summonLockUntil = 0;
|
||||||
|
|
||||||
constructor(private readonly opts: AudioServiceOptions) {}
|
constructor(private readonly opts: AudioServiceOptions) {}
|
||||||
|
|
||||||
getActiveVoiceChannel(): VoiceBasedChannel | undefined {
|
getCurrentChannel(): VoiceBasedChannel | undefined {
|
||||||
return this.joinedChannels[0];
|
return this.currentChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
isInVoiceChannel(channel: VoiceBasedChannel): boolean {
|
getCurrentConnection(): VoiceConnection | undefined {
|
||||||
return this.joinedChannels.includes(channel);
|
return this.currentConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
getConnectionForVoiceChannel(channel: VoiceBasedChannel): VoiceConnection | undefined {
|
isInChannel(channel: VoiceBasedChannel): boolean {
|
||||||
return this.connections.get(channel);
|
return this.currentChannel?.id === channel.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
async joinChannel(channel: VoiceBasedChannel): Promise<void> {
|
isSummonLocked(): boolean {
|
||||||
if (this.isInVoiceChannel(channel)) return;
|
if (this.summonLockUntil <= Date.now()) return false;
|
||||||
|
// Release the lock early if the summon channel has only the bot left.
|
||||||
|
if ((this.currentChannel?.members.size ?? 0) < 2) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async moveTo(channel: VoiceBasedChannel, opts: MoveOptions = {}): Promise<void> {
|
||||||
|
if (opts.summon) this.summonLockUntil = Date.now() + SUMMON_LOCK_MS;
|
||||||
|
if (this.currentChannel?.id === channel.id) return;
|
||||||
|
|
||||||
const connection = joinVoiceChannel({
|
const connection = joinVoiceChannel({
|
||||||
channelId: channel.id,
|
channelId: channel.id,
|
||||||
guildId: channel.guild.id,
|
guildId: channel.guild.id,
|
||||||
adapterCreator: createAdapter(channel),
|
adapterCreator: createAdapter(channel),
|
||||||
});
|
});
|
||||||
connection.subscribe(this.player);
|
connection.subscribe(this.player);
|
||||||
this.joinedChannels.push(channel);
|
|
||||||
this.connections.set(channel, connection);
|
this.currentConnection?.destroy();
|
||||||
|
this.currentChannel = channel;
|
||||||
|
this.currentConnection = connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
async leaveChannel(channel: VoiceBasedChannel): Promise<void> {
|
async leave(): Promise<void> {
|
||||||
if (!this.isInVoiceChannel(channel)) return;
|
if (!this.currentConnection) return;
|
||||||
const conn = this.connections.get(channel);
|
this.queue.flush();
|
||||||
this.joinedChannels = this.joinedChannels.filter((c) => c !== channel);
|
this.currentConnection.destroy();
|
||||||
conn?.disconnect();
|
this.currentConnection = undefined;
|
||||||
this.connections.delete(channel);
|
this.currentChannel = undefined;
|
||||||
|
this.summonLockUntil = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,21 +1,67 @@
|
|||||||
|
import type { Guild, VoiceBasedChannel } from "discord.js";
|
||||||
|
import { ChannelType } from "discord.js";
|
||||||
import type { Module } from "./types.js";
|
import type { Module } from "./types.js";
|
||||||
|
|
||||||
|
function chooseTargetChannel(
|
||||||
|
guild: Guild,
|
||||||
|
recentEventChannel: VoiceBasedChannel | null | undefined,
|
||||||
|
currentChannelId: string | undefined,
|
||||||
|
): VoiceBasedChannel | undefined {
|
||||||
|
let best: VoiceBasedChannel | undefined;
|
||||||
|
let bestCount = 0;
|
||||||
|
|
||||||
|
for (const ch of guild.channels.cache.values()) {
|
||||||
|
if (ch.type !== ChannelType.GuildVoice && ch.type !== ChannelType.GuildStageVoice) continue;
|
||||||
|
const voice = ch as VoiceBasedChannel;
|
||||||
|
const humans = voice.members.filter((m) => !m.user.bot).size;
|
||||||
|
if (humans === 0) continue;
|
||||||
|
|
||||||
|
if (humans > bestCount) {
|
||||||
|
best = voice;
|
||||||
|
bestCount = humans;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (humans === bestCount && best) {
|
||||||
|
// Tie-break: prefer the channel that just had activity, then the bot's current channel.
|
||||||
|
if (recentEventChannel && voice.id === recentEventChannel.id) {
|
||||||
|
best = voice;
|
||||||
|
} else if (
|
||||||
|
currentChannelId &&
|
||||||
|
voice.id === currentChannelId &&
|
||||||
|
best.id !== recentEventChannel?.id
|
||||||
|
) {
|
||||||
|
best = voice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
export const announcer: Module = ({ client, audio, tts, t }) => {
|
export const announcer: Module = ({ client, audio, tts, t }) => {
|
||||||
client.on("voiceStateUpdate", async (oldState, newState) => {
|
client.on("voiceStateUpdate", async (oldState, newState) => {
|
||||||
if (newState.member?.user.bot) return;
|
if (newState.member?.user.bot) return;
|
||||||
if (oldState.channel && newState.channel) return;
|
|
||||||
const channel = oldState.channel ?? newState.channel;
|
|
||||||
if (!channel) return;
|
|
||||||
|
|
||||||
const joined = !oldState.channel;
|
const guild = newState.guild ?? oldState.guild;
|
||||||
|
const recentChannel = newState.channel ?? oldState.channel;
|
||||||
|
|
||||||
if (!joined && channel.members.size < 2) {
|
if (!audio.isSummonLocked()) {
|
||||||
audio.queue.flush();
|
const target = chooseTargetChannel(guild, recentChannel, audio.getCurrentChannel()?.id);
|
||||||
await audio.leaveChannel(channel);
|
const current = audio.getCurrentChannel();
|
||||||
return;
|
if (!target && current) {
|
||||||
|
await audio.leave();
|
||||||
|
} else if (target && target.id !== current?.id) {
|
||||||
|
await audio.moveTo(target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await audio.joinChannel(channel);
|
const joined = !oldState.channel && !!newState.channel;
|
||||||
|
const left = !!oldState.channel && !newState.channel;
|
||||||
|
if (!joined && !left) return;
|
||||||
|
|
||||||
|
const eventChannel = newState.channel ?? oldState.channel;
|
||||||
|
const current = audio.getCurrentChannel();
|
||||||
|
if (!eventChannel || !current || eventChannel.id !== current.id) return;
|
||||||
|
|
||||||
const username = newState.member?.displayName ?? oldState.member?.displayName ?? "someone";
|
const username = newState.member?.displayName ?? oldState.member?.displayName ?? "someone";
|
||||||
const str = joined ? t("USER_JOINED", username) : t("USER_LEFT", username);
|
const str = joined ? t("USER_JOINED", username) : t("USER_LEFT", username);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const summon: Module = ({ audio, commands, rootDir }) => {
|
|||||||
commands.register("summon", async (_args, message) => {
|
commands.register("summon", async (_args, message) => {
|
||||||
const channel = message.member?.voice.channel;
|
const channel = message.member?.voice.channel;
|
||||||
if (!channel) return;
|
if (!channel) return;
|
||||||
await audio.joinChannel(channel);
|
await audio.moveTo(channel, { summon: true });
|
||||||
respond(audio, sysmsg, message, "Hi!");
|
respond(audio, sysmsg, message, "Hi!");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const welcomer: Module = ({ client, audio, config, strings, rootDir }) =>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const voiceChannel = channel as VoiceBasedChannel;
|
const voiceChannel = channel as VoiceBasedChannel;
|
||||||
await audio.joinChannel(voiceChannel);
|
await audio.moveTo(voiceChannel, { summon: true });
|
||||||
audio.queue.add(sysstart);
|
audio.queue.add(sysstart);
|
||||||
await audio.speak(voiceChannel, strings.WELCOME);
|
await audio.speak(voiceChannel, strings.WELCOME);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user