diff --git a/src/Player.ts b/src/Player.ts index 69e7d83..ad922d4 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -8,6 +8,7 @@ import { Track } from './Structures/Track'; import { PlayerErrorEventCodes, PlayerEvents } from './utils/Constants'; import PlayerError from './utils/PlayerError'; import ytdl from 'discord-ytdl-core'; +import { ExtractorModel } from "./Structures/ExtractorModel"; // @ts-ignore import spotify from 'spotify-url-info'; @@ -24,6 +25,7 @@ export class Player extends EventEmitter { public queues: Collection; private _resultsCollectors: Collection>; private _cooldownsTimeout: Collection; + public Extractors = new Collection(); constructor(client: Client, options?: PlayerOptions) { super(); @@ -62,6 +64,35 @@ export class Player extends EventEmitter { return AudioFilters; } + /** + * Define custom extractor in this player + * @param extractorName The extractor name + * @param extractor The extractor itself + */ + use(extractorName: string, extractor: any) { + if (!extractorName) throw new PlayerError("Missing extractor name!", "PlayerExtractorError"); + + const methods = ["validate", "getInfo"]; + + for (const method of methods) { + if (typeof extractor[method] !== "function") throw new PlayerError("Invalid extractor supplied!", "PlayerExtractorError"); + } + + this.Extractors.set(extractorName, new ExtractorModel(extractorName, extractor)); + + return Player; + } + + /** + * Remove existing extractor from this player + * @param extractorName The extractor name + */ + unuse(extractorName: string) { + if (!extractorName) throw new PlayerError("Missing extractor name!", "PlayerExtractorError"); + + return this.Extractors.delete(extractorName); + } + private _searchTracks(message: Message, query: string, firstResult?: boolean): Promise { return new Promise(async (resolve) => { let tracks: Track[] = []; @@ -83,7 +114,7 @@ export class Player extends EventEmitter { requestedBy: message.author, fromPlaylist: false, source: 'soundcloud', - engine: data + engine: data.engine }); tracks.push(track); @@ -342,7 +373,31 @@ export class Player extends EventEmitter { source: 'youtube' }); } else { - track = await this._searchTracks(message, query, firstResult); + for (const [_, extractor] of this.Extractors) { + if (extractor.validate(query)) { + const data = await extractor.handle(query); + if (data) { + console.log(data) + track = new Track(this, { + title: data.title, + description: data.description, + duration: Util.durationString(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); } } diff --git a/src/Structures/ExtractorModel.ts b/src/Structures/ExtractorModel.ts new file mode 100644 index 0000000..f0c004b --- /dev/null +++ b/src/Structures/ExtractorModel.ts @@ -0,0 +1,44 @@ +import { ExtractorModelData } from "../types/types"; + +class ExtractorModel { + name: string; + private _raw: any; + + constructor(extractorName: string, data: any) { + this.name = extractorName; + + Object.defineProperty(this, "_raw", { value: data, configurable: false, writable: false, enumerable: false }); + } + + async handle(query: string) { + const data = await this._raw.getInfo(query); + if (!data) return null; + + return { + title: data.title, + duration: data.duration, + thumbnail: data.thumbnail, + engine: data.engine, + views: data.views, + author: data.author, + description: data.description, + url: data.url + } as ExtractorModelData; + } + + validate(query: string) { + return Boolean(this._raw.validate(query)); + } + + get version() { + return this._raw.version ?? "0.0.0"; + } + + get important() { + return Boolean(this._raw.important); + } + +} + +export default ExtractorModel; +export { ExtractorModel }; \ No newline at end of file diff --git a/src/types/types.ts b/src/types/types.ts index 192c826..60c379f 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1,5 +1,6 @@ import { downloadOptions } from 'ytdl-core'; import { User } from 'discord.js'; +import { Readable, Duplex } from "stream"; export interface PlayerOptions { leaveOnEnd?: boolean; @@ -75,3 +76,15 @@ export type QueryType = | 'reverbnation' | 'attachment' | 'youtube_search'; + +export interface ExtractorModelData { + title: string; + duration: number; + thumbnail: string; + engine: string | Readable | Duplex; + views: number; + author: string; + description: string; + url: string; + version?: string; +} \ No newline at end of file