feat: voice states handling

This commit is contained in:
Snowflake107 2021-06-13 22:32:04 +05:45
parent da5f4a64ba
commit a43df93abe
4 changed files with 63 additions and 16 deletions

View file

@ -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);
}
} }
/** /**

View file

@ -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;
if (!options.filtersUpdate) {
this.previousTracks = this.previousTracks.filter((x) => x._trackID !== track._trackID); this.previousTracks = this.previousTracks.filter((x) => x._trackID !== track._trackID);
this.previousTracks.push(track); 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) {

View file

@ -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;

View file

@ -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 };