feat: voice states handling
This commit is contained in:
parent
da5f4a64ba
commit
a43df93abe
4 changed files with 63 additions and 16 deletions
|
@ -1,4 +1,4 @@
|
||||||
import { Client, Collection, Guild, Snowflake, User } from "discord.js";
|
import { Client, Collection, Guild, Snowflake, User, VoiceState } from "discord.js";
|
||||||
import { TypedEmitter as EventEmitter } from "tiny-typed-emitter";
|
import { TypedEmitter as EventEmitter } from "tiny-typed-emitter";
|
||||||
import { Queue } from "./Structures/Queue";
|
import { Queue } from "./Structures/Queue";
|
||||||
import { VoiceUtils } from "./VoiceInterface/VoiceUtils";
|
import { VoiceUtils } from "./VoiceInterface/VoiceUtils";
|
||||||
|
@ -31,6 +31,39 @@ class DiscordPlayer extends EventEmitter<PlayerEvents> {
|
||||||
* @type {Discord.Client}
|
* @type {Discord.Client}
|
||||||
*/
|
*/
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
|
||||||
|
this.client.on("voiceStateUpdate", this._handleVoiceState.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleVoiceState(oldState: VoiceState, newState: VoiceState): void {
|
||||||
|
const queue = this.getQueue(oldState.guild.id);
|
||||||
|
if (!queue) return;
|
||||||
|
|
||||||
|
if (oldState.member.id === this.client.user.id && !newState.channelID) {
|
||||||
|
queue.destroy();
|
||||||
|
return void this.emit("botDisconnect", queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!queue.options.leaveOnEmpty || !queue.connection || !queue.connection.channel) return;
|
||||||
|
|
||||||
|
if (!oldState.channelID || newState.channelID) {
|
||||||
|
const emptyTimeout = queue._cooldownsTimeout.get(`empty_${oldState.guild.id}`);
|
||||||
|
const channelEmpty = Util.isVoiceEmpty(queue.connection.channel);
|
||||||
|
|
||||||
|
if (!channelEmpty && emptyTimeout) {
|
||||||
|
clearTimeout(emptyTimeout);
|
||||||
|
queue._cooldownsTimeout.delete(`empty_${oldState.guild.id}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!Util.isVoiceEmpty(queue.connection.channel)) return;
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
if (!Util.isVoiceEmpty(queue.connection.channel)) return;
|
||||||
|
if (!this.queues.has(queue.guild.id)) return;
|
||||||
|
queue.destroy();
|
||||||
|
this.emit("channelEmpty", queue);
|
||||||
|
}, queue.options.leaveOnEmptyCooldown || 0);
|
||||||
|
queue._cooldownsTimeout.set(`empty_${oldState.guild.id}`, timeout);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Guild, StageChannel, VoiceChannel } from "discord.js";
|
import { Collection, Guild, Snowflake, StageChannel, VoiceChannel } from "discord.js";
|
||||||
import { Player } from "../Player";
|
import { Player } from "../Player";
|
||||||
import { StreamDispatcher } from "../VoiceInterface/BasicStreamDispatcher";
|
import { StreamDispatcher } from "../VoiceInterface/BasicStreamDispatcher";
|
||||||
import Track from "./Track";
|
import Track from "./Track";
|
||||||
|
@ -18,6 +18,7 @@ class Queue<T = unknown> {
|
||||||
public playing = false;
|
public playing = false;
|
||||||
public metadata?: T = null;
|
public metadata?: T = null;
|
||||||
public repeatMode: QueueRepeatMode = 0;
|
public repeatMode: QueueRepeatMode = 0;
|
||||||
|
public _cooldownsTimeout = new Collection<string, NodeJS.Timeout>();
|
||||||
|
|
||||||
constructor(player: Player, guild: Guild, options: PlayerOptions = {}) {
|
constructor(player: Player, guild: Guild, options: PlayerOptions = {}) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
|
@ -48,6 +49,10 @@ class Queue<T = unknown> {
|
||||||
return this.connection.audioResource?.metadata ?? this.tracks[0];
|
return this.connection.audioResource?.metadata ?? this.tracks[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nowPlaying() {
|
||||||
|
return this.current;
|
||||||
|
}
|
||||||
|
|
||||||
async connect(channel: StageChannel | VoiceChannel) {
|
async connect(channel: StageChannel | VoiceChannel) {
|
||||||
if (!["stage", "voice"].includes(channel?.type)) throw new TypeError(`Channel type must be voice or stage, got ${channel?.type}!`);
|
if (!["stage", "voice"].includes(channel?.type)) throw new TypeError(`Channel type must be voice or stage, got ${channel?.type}!`);
|
||||||
const connection = await this.player.voiceUtils.connect(channel);
|
const connection = await this.player.voiceUtils.connect(channel);
|
||||||
|
@ -125,8 +130,12 @@ class Queue<T = unknown> {
|
||||||
if (src && (this.playing || this.tracks.length) && !options.immediate) return this.addTrack(src);
|
if (src && (this.playing || this.tracks.length) && !options.immediate) return this.addTrack(src);
|
||||||
const track = options.filtersUpdate ? this.current : src ?? this.tracks.shift();
|
const track = options.filtersUpdate ? this.current : src ?? this.tracks.shift();
|
||||||
if (!track) return;
|
if (!track) return;
|
||||||
this.previousTracks = this.previousTracks.filter((x) => x._trackID !== track._trackID);
|
|
||||||
this.previousTracks.push(track);
|
if (!options.filtersUpdate) {
|
||||||
|
this.previousTracks = this.previousTracks.filter((x) => x._trackID !== track._trackID);
|
||||||
|
this.previousTracks.push(track);
|
||||||
|
}
|
||||||
|
|
||||||
let stream;
|
let stream;
|
||||||
if (["youtube", "spotify"].includes(track.raw.source)) {
|
if (["youtube", "spotify"].includes(track.raw.source)) {
|
||||||
if (track.raw.source === "spotify" && !track.raw.engine) {
|
if (track.raw.source === "spotify" && !track.raw.engine) {
|
||||||
|
|
|
@ -119,21 +119,21 @@ export enum QueryType {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlayerEvents {
|
export interface PlayerEvents {
|
||||||
botDisconnect: () => any;
|
botDisconnect: (queue: Queue) => any;
|
||||||
channelEmpty: () => any;
|
channelEmpty: (queue: Queue) => any;
|
||||||
connectionCreate: () => any;
|
connectionCreate: (queue: Queue) => any;
|
||||||
debug: (queue: Queue, message: string) => any;
|
debug: (queue: Queue, message: string) => any;
|
||||||
error: (queue: Queue, error: Error) => any;
|
error: (queue: Queue, error: Error) => any;
|
||||||
musicStop: () => any;
|
musicStop: (queue: Queue) => any;
|
||||||
noResults: () => any;
|
noResults: (queue: Queue) => any;
|
||||||
playlistAdd: () => any;
|
playlistAdd: (queue: Queue) => any;
|
||||||
playlistParseEnd: () => any;
|
playlistParseEnd: (queue: Queue) => any;
|
||||||
playlistParseStart: () => any;
|
playlistParseStart: (queue: Queue) => any;
|
||||||
queueCreate: () => any;
|
queueCreate: (queue: Queue) => any;
|
||||||
queueEnd: (queue: Queue) => any;
|
queueEnd: (queue: Queue) => any;
|
||||||
searchCancel: () => any;
|
searchCancel: (queue: Queue) => any;
|
||||||
searchInvalidResponse: () => any;
|
searchInvalidResponse: (queue: Queue) => any;
|
||||||
searchResults: () => any;
|
searchResults: (queue: Queue) => any;
|
||||||
trackAdd: (queue: Queue, track: Track) => any;
|
trackAdd: (queue: Queue, track: Track) => any;
|
||||||
tracksAdd: (queue: Queue, track: Track[]) => any;
|
tracksAdd: (queue: Queue, track: Track[]) => any;
|
||||||
trackStart: (queue: Queue, track: Track) => any;
|
trackStart: (queue: Queue, track: Track) => any;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { StageChannel, VoiceChannel } from "discord.js";
|
||||||
import { TimeData } from "../types/types";
|
import { TimeData } from "../types/types";
|
||||||
|
|
||||||
class Util {
|
class Util {
|
||||||
|
@ -34,6 +35,10 @@ class Util {
|
||||||
if (!Array.isArray(arr)) return;
|
if (!Array.isArray(arr)) return;
|
||||||
return arr[arr.length - 1];
|
return arr[arr.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static isVoiceEmpty(channel: VoiceChannel | StageChannel) {
|
||||||
|
return channel.members.filter((member) => !member.user.bot).size === 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Util };
|
export { Util };
|
||||||
|
|
Loading…
Reference in a new issue