basic player
This commit is contained in:
parent
5ba21c3337
commit
181131f755
9 changed files with 206 additions and 34 deletions
|
@ -1,10 +1,13 @@
|
||||||
import { Client, Collection, Guild, Snowflake } from "discord.js";
|
import { Client, Collection, Guild, Snowflake, User } 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";
|
||||||
import { PlayerOptions } from "./types/types";
|
import { PlayerEvents, PlayerOptions, QueryType } from "./types/types";
|
||||||
|
import Track from "./Structures/Track";
|
||||||
|
import { QueryResolver } from "./utils/QueryResolver";
|
||||||
|
import YouTube from "youtube-sr";
|
||||||
|
|
||||||
class DiscordPlayer extends EventEmitter {
|
class DiscordPlayer extends EventEmitter<PlayerEvents> {
|
||||||
public readonly client: Client;
|
public readonly client: Client;
|
||||||
public readonly queues = new Collection<Snowflake, Queue>();
|
public readonly queues = new Collection<Snowflake, Queue>();
|
||||||
public readonly voiceUtils = new VoiceUtils();
|
public readonly voiceUtils = new VoiceUtils();
|
||||||
|
@ -26,6 +29,42 @@ class DiscordPlayer extends EventEmitter {
|
||||||
return this.queues.get(guild);
|
return this.queues.get(guild);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search tracks
|
||||||
|
* @param {string|Track} query The search query
|
||||||
|
* @param {User} requestedBy The person who requested track search
|
||||||
|
* @returns {Promise<Track[]>}
|
||||||
|
*/
|
||||||
|
async search(query: string | Track, requestedBy: User) {
|
||||||
|
if (query instanceof Track) return [query];
|
||||||
|
|
||||||
|
// @todo: add extractors
|
||||||
|
const qt = QueryResolver.resolve(query);
|
||||||
|
switch (qt) {
|
||||||
|
case QueryType.YOUTUBE: {
|
||||||
|
const videos = await YouTube.search(qt, {
|
||||||
|
type: "video"
|
||||||
|
});
|
||||||
|
|
||||||
|
return videos.map(
|
||||||
|
(m) =>
|
||||||
|
new Track(this, {
|
||||||
|
title: m.title,
|
||||||
|
description: m.description,
|
||||||
|
author: m.channel?.name,
|
||||||
|
url: m.url,
|
||||||
|
requestedBy: requestedBy,
|
||||||
|
thumbnail: m.thumbnail?.displayThumbnailURL("maxresdefault"),
|
||||||
|
views: m.views,
|
||||||
|
fromPlaylist: false,
|
||||||
|
duration: m.durationFormatted,
|
||||||
|
raw: m
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
*[Symbol.iterator]() {
|
*[Symbol.iterator]() {
|
||||||
yield* Array.from(this.queues.values());
|
yield* Array.from(this.queues.values());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
import { Guild, StageChannel, VoiceChannel } from "discord.js";
|
import { Guild, StageChannel, VoiceChannel } from "discord.js";
|
||||||
import { Player } from "../Player";
|
import { Player } from "../Player";
|
||||||
import { VoiceSubscription } from "../VoiceInterface/VoiceSubscription";
|
import { StreamDispatcher } from "../VoiceInterface/BasicStreamDispatcher";
|
||||||
import Track from "./Track";
|
import Track from "./Track";
|
||||||
import { PlayerOptions } from "../types/types";
|
import { PlayerOptions } from "../types/types";
|
||||||
|
import ytdl from "discord-ytdl-core";
|
||||||
|
import { AudioResource, StreamType } from "@discordjs/voice";
|
||||||
|
|
||||||
class Queue {
|
class Queue {
|
||||||
public readonly guild: Guild;
|
public readonly guild: Guild;
|
||||||
public readonly player: Player;
|
public readonly player: Player;
|
||||||
public voiceConnection: VoiceSubscription;
|
public connection: StreamDispatcher;
|
||||||
public tracks: Track[] = [];
|
public tracks: Track[] = [];
|
||||||
public options: PlayerOptions;
|
public options: PlayerOptions;
|
||||||
|
public playing = false;
|
||||||
|
|
||||||
constructor(player: Player, guild: Guild, options: PlayerOptions = {}) {
|
constructor(player: Player, guild: Guild, options: PlayerOptions = {}) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
|
@ -29,38 +32,118 @@ class Queue {
|
||||||
ytdlDownloadOptions: {},
|
ytdlDownloadOptions: {},
|
||||||
useSafeSearch: false,
|
useSafeSearch: false,
|
||||||
disableAutoRegister: false,
|
disableAutoRegister: false,
|
||||||
fetchBeforeQueued: false
|
fetchBeforeQueued: false,
|
||||||
|
initialVolume: 100
|
||||||
} as PlayerOptions,
|
} as PlayerOptions,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get current() {
|
get current() {
|
||||||
return this.voiceConnection.audioResource?.metadata ?? this.tracks[0];
|
return this.connection.audioResource?.metadata ?? this.tracks[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
async joinVoiceChannel(channel: StageChannel | VoiceChannel) {
|
async connect(channel: StageChannel | VoiceChannel) {
|
||||||
if (!["stage", "voice"].includes(channel.type))
|
if (!["stage", "voice"].includes(channel?.type))
|
||||||
throw new TypeError(`Channel type must be voice or stage, got ${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);
|
||||||
this.voiceConnection = connection;
|
this.connection = connection;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.voiceConnection.stop();
|
this.connection.end();
|
||||||
this.voiceConnection.disconnect();
|
this.connection.disconnect();
|
||||||
this.player.queues.delete(this.guild.id);
|
this.player.queues.delete(this.guild.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
play() {
|
skip() {
|
||||||
throw new Error("Not implemented");
|
if (!this.connection) return false;
|
||||||
|
return this.connection.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
addTrack(track: Track) {
|
||||||
|
this.addTracks([track]);
|
||||||
|
}
|
||||||
|
|
||||||
|
addTracks(tracks: Track[]) {
|
||||||
|
this.tracks.push(...tracks);
|
||||||
|
}
|
||||||
|
|
||||||
|
async play(src?: Track) {
|
||||||
|
if (!this.connection || !this.connection.voiceConnection)
|
||||||
|
throw new Error("Voice connection is not available, use <Queue>.connect()!");
|
||||||
|
const track = src ?? this.tracks.shift();
|
||||||
|
if (!track) return;
|
||||||
|
|
||||||
|
let resource: AudioResource<Track>;
|
||||||
|
|
||||||
|
if (["youtube", "spotify"].includes(track.raw.source)) {
|
||||||
|
const stream = ytdl(track.raw.source === "spotify" ? track.raw.engine : track.url, {
|
||||||
|
// because we don't wanna decode opus into pcm again just for volume, let discord.js handle that
|
||||||
|
opusEncoded: false,
|
||||||
|
fmt: "s16le"
|
||||||
|
});
|
||||||
|
|
||||||
|
resource = this.connection.createStream(stream, {
|
||||||
|
type: StreamType.Raw,
|
||||||
|
data: track
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const stream = ytdl.arbitraryStream(
|
||||||
|
track.raw.source === "soundcloud"
|
||||||
|
? await track.raw.engine.downloadProgressive()
|
||||||
|
: (track.raw.engine as string),
|
||||||
|
{
|
||||||
|
// because we don't wanna decode opus into pcm again just for volume, let discord.js handle that
|
||||||
|
opusEncoded: false,
|
||||||
|
fmt: "s16le"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
resource = this.connection.createStream(stream, {
|
||||||
|
type: StreamType.Raw,
|
||||||
|
data: track
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const dispatcher = this.connection.playStream(resource);
|
||||||
|
dispatcher.setVolume(this.options.initialVolume);
|
||||||
|
|
||||||
|
dispatcher.on("start", () => {
|
||||||
|
this.playing = true;
|
||||||
|
this.player.emit("trackStart", this, this.current);
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatcher.on("finish", () => {
|
||||||
|
this.playing = false;
|
||||||
|
if (!this.tracks.length) {
|
||||||
|
this.destroy();
|
||||||
|
this.player.emit("queueEnd", this);
|
||||||
|
} else {
|
||||||
|
const nextTrack = this.tracks.shift();
|
||||||
|
this.play(nextTrack);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
*[Symbol.iterator]() {
|
*[Symbol.iterator]() {
|
||||||
yield* this.tracks;
|
yield* this.tracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
guild: this.guild.id,
|
||||||
|
options: this.options,
|
||||||
|
tracks: this.tracks.map((m) => m.toJSON())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
if (!this.tracks.length) return "No songs available to display!";
|
||||||
|
return `**Upcoming Songs:**\n${this.tracks.map((m, i) => `${i + 1}. **${m.title}**`).join("\n")}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Queue };
|
export { Queue };
|
||||||
|
|
|
@ -104,7 +104,7 @@ class Track {
|
||||||
this.fromPlaylist = Boolean(data.fromPlaylist);
|
this.fromPlaylist = Boolean(data.fromPlaylist);
|
||||||
|
|
||||||
// raw
|
// raw
|
||||||
Object.defineProperty(this, "raw", { get: () => data, enumerable: false });
|
Object.defineProperty(this, "raw", { get: () => data.raw ?? data, enumerable: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -148,6 +148,25 @@ class Track {
|
||||||
toString(): string {
|
toString(): string {
|
||||||
return `${this.title} by ${this.author}`;
|
return `${this.title} by ${this.author}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw JSON representation of this track
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
title: this.title,
|
||||||
|
description: this.description,
|
||||||
|
author: this.author,
|
||||||
|
url: this.url,
|
||||||
|
thumbnail: this.thumbnail,
|
||||||
|
duration: this.duration,
|
||||||
|
durationMS: this.durationMS,
|
||||||
|
views: this.views,
|
||||||
|
requested: this.requestedBy.id,
|
||||||
|
fromPlaylist: this.fromPlaylist
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Track;
|
export default Track;
|
||||||
|
|
|
@ -22,7 +22,7 @@ export interface VoiceEvents {
|
||||||
finish: () => any;
|
finish: () => any;
|
||||||
}
|
}
|
||||||
|
|
||||||
class VoiceSubscription extends EventEmitter<VoiceEvents> {
|
class BasicStreamDispatcher extends EventEmitter<VoiceEvents> {
|
||||||
public readonly voiceConnection: VoiceConnection;
|
public readonly voiceConnection: VoiceConnection;
|
||||||
public readonly audioPlayer: AudioPlayer;
|
public readonly audioPlayer: AudioPlayer;
|
||||||
public connectPromise?: Promise<void>;
|
public connectPromise?: Promise<void>;
|
||||||
|
@ -46,7 +46,7 @@ class VoiceSubscription extends EventEmitter<VoiceEvents> {
|
||||||
this.voiceConnection.destroy();
|
this.voiceConnection.destroy();
|
||||||
}
|
}
|
||||||
} else if (newState.status === VoiceConnectionStatus.Destroyed) {
|
} else if (newState.status === VoiceConnectionStatus.Destroyed) {
|
||||||
this.stop();
|
this.end();
|
||||||
} else if (
|
} else if (
|
||||||
!this.connectPromise &&
|
!this.connectPromise &&
|
||||||
(newState.status === VoiceConnectionStatus.Connecting ||
|
(newState.status === VoiceConnectionStatus.Connecting ||
|
||||||
|
@ -64,6 +64,7 @@ class VoiceSubscription extends EventEmitter<VoiceEvents> {
|
||||||
|
|
||||||
this.audioPlayer.on("stateChange", (oldState, newState) => {
|
this.audioPlayer.on("stateChange", (oldState, newState) => {
|
||||||
if (newState.status === AudioPlayerStatus.Idle && oldState.status !== AudioPlayerStatus.Idle) {
|
if (newState.status === AudioPlayerStatus.Idle && oldState.status !== AudioPlayerStatus.Idle) {
|
||||||
|
this.audioResource = null;
|
||||||
void this.emit("finish");
|
void this.emit("finish");
|
||||||
} else if (newState.status === AudioPlayerStatus.Playing) {
|
} else if (newState.status === AudioPlayerStatus.Playing) {
|
||||||
void this.emit("start");
|
void this.emit("start");
|
||||||
|
@ -78,14 +79,14 @@ class VoiceSubscription extends EventEmitter<VoiceEvents> {
|
||||||
/**
|
/**
|
||||||
* Creates stream
|
* Creates stream
|
||||||
* @param {Readable|Duplex|string} src The stream source
|
* @param {Readable|Duplex|string} src The stream source
|
||||||
* @param {({type?:StreamType;data?:any;inlineVolume?:boolean})} [ops] Options
|
* @param {({type?:StreamType;data?:any;})} [ops] Options
|
||||||
* @returns {AudioResource}
|
* @returns {AudioResource}
|
||||||
*/
|
*/
|
||||||
createStream(src: Readable | Duplex | string, ops?: { type?: StreamType; data?: any; inlineVolume?: boolean }) {
|
createStream(src: Readable | Duplex | string, ops?: { type?: StreamType; data?: any }) {
|
||||||
this.audioResource = createAudioResource(src, {
|
this.audioResource = createAudioResource(src, {
|
||||||
inputType: ops?.type ?? StreamType.Arbitrary,
|
inputType: ops?.type ?? StreamType.Arbitrary,
|
||||||
metadata: ops?.data,
|
metadata: ops?.data,
|
||||||
inlineVolume: Boolean(ops?.inlineVolume)
|
inlineVolume: true // we definitely need volume controls, right?
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.audioResource;
|
return this.audioResource;
|
||||||
|
@ -108,7 +109,7 @@ class VoiceSubscription extends EventEmitter<VoiceEvents> {
|
||||||
/**
|
/**
|
||||||
* Stops the player
|
* Stops the player
|
||||||
*/
|
*/
|
||||||
stop() {
|
end() {
|
||||||
this.audioPlayer.stop();
|
this.audioPlayer.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,16 +127,23 @@ class VoiceSubscription extends EventEmitter<VoiceEvents> {
|
||||||
*/
|
*/
|
||||||
playStream(resource: AudioResource<Track> = this.audioResource) {
|
playStream(resource: AudioResource<Track> = this.audioResource) {
|
||||||
if (!resource) throw new PlayerError("Audio resource is not available!");
|
if (!resource) throw new PlayerError("Audio resource is not available!");
|
||||||
if (!this.audioResource && resource) this.audioResource = resource;
|
if (!this.audioResource) this.audioResource = resource;
|
||||||
this.audioPlayer.play(resource);
|
this.audioPlayer.play(resource);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setVolume(value: number) {
|
||||||
|
if (!this.audioResource) return;
|
||||||
|
|
||||||
|
// ye boi logarithmic ✌
|
||||||
|
this.audioResource.volume.setVolumeLogarithmic(value / 200);
|
||||||
|
}
|
||||||
|
|
||||||
get streamTime() {
|
get streamTime() {
|
||||||
if (!this.audioResource) return 0;
|
if (!this.audioResource) return 0;
|
||||||
return this.audioResource.playbackDuration;
|
return this.audioResource.playbackDuration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { VoiceSubscription };
|
export { BasicStreamDispatcher as StreamDispatcher };
|
|
@ -1,15 +1,15 @@
|
||||||
import { VoiceChannel, StageChannel, Collection, Snowflake } from "discord.js";
|
import { VoiceChannel, StageChannel, Collection, Snowflake } from "discord.js";
|
||||||
import { entersState, joinVoiceChannel, VoiceConnection, VoiceConnectionStatus } from "@discordjs/voice";
|
import { entersState, joinVoiceChannel, VoiceConnection, VoiceConnectionStatus } from "@discordjs/voice";
|
||||||
import { VoiceSubscription } from "./VoiceSubscription";
|
import { StreamDispatcher } from "./BasicStreamDispatcher";
|
||||||
|
|
||||||
class VoiceUtils {
|
class VoiceUtils {
|
||||||
public cache = new Collection<Snowflake, VoiceSubscription>();
|
public cache = new Collection<Snowflake, StreamDispatcher>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Joins a voice channel
|
* Joins a voice channel
|
||||||
* @param {StageChannel|VoiceChannel} channel The voice channel
|
* @param {StageChannel|VoiceChannel} channel The voice channel
|
||||||
* @param {({deaf?: boolean;maxTime?: number;})} [options] Join options
|
* @param {({deaf?: boolean;maxTime?: number;})} [options] Join options
|
||||||
* @returns {Promise<VoiceSubscription>}
|
* @returns {Promise<BasicStreamDispatcher>}
|
||||||
*/
|
*/
|
||||||
public async connect(
|
public async connect(
|
||||||
channel: VoiceChannel | StageChannel,
|
channel: VoiceChannel | StageChannel,
|
||||||
|
@ -17,7 +17,7 @@ class VoiceUtils {
|
||||||
deaf?: boolean;
|
deaf?: boolean;
|
||||||
maxTime?: number;
|
maxTime?: number;
|
||||||
}
|
}
|
||||||
): Promise<VoiceSubscription> {
|
): Promise<StreamDispatcher> {
|
||||||
let conn = joinVoiceChannel({
|
let conn = joinVoiceChannel({
|
||||||
guildId: channel.guild.id,
|
guildId: channel.guild.id,
|
||||||
channelId: channel.id,
|
channelId: channel.id,
|
||||||
|
@ -27,7 +27,7 @@ class VoiceUtils {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
conn = await entersState(conn, VoiceConnectionStatus.Ready, options?.maxTime ?? 20000);
|
conn = await entersState(conn, VoiceConnectionStatus.Ready, options?.maxTime ?? 20000);
|
||||||
const sub = new VoiceSubscription(conn);
|
const sub = new StreamDispatcher(conn);
|
||||||
this.cache.set(channel.guild.id, sub);
|
this.cache.set(channel.guild.id, sub);
|
||||||
return sub;
|
return sub;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -40,8 +40,8 @@ class VoiceUtils {
|
||||||
* Disconnects voice connection
|
* Disconnects voice connection
|
||||||
* @param {VoiceConnection} connection The voice connection
|
* @param {VoiceConnection} connection The voice connection
|
||||||
*/
|
*/
|
||||||
public disconnect(connection: VoiceConnection | VoiceSubscription) {
|
public disconnect(connection: VoiceConnection | StreamDispatcher) {
|
||||||
if (connection instanceof VoiceSubscription) return connection.voiceConnection.destroy();
|
if (connection instanceof StreamDispatcher) return connection.voiceConnection.destroy();
|
||||||
return connection.destroy();
|
return connection.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export { AudioFilters } from "./utils/AudioFilters";
|
export { AudioFilters } from "./utils/AudioFilters";
|
||||||
export { PlayerError } from "./utils/PlayerError";
|
export { PlayerError } from "./utils/PlayerError";
|
||||||
export { VoiceUtils } from "./VoiceInterface/VoiceUtils";
|
export { VoiceUtils } from "./VoiceInterface/VoiceUtils";
|
||||||
export { VoiceEvents, VoiceSubscription } from "./VoiceInterface/VoiceSubscription";
|
export { VoiceEvents, StreamDispatcher } from "./VoiceInterface/BasicStreamDispatcher";
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { User } from "discord.js";
|
import { User } from "discord.js";
|
||||||
import { downloadOptions } from "ytdl-core";
|
import { downloadOptions } from "ytdl-core";
|
||||||
import { Readable, Duplex } from "stream";
|
import { Readable, Duplex } from "stream";
|
||||||
|
import { Queue } from "../Structures/Queue";
|
||||||
|
import Track from "../Structures/Track";
|
||||||
|
|
||||||
export type FiltersName = keyof QueueFilters;
|
export type FiltersName = keyof QueueFilters;
|
||||||
|
|
||||||
|
@ -50,6 +52,7 @@ export interface RawTrackData {
|
||||||
source?: TrackSource;
|
source?: TrackSource;
|
||||||
engine?: any;
|
engine?: any;
|
||||||
live?: boolean;
|
live?: boolean;
|
||||||
|
raw?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TimeData {
|
export interface TimeData {
|
||||||
|
@ -79,6 +82,7 @@ export interface PlayerOptions {
|
||||||
useSafeSearch?: boolean;
|
useSafeSearch?: boolean;
|
||||||
disableAutoRegister?: boolean;
|
disableAutoRegister?: boolean;
|
||||||
fetchBeforeQueued?: boolean;
|
fetchBeforeQueued?: boolean;
|
||||||
|
initialVolume?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExtractorModelData {
|
export interface ExtractorModelData {
|
||||||
|
@ -110,3 +114,22 @@ export enum QueryType {
|
||||||
REVERBNATION = "reverbnation",
|
REVERBNATION = "reverbnation",
|
||||||
YOUTUBE_SEARCH = "youtube_search"
|
YOUTUBE_SEARCH = "youtube_search"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PlayerEvents {
|
||||||
|
botDisconnect: () => any;
|
||||||
|
channelEmpty: () => any;
|
||||||
|
connectionCreate: () => any;
|
||||||
|
error: () => any;
|
||||||
|
musicStop: () => any;
|
||||||
|
noResults: () => any;
|
||||||
|
playlistAdd: () => any;
|
||||||
|
playlistParseEnd: () => any;
|
||||||
|
playlistParseStart: () => any;
|
||||||
|
queueCreate: () => any;
|
||||||
|
queueEnd: (queue: Queue) => any;
|
||||||
|
searchCancel: () => any;
|
||||||
|
searchInvalidResponse: () => any;
|
||||||
|
searchResults: () => any;
|
||||||
|
trackAdd: () => any;
|
||||||
|
trackStart: (queue: Queue, track: Track) => any;
|
||||||
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ const attachmentRegex =
|
||||||
// scary things above *sigh*
|
// scary things above *sigh*
|
||||||
|
|
||||||
class QueryResolver {
|
class QueryResolver {
|
||||||
|
|
||||||
static resolve(query: string): QueryType {
|
static resolve(query: string): QueryType {
|
||||||
if (SoundcloudValidateURL(query, "track")) return QueryType.SOUNDCLOUD_TRACK;
|
if (SoundcloudValidateURL(query, "track")) return QueryType.SOUNDCLOUD_TRACK;
|
||||||
if (SoundcloudValidateURL(query, "playlist") || query.includes("/sets/")) return QueryType.SOUNDCLOUD_PLAYLIST;
|
if (SoundcloudValidateURL(query, "playlist") || query.includes("/sets/")) return QueryType.SOUNDCLOUD_PLAYLIST;
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
"outDir": "./lib",
|
"outDir": "./lib",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"strictNullChecks": false,
|
"strictNullChecks": false,
|
||||||
"esModuleInterop": true
|
"esModuleInterop": true,
|
||||||
|
"removeComments": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*"
|
"src/**/*"
|
||||||
|
|
Loading…
Reference in a new issue