2021-04-06 17:58:46 +05:00
|
|
|
import { EventEmitter } from 'events';
|
2021-05-10 09:58:37 +05:00
|
|
|
import { Client, Collection, Snowflake, Message, Collector } from 'discord.js';
|
2021-04-06 17:58:46 +05:00
|
|
|
import Util from './utils/Util';
|
2021-05-01 13:28:24 +05:00
|
|
|
import Queue from './Structures/Queue';
|
2021-05-10 09:58:37 +05:00
|
|
|
import Track from './Structures/Track';
|
|
|
|
import PlayerError from './utils/PlayerError';
|
2021-05-01 13:28:24 +05:00
|
|
|
import { ExtractorModel } from './Structures/ExtractorModel';
|
2021-05-10 09:58:37 +05:00
|
|
|
import ytdl from 'discord-ytdl-core';
|
|
|
|
import { PlayerEvents, PlayerErrorEventCodes } from './utils/Constants';
|
2021-04-04 22:36:40 +05:00
|
|
|
|
2021-04-06 20:38:17 +05:00
|
|
|
export class Player extends EventEmitter {
|
2021-04-21 12:09:16 +05:00
|
|
|
public client: Client;
|
2021-05-01 13:28:24 +05:00
|
|
|
public queues = new Collection<Snowflake, Queue>();
|
|
|
|
public Extractors = new Collection<string, ExtractorModel>();
|
2021-05-10 09:58:37 +05:00
|
|
|
private _cooldownsTimeout = new Collection<string, NodeJS.Timeout>();
|
|
|
|
private _resultsCollectors = new Collection<string, Collector<Snowflake, Message>>();
|
2021-04-09 17:59:14 +05:00
|
|
|
|
2021-05-01 13:05:12 +05:00
|
|
|
constructor(client: Client) {
|
2021-04-04 22:36:40 +05:00
|
|
|
super();
|
|
|
|
|
2021-04-06 17:58:46 +05:00
|
|
|
Object.defineProperty(this, 'client', {
|
2021-04-04 22:36:40 +05:00
|
|
|
value: client,
|
|
|
|
enumerable: false
|
|
|
|
});
|
|
|
|
|
2021-05-01 13:28:24 +05:00
|
|
|
Util.alertFFmpeg();
|
|
|
|
}
|
|
|
|
|
|
|
|
public createQueue(message: Message) {
|
2021-05-10 09:58:37 +05:00
|
|
|
return new Promise<Queue>((resolve) => {
|
|
|
|
if (this.queues.has(message.guild.id)) return this.queues.get(message.guild.id);
|
|
|
|
const channel = message.member.voice?.channel;
|
2021-06-10 00:27:29 +05:00
|
|
|
if (!channel)
|
|
|
|
return void this.emit(
|
|
|
|
PlayerEvents.ERROR,
|
|
|
|
new PlayerError(
|
|
|
|
'Voice connection is not available in this server!',
|
|
|
|
PlayerErrorEventCodes.NOT_CONNECTED,
|
|
|
|
message
|
|
|
|
)
|
|
|
|
);
|
2021-05-10 09:58:37 +05:00
|
|
|
|
|
|
|
const queue = new Queue(this, message.guild);
|
|
|
|
void this.queues.set(message.guild.id, queue);
|
|
|
|
|
|
|
|
channel
|
|
|
|
.join()
|
|
|
|
.then((connection) => {
|
|
|
|
this.emit(PlayerEvents.CONNECTION_CREATE, message, connection);
|
|
|
|
|
|
|
|
queue.voiceConnection = connection;
|
|
|
|
if (queue.options.setSelfDeaf) connection.voice.setSelfDeaf(true);
|
|
|
|
this.emit(PlayerEvents.QUEUE_CREATE, message, queue);
|
|
|
|
resolve(queue);
|
|
|
|
})
|
|
|
|
.catch((err) => {
|
|
|
|
this.queues.delete(message.guild.id);
|
|
|
|
this.emit(
|
|
|
|
PlayerEvents.ERROR,
|
|
|
|
new PlayerError(err.message ?? err, PlayerErrorEventCodes.UNABLE_TO_JOIN, message)
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
return queue;
|
2021-06-10 00:27:29 +05:00
|
|
|
});
|
2021-05-01 13:28:24 +05:00
|
|
|
}
|
|
|
|
|
|
|
|
public getQueue(message: Message) {
|
|
|
|
return this.queues.get(message.guild.id) ?? null;
|
2021-04-23 23:27:51 +05:00
|
|
|
}
|
|
|
|
|
2021-05-10 09:58:37 +05:00
|
|
|
async play(message: Message, query: string | Track, firstResult?: boolean): Promise<void> {
|
|
|
|
if (!message) throw new PlayerError('Play function needs message');
|
|
|
|
if (!query) throw new PlayerError('Play function needs search query as a string or Player.Track object');
|
|
|
|
|
|
|
|
if (this._cooldownsTimeout.has(`end_${message.guild.id}`)) {
|
|
|
|
clearTimeout(this._cooldownsTimeout.get(`end_${message.guild.id}`));
|
|
|
|
this._cooldownsTimeout.delete(`end_${message.guild.id}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof query === 'string') query = query.replace(/<(.+)>/g, '$1');
|
|
|
|
let track;
|
|
|
|
|
|
|
|
const queue = this.getQueue(message);
|
|
|
|
|
|
|
|
if (query instanceof Track) track = query;
|
|
|
|
else {
|
|
|
|
if (ytdl.validateURL(query)) {
|
2021-06-10 00:27:29 +05:00
|
|
|
const info = await ytdl.getBasicInfo(query).catch(() => {});
|
2021-05-10 09:58:37 +05:00
|
|
|
if (!info) return void this.emit(PlayerEvents.NO_RESULTS, message, query);
|
|
|
|
if (info.videoDetails.isLiveContent && !queue.options.enableLive)
|
|
|
|
return void this.emit(
|
|
|
|
PlayerEvents.ERROR,
|
|
|
|
new PlayerError('Live video is not enabled!', PlayerErrorEventCodes.LIVE_VIDEO, message)
|
|
|
|
);
|
|
|
|
const lastThumbnail = info.videoDetails.thumbnails[info.videoDetails.thumbnails.length - 1];
|
|
|
|
|
|
|
|
track = new Track(this, {
|
|
|
|
title: info.videoDetails.title,
|
|
|
|
description: info.videoDetails.description,
|
|
|
|
author: info.videoDetails.author.name,
|
|
|
|
url: info.videoDetails.video_url,
|
|
|
|
thumbnail: lastThumbnail.url,
|
|
|
|
duration: Util.buildTimeCode(Util.parseMS(parseInt(info.videoDetails.lengthSeconds) * 1000)),
|
|
|
|
views: parseInt(info.videoDetails.viewCount),
|
|
|
|
requestedBy: message.author,
|
|
|
|
fromPlaylist: false,
|
|
|
|
source: 'youtube',
|
|
|
|
live: Boolean(info.videoDetails.isLiveContent)
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
for (const [_, extractor] of this.Extractors) {
|
|
|
|
if (extractor.validate(query)) {
|
|
|
|
const data = await extractor.handle(query);
|
|
|
|
if (data) {
|
|
|
|
track = new Track(this, {
|
|
|
|
title: data.title,
|
|
|
|
description: data.description,
|
|
|
|
duration: Util.buildTimeCode(Util.parseMS(data.duration)),
|
|
|
|
thumbnail: data.thumbnail,
|
|
|
|
author: data.author,
|
|
|
|
views: data.views,
|
|
|
|
engine: data.engine,
|
|
|
|
source: 'arbitrary',
|
|
|
|
fromPlaylist: false,
|
|
|
|
requestedBy: message.author,
|
|
|
|
url: data.url
|
|
|
|
});
|
|
|
|
|
|
|
|
if (extractor.important) break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!track) track = await this.searchTracks(message, query, firstResult);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (track) {
|
|
|
|
if (queue) {
|
|
|
|
const q = queue.addTrack(track);
|
|
|
|
this.emit(PlayerEvents.TRACK_ADD, message, q, q.tracks[q.tracks.length - 1]);
|
|
|
|
} else {
|
|
|
|
const q = queue.addTrack(track);
|
|
|
|
if (q) this.emit(PlayerEvents.TRACK_START, message, q.tracks[0], q);
|
|
|
|
|
|
|
|
// todo: start playing
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private searchTracks(message: Message, query: string, firstResult?: boolean): Promise<Track> {
|
|
|
|
return new Promise(async (resolve) => {
|
|
|
|
let tracks: Track[] = [];
|
|
|
|
const queryType = Util.getQueryType(query);
|
|
|
|
|
|
|
|
switch (queryType) {
|
|
|
|
default:
|
|
|
|
tracks = await Util.ytSearch(query, { user: message.author, player: this });
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tracks.length < 1) return void this.emit(PlayerEvents.NO_RESULTS, message, query);
|
|
|
|
if (firstResult || tracks.length === 1) return resolve(tracks[0]);
|
|
|
|
|
|
|
|
const collectorString = `${message.author.id}-${message.channel.id}`;
|
|
|
|
const currentCollector = this._resultsCollectors.get(collectorString);
|
|
|
|
if (currentCollector) currentCollector.stop();
|
|
|
|
|
|
|
|
const collector = message.channel.createMessageCollector((m) => m.author.id === message.author.id, {
|
|
|
|
time: 60000
|
|
|
|
});
|
|
|
|
|
|
|
|
this._resultsCollectors.set(collectorString, collector);
|
|
|
|
|
|
|
|
this.emit(PlayerEvents.SEARCH_RESULTS, message, query, tracks, collector);
|
|
|
|
|
|
|
|
collector.on('collect', ({ content }) => {
|
|
|
|
if (content === 'cancel') {
|
|
|
|
collector.stop();
|
2021-06-09 18:42:28 +05:00
|
|
|
return void this.emit(PlayerEvents.SEARCH_CANCEL, message, query, tracks);
|
2021-05-10 09:58:37 +05:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!isNaN(content) && parseInt(content) >= 1 && parseInt(content) <= tracks.length) {
|
|
|
|
const index = parseInt(content, 10);
|
|
|
|
const track = tracks[index - 1];
|
|
|
|
collector.stop();
|
|
|
|
resolve(track);
|
|
|
|
} else {
|
|
|
|
this.emit(PlayerEvents.SEARCH_INVALID_RESPONSE, message, query, tracks, content, collector);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
collector.on('end', (_, reason) => {
|
|
|
|
if (reason === 'time') {
|
|
|
|
this.emit(PlayerEvents.SEARCH_CANCEL, message, query, tracks);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2021-04-04 22:44:45 +05:00
|
|
|
}
|
2021-04-06 20:38:17 +05:00
|
|
|
|
2021-06-10 00:27:29 +05:00
|
|
|
export default Player;
|