2021-06-11 23:19:52 +05:00
|
|
|
import { Client, Collection, Guild, Snowflake, User } from "discord.js";
|
2021-06-11 15:32:22 +05:00
|
|
|
import { TypedEmitter as EventEmitter } from "tiny-typed-emitter";
|
|
|
|
import { Queue } from "./Structures/Queue";
|
2021-06-11 16:50:43 +05:00
|
|
|
import { VoiceUtils } from "./VoiceInterface/VoiceUtils";
|
2021-06-13 17:03:20 +05:00
|
|
|
import { PlayerEvents, PlayerOptions, QueryType, SearchOptions } from "./types/types";
|
2021-06-11 23:19:52 +05:00
|
|
|
import Track from "./Structures/Track";
|
|
|
|
import { QueryResolver } from "./utils/QueryResolver";
|
|
|
|
import YouTube from "youtube-sr";
|
2021-06-13 17:03:20 +05:00
|
|
|
import { Util } from "./utils/Util";
|
|
|
|
// @ts-ignore
|
|
|
|
import { Client as SoundCloud } from "soundcloud-scraper";
|
|
|
|
|
|
|
|
const soundcloud = new SoundCloud();
|
2021-06-11 15:32:22 +05:00
|
|
|
|
2021-06-11 23:19:52 +05:00
|
|
|
class DiscordPlayer extends EventEmitter<PlayerEvents> {
|
2021-06-11 15:32:22 +05:00
|
|
|
public readonly client: Client;
|
|
|
|
public readonly queues = new Collection<Snowflake, Queue>();
|
2021-06-11 16:50:43 +05:00
|
|
|
public readonly voiceUtils = new VoiceUtils();
|
2021-06-11 15:32:22 +05:00
|
|
|
|
2021-06-13 15:40:41 +05:00
|
|
|
/**
|
|
|
|
* Creates new Discord Player
|
|
|
|
* @param {Discord.Client} client The Discord Client
|
|
|
|
*/
|
2021-06-11 15:32:22 +05:00
|
|
|
constructor(client: Client) {
|
|
|
|
super();
|
2021-06-13 15:40:41 +05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The discord.js client
|
|
|
|
* @type {Discord.Client}
|
|
|
|
*/
|
2021-06-11 15:32:22 +05:00
|
|
|
this.client = client;
|
|
|
|
}
|
|
|
|
|
2021-06-13 15:40:41 +05:00
|
|
|
/**
|
|
|
|
* Creates a queue for a guild if not available, else returns existing queue
|
|
|
|
* @param {Discord.Guild} guild The guild
|
|
|
|
* @param {PlayerOptions} queueInitOptions Queue init options
|
|
|
|
* @returns {Queue}
|
|
|
|
*/
|
2021-06-13 17:43:36 +05:00
|
|
|
createQueue<T = unknown>(guild: Guild, queueInitOptions?: PlayerOptions & { metadata?: T }): Queue<T> {
|
2021-06-12 11:37:41 +05:00
|
|
|
if (this.queues.has(guild.id)) return this.queues.get(guild.id) as Queue<T>;
|
|
|
|
|
2021-06-13 15:40:41 +05:00
|
|
|
const _meta = queueInitOptions.metadata;
|
|
|
|
delete queueInitOptions["metadata"];
|
2021-06-11 15:32:22 +05:00
|
|
|
const queue = new Queue(this, guild, queueInitOptions);
|
2021-06-13 15:40:41 +05:00
|
|
|
queue.metadata = _meta;
|
2021-06-11 15:32:22 +05:00
|
|
|
this.queues.set(guild.id, queue);
|
|
|
|
|
2021-06-12 11:37:41 +05:00
|
|
|
return queue as Queue<T>;
|
2021-06-11 15:32:22 +05:00
|
|
|
}
|
|
|
|
|
2021-06-13 15:40:41 +05:00
|
|
|
/**
|
|
|
|
* Returns the queue if available
|
|
|
|
* @param {Discord.Snowflake} guild The guild id
|
|
|
|
* @returns {Queue}
|
|
|
|
*/
|
2021-06-12 11:37:41 +05:00
|
|
|
getQueue<T = unknown>(guild: Snowflake) {
|
|
|
|
return this.queues.get(guild) as Queue<T>;
|
2021-06-11 15:32:22 +05:00
|
|
|
}
|
2021-06-11 19:57:49 +05:00
|
|
|
|
2021-06-13 15:40:41 +05:00
|
|
|
/**
|
|
|
|
* Deletes a queue and returns deleted queue object
|
|
|
|
* @param {Discord.Snowflake} guild The guild id to remove
|
|
|
|
* @returns {Queue}
|
|
|
|
*/
|
2021-06-13 13:06:19 +05:00
|
|
|
deleteQueue<T = unknown>(guild: Snowflake) {
|
|
|
|
const prev = this.getQueue<T>(guild);
|
2021-06-13 13:11:54 +05:00
|
|
|
|
|
|
|
try {
|
|
|
|
prev.destroy();
|
|
|
|
} catch {}
|
2021-06-13 13:06:19 +05:00
|
|
|
this.queues.delete(guild);
|
2021-06-13 13:11:54 +05:00
|
|
|
|
2021-06-13 13:06:19 +05:00
|
|
|
return prev;
|
|
|
|
}
|
|
|
|
|
2021-06-11 23:19:52 +05:00
|
|
|
/**
|
|
|
|
* Search tracks
|
|
|
|
* @param {string|Track} query The search query
|
2021-06-13 15:40:41 +05:00
|
|
|
* @param {Discord.User} requestedBy The person who requested track search
|
2021-06-11 23:19:52 +05:00
|
|
|
* @returns {Promise<Track[]>}
|
|
|
|
*/
|
2021-06-13 17:03:20 +05:00
|
|
|
async search(query: string | Track, options: SearchOptions) {
|
2021-06-11 23:19:52 +05:00
|
|
|
if (query instanceof Track) return [query];
|
2021-06-13 17:03:20 +05:00
|
|
|
if (!options) throw new Error("DiscordPlayer#search needs search options!");
|
|
|
|
if (!("searchEngine" in options)) options.searchEngine = QueryType.AUTO;
|
2021-06-11 23:19:52 +05:00
|
|
|
|
|
|
|
// @todo: add extractors
|
2021-06-13 17:03:20 +05:00
|
|
|
const qt = options.searchEngine === QueryType.AUTO ? QueryResolver.resolve(query) : options.searchEngine;
|
2021-06-11 23:19:52 +05:00
|
|
|
switch (qt) {
|
2021-06-11 23:39:21 +05:00
|
|
|
case QueryType.YOUTUBE_SEARCH: {
|
|
|
|
const videos = await YouTube.search(query, {
|
2021-06-11 23:19:52 +05:00
|
|
|
type: "video"
|
|
|
|
});
|
|
|
|
|
2021-06-11 23:39:21 +05:00
|
|
|
return videos.map((m) => {
|
2021-06-12 00:18:53 +05:00
|
|
|
(m as any).source = "youtube";
|
|
|
|
return new Track(this, {
|
|
|
|
title: m.title,
|
|
|
|
description: m.description,
|
|
|
|
author: m.channel?.name,
|
|
|
|
url: m.url,
|
2021-06-13 17:03:20 +05:00
|
|
|
requestedBy: options.requestedBy,
|
2021-06-12 00:18:53 +05:00
|
|
|
thumbnail: m.thumbnail?.displayThumbnailURL("maxresdefault"),
|
|
|
|
views: m.views,
|
|
|
|
fromPlaylist: false,
|
|
|
|
duration: m.durationFormatted,
|
|
|
|
raw: m
|
2021-06-11 23:39:21 +05:00
|
|
|
});
|
2021-06-12 00:18:53 +05:00
|
|
|
});
|
2021-06-11 23:19:52 +05:00
|
|
|
}
|
2021-06-13 17:03:20 +05:00
|
|
|
case QueryType.SOUNDCLOUD_TRACK:
|
|
|
|
case QueryType.SOUNDCLOUD_SEARCH: {
|
|
|
|
const result: any[] = QueryResolver.resolve(query) === QueryType.SOUNDCLOUD_TRACK ? [{ url: query }] : await soundcloud.search(query, "track").catch(() => {});
|
|
|
|
if (!result || !result.length) return [];
|
|
|
|
const res: Track[] = [];
|
|
|
|
|
|
|
|
for (const r of result) {
|
|
|
|
const trackInfo = await soundcloud.getSongInfo(r.url).catch(() => {});
|
|
|
|
if (!trackInfo) continue;
|
|
|
|
|
|
|
|
const track = new Track(this, {
|
|
|
|
title: trackInfo.title,
|
|
|
|
url: trackInfo.url,
|
|
|
|
duration: Util.buildTimeCode(Util.parseMS(trackInfo.duration)),
|
|
|
|
description: trackInfo.description,
|
|
|
|
thumbnail: trackInfo.thumbnail,
|
|
|
|
views: trackInfo.playCount,
|
|
|
|
author: trackInfo.author.name,
|
|
|
|
requestedBy: options.requestedBy,
|
|
|
|
fromPlaylist: false,
|
|
|
|
source: "soundcloud",
|
|
|
|
engine: trackInfo
|
|
|
|
});
|
|
|
|
|
|
|
|
res.push(track);
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
2021-06-11 23:39:21 +05:00
|
|
|
default:
|
|
|
|
return [];
|
2021-06-11 23:19:52 +05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-11 19:57:49 +05:00
|
|
|
*[Symbol.iterator]() {
|
|
|
|
yield* Array.from(this.queues.values());
|
|
|
|
}
|
2021-06-11 15:32:22 +05:00
|
|
|
}
|
|
|
|
|
|
|
|
export { DiscordPlayer as Player };
|