mirror of
https://github.com/JonnyBro/JaBa.git
synced 2024-11-22 13:14:58 +05:00
Сделал музыку отдельным модулем =)
Устанавливается через npm
This commit is contained in:
parent
7a4ee234cd
commit
2c0f9e38d9
61 changed files with 104 additions and 11706 deletions
|
@ -1,5 +1,5 @@
|
||||||
const { Client, Collection, SlashCommandBuilder, ContextMenuCommandBuilder } = require("discord.js"),
|
const { Client, Collection, SlashCommandBuilder, ContextMenuCommandBuilder } = require("discord.js"),
|
||||||
{ Player } = require("../helpers/Music/dist/index"),
|
{ Player } = require("discord-player-play-dl"),
|
||||||
{ DiscordTogether } = require("../helpers/discordTogether"),
|
{ DiscordTogether } = require("../helpers/discordTogether"),
|
||||||
{ GiveawaysManager } = require("discord-giveaways"),
|
{ GiveawaysManager } = require("discord-giveaways"),
|
||||||
{ REST } = require("@discordjs/rest"),
|
{ REST } = require("@discordjs/rest"),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const { SlashCommandBuilder, ActionRowBuilder, SelectMenuBuilder } = require("discord.js"),
|
const { SlashCommandBuilder, ActionRowBuilder, SelectMenuBuilder } = require("discord.js"),
|
||||||
{ QueueRepeatMode } = require("../../helpers/Music/dist/index");
|
{ QueueRepeatMode } = require("discord-player-play-dl");
|
||||||
const BaseCommand = require("../../base/BaseCommand");
|
const BaseCommand = require("../../base/BaseCommand");
|
||||||
|
|
||||||
class Loop extends BaseCommand {
|
class Loop extends BaseCommand {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"),
|
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"),
|
||||||
{ QueueRepeatMode } = require("../../helpers/Music/dist/index");
|
{ QueueRepeatMode } = require("discord-player-play-dl");
|
||||||
const BaseCommand = require("../../base/BaseCommand");
|
const BaseCommand = require("../../base/BaseCommand");
|
||||||
|
|
||||||
class Nowplaying extends BaseCommand {
|
class Nowplaying extends BaseCommand {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, PermissionsBitField } = require("discord.js"),
|
const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, PermissionsBitField } = require("discord.js"),
|
||||||
{ QueryType } = require("../../helpers/Music/dist/index");
|
{ QueryType } = require("discord-player-play-dl");
|
||||||
const BaseCommand = require("../../base/BaseCommand");
|
const BaseCommand = require("../../base/BaseCommand");
|
||||||
|
|
||||||
class Play extends BaseCommand {
|
class Play extends BaseCommand {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require("discord.js"),
|
const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require("discord.js"),
|
||||||
{ QueueRepeatMode } = require("../../helpers/Music/dist/index");
|
{ QueueRepeatMode } = require("discord-player-play-dl");
|
||||||
const BaseCommand = require("../../base/BaseCommand");
|
const BaseCommand = require("../../base/BaseCommand");
|
||||||
|
|
||||||
class Queue extends BaseCommand {
|
class Queue extends BaseCommand {
|
||||||
|
@ -151,7 +151,7 @@ class Queue extends BaseCommand {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {import("discord.js").ChatInputCommandInteraction} interaction
|
* @param {import("discord.js").ChatInputCommandInteraction} interaction
|
||||||
* @param {import("../../helpers/Music/dist/index").Queue} queue
|
* @param {import("discord-player-play-dl").Queue} queue
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function generateQueueEmbeds(interaction, queue) {
|
function generateQueueEmbeds(interaction, queue) {
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
node_modules/
|
|
||||||
dist/
|
|
||||||
|
|
||||||
*.d.ts
|
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"root": true,
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"env": {
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"plugins": [
|
|
||||||
"@typescript-eslint"
|
|
||||||
],
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": "error",
|
|
||||||
"@typescript-eslint/no-explicit-any": "error",
|
|
||||||
"@typescript-eslint/ban-ts-comment": "error",
|
|
||||||
"semi": "error",
|
|
||||||
"no-console": "error"
|
|
||||||
}
|
|
||||||
}
|
|
96
helpers/Music/dist/Player.d.ts
vendored
96
helpers/Music/dist/Player.d.ts
vendored
|
@ -1,96 +0,0 @@
|
||||||
import { Client, Collection, GuildResolvable } from "discord.js";
|
|
||||||
import { TypedEmitter as EventEmitter } from "tiny-typed-emitter";
|
|
||||||
import { Queue } from "./Structures/Queue";
|
|
||||||
import { VoiceUtils } from "./VoiceInterface/VoiceUtils";
|
|
||||||
import { PlayerEvents, PlayerOptions, SearchOptions, PlayerInitOptions, PlayerSearchResult, PlaylistInitData } from "./types/types";
|
|
||||||
import Track from "./Structures/Track";
|
|
||||||
import { Playlist } from "./Structures/Playlist";
|
|
||||||
import { ExtractorModel } from "./Structures/ExtractorModel";
|
|
||||||
declare class Player extends EventEmitter<PlayerEvents> {
|
|
||||||
readonly client: Client;
|
|
||||||
readonly options: PlayerInitOptions;
|
|
||||||
readonly queues: Collection<string, Queue<unknown>>;
|
|
||||||
readonly voiceUtils: VoiceUtils;
|
|
||||||
readonly extractors: Collection<string, ExtractorModel>;
|
|
||||||
requiredEvents: string[];
|
|
||||||
/**
|
|
||||||
* Creates new Discord Player
|
|
||||||
* @param {Client} client The Discord Client
|
|
||||||
* @param {PlayerInitOptions} [options] The player init options
|
|
||||||
*/
|
|
||||||
constructor(client: Client, options?: PlayerInitOptions);
|
|
||||||
/**
|
|
||||||
* Handles voice state update
|
|
||||||
* @param {VoiceState} oldState The old voice state
|
|
||||||
* @param {VoiceState} newState The new voice state
|
|
||||||
* @returns {void}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private _handleVoiceState;
|
|
||||||
/**
|
|
||||||
* Creates a queue for a guild if not available, else returns existing queue
|
|
||||||
* @param {GuildResolvable} guild The guild
|
|
||||||
* @param {PlayerOptions} queueInitOptions Queue init options
|
|
||||||
* @returns {Queue}
|
|
||||||
*/
|
|
||||||
createQueue<T = unknown>(guild: GuildResolvable, queueInitOptions?: PlayerOptions & {
|
|
||||||
metadata?: T;
|
|
||||||
}): Queue<T>;
|
|
||||||
/**
|
|
||||||
* Returns the queue if available
|
|
||||||
* @param {GuildResolvable} guild The guild id
|
|
||||||
* @returns {Queue}
|
|
||||||
*/
|
|
||||||
getQueue<T = unknown>(guild: GuildResolvable): Queue<T>;
|
|
||||||
/**
|
|
||||||
* Deletes a queue and returns deleted queue object
|
|
||||||
* @param {GuildResolvable} guild The guild id to remove
|
|
||||||
* @returns {Queue}
|
|
||||||
*/
|
|
||||||
deleteQueue<T = unknown>(guild: GuildResolvable): Queue<T>;
|
|
||||||
/**
|
|
||||||
* @typedef {object} PlayerSearchResult
|
|
||||||
* @property {Playlist} [playlist] The playlist (if any)
|
|
||||||
* @property {Track[]} tracks The tracks
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Search tracks
|
|
||||||
* @param {string|Track} query The search query
|
|
||||||
* @param {SearchOptions} options The search options
|
|
||||||
* @returns {Promise<PlayerSearchResult>}
|
|
||||||
*/
|
|
||||||
search(query: string | Track, options: SearchOptions): Promise<PlayerSearchResult>;
|
|
||||||
/**
|
|
||||||
* Registers extractor
|
|
||||||
* @param {string} extractorName The extractor name
|
|
||||||
* @param {ExtractorModel|any} extractor The extractor object
|
|
||||||
* @param {boolean} [force=false] Overwrite existing extractor with this name (if available)
|
|
||||||
* @returns {ExtractorModel}
|
|
||||||
*/
|
|
||||||
use(extractorName: string, extractor: ExtractorModel | any, force?: boolean): ExtractorModel;
|
|
||||||
/**
|
|
||||||
* Removes registered extractor
|
|
||||||
* @param {string} extractorName The extractor name
|
|
||||||
* @returns {ExtractorModel}
|
|
||||||
*/
|
|
||||||
unuse(extractorName: string): ExtractorModel;
|
|
||||||
/**
|
|
||||||
* Generates a report of the dependencies used by the `@discordjs/voice` module. Useful for debugging.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
scanDeps(): string;
|
|
||||||
emit<U extends keyof PlayerEvents>(eventName: U, ...args: Parameters<PlayerEvents[U]>): boolean;
|
|
||||||
/**
|
|
||||||
* Resolves queue
|
|
||||||
* @param {GuildResolvable|Queue} queueLike Queue like object
|
|
||||||
* @returns {Queue}
|
|
||||||
*/
|
|
||||||
resolveQueue<T>(queueLike: GuildResolvable | Queue): Queue<T>;
|
|
||||||
[Symbol.iterator](): Generator<Queue<unknown>, void, undefined>;
|
|
||||||
/**
|
|
||||||
* Creates `Playlist` instance
|
|
||||||
* @param data The data to initialize a playlist
|
|
||||||
*/
|
|
||||||
createPlaylist(data: PlaylistInitData): Playlist;
|
|
||||||
}
|
|
||||||
export { Player };
|
|
579
helpers/Music/dist/Player.js
vendored
579
helpers/Music/dist/Player.js
vendored
|
@ -1,579 +0,0 @@
|
||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.Player = void 0;
|
|
||||||
const tslib_1 = require("tslib");
|
|
||||||
const discord_js_1 = require("discord.js");
|
|
||||||
const tiny_typed_emitter_1 = require("tiny-typed-emitter");
|
|
||||||
const Queue_1 = require("./Structures/Queue");
|
|
||||||
const VoiceUtils_1 = require("./VoiceInterface/VoiceUtils");
|
|
||||||
const types_1 = require("./types/types");
|
|
||||||
const Track_1 = tslib_1.__importDefault(require("./Structures/Track"));
|
|
||||||
const play_dl_1 = tslib_1.__importDefault(require("play-dl"));
|
|
||||||
const spotify_url_info_1 = tslib_1.__importDefault(require("spotify-url-info"));
|
|
||||||
const QueryResolver_1 = require("./utils/QueryResolver");
|
|
||||||
const Util_1 = require("./utils/Util");
|
|
||||||
const PlayerError_1 = require("./Structures/PlayerError");
|
|
||||||
const Playlist_1 = require("./Structures/Playlist");
|
|
||||||
const ExtractorModel_1 = require("./Structures/ExtractorModel");
|
|
||||||
const voice_1 = require("@discordjs/voice");
|
|
||||||
class Player extends tiny_typed_emitter_1.TypedEmitter {
|
|
||||||
/**
|
|
||||||
* Creates new Discord Player
|
|
||||||
* @param {Client} client The Discord Client
|
|
||||||
* @param {PlayerInitOptions} [options] The player init options
|
|
||||||
*/
|
|
||||||
constructor(client, options = {}) {
|
|
||||||
super();
|
|
||||||
this.options = {
|
|
||||||
autoRegisterExtractor: true,
|
|
||||||
connectionTimeout: 20000
|
|
||||||
};
|
|
||||||
this.queues = new discord_js_1.Collection();
|
|
||||||
this.voiceUtils = new VoiceUtils_1.VoiceUtils();
|
|
||||||
this.extractors = new discord_js_1.Collection();
|
|
||||||
this.requiredEvents = ["error", "connectionError"];
|
|
||||||
/**
|
|
||||||
* The discord.js client
|
|
||||||
* @type {Client}
|
|
||||||
*/
|
|
||||||
this.client = client;
|
|
||||||
if (this.client?.options?.intents && !new discord_js_1.IntentsBitField(this.client?.options?.intents).has(discord_js_1.IntentsBitField.Flags.GuildVoiceStates)) {
|
|
||||||
throw new PlayerError_1.PlayerError('client is missing "GuildVoiceStates" intent');
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The extractors collection
|
|
||||||
* @type {ExtractorModel}
|
|
||||||
*/
|
|
||||||
this.options = Object.assign(this.options, options);
|
|
||||||
this.client.on("voiceStateUpdate", this._handleVoiceState.bind(this));
|
|
||||||
if (this.options?.autoRegisterExtractor) {
|
|
||||||
let nv; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
if ((nv = Util_1.Util.require("@discord-player/extractor"))) {
|
|
||||||
["Attachment", "Facebook", "Reverbnation", "Vimeo"].forEach((ext) => void this.use(ext, nv[ext]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Handles voice state update
|
|
||||||
* @param {VoiceState} oldState The old voice state
|
|
||||||
* @param {VoiceState} newState The new voice state
|
|
||||||
* @returns {void}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_handleVoiceState(oldState, newState) {
|
|
||||||
const queue = this.getQueue(oldState.guild.id);
|
|
||||||
if (!queue || !queue.connection)
|
|
||||||
return;
|
|
||||||
if (oldState.channelId && !newState.channelId && newState.member.id === newState.guild.members.me.id) {
|
|
||||||
try {
|
|
||||||
queue.destroy();
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
/* noop */
|
|
||||||
}
|
|
||||||
return void this.emit("botDisconnect", queue);
|
|
||||||
}
|
|
||||||
if (!oldState.channelId && newState.channelId && newState.member.id === newState.guild.members.me.id) {
|
|
||||||
if (!oldState.serverMute && newState.serverMute) {
|
|
||||||
// state.serverMute can be null
|
|
||||||
queue.setPaused(!!newState.serverMute);
|
|
||||||
}
|
|
||||||
else if (!oldState.suppress && newState.suppress) {
|
|
||||||
// state.suppress can be null
|
|
||||||
queue.setPaused(!!newState.suppress);
|
|
||||||
if (newState.suppress) {
|
|
||||||
newState.guild.members.me.voice.setRequestToSpeak(true).catch(Util_1.Util.noop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldState.channelId === newState.channelId && newState.member.id === newState.guild.members.me.id) {
|
|
||||||
if (!oldState.serverMute && newState.serverMute) {
|
|
||||||
// state.serverMute can be null
|
|
||||||
queue.setPaused(!!newState.serverMute);
|
|
||||||
}
|
|
||||||
else if (!oldState.suppress && newState.suppress) {
|
|
||||||
// state.suppress can be null
|
|
||||||
queue.setPaused(!!newState.suppress);
|
|
||||||
if (newState.suppress) {
|
|
||||||
newState.guild.members.me.voice.setRequestToSpeak(true).catch(Util_1.Util.noop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (queue.connection && !newState.channelId && oldState.channelId === queue.connection.channel.id) {
|
|
||||||
if (!Util_1.Util.isVoiceEmpty(queue.connection.channel))
|
|
||||||
return;
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
if (!Util_1.Util.isVoiceEmpty(queue.connection.channel))
|
|
||||||
return;
|
|
||||||
if (!this.queues.has(queue.guild.id))
|
|
||||||
return;
|
|
||||||
if (queue.options.leaveOnEmpty)
|
|
||||||
queue.destroy(true);
|
|
||||||
this.emit("channelEmpty", queue);
|
|
||||||
}, queue.options.leaveOnEmptyCooldown || 0).unref();
|
|
||||||
queue._cooldownsTimeout.set(`empty_${oldState.guild.id}`, timeout);
|
|
||||||
}
|
|
||||||
if (queue.connection && newState.channelId && newState.channelId === queue.connection.channel.id) {
|
|
||||||
const emptyTimeout = queue._cooldownsTimeout.get(`empty_${oldState.guild.id}`);
|
|
||||||
const channelEmpty = Util_1.Util.isVoiceEmpty(queue.connection.channel);
|
|
||||||
if (!channelEmpty && emptyTimeout) {
|
|
||||||
clearTimeout(emptyTimeout);
|
|
||||||
queue._cooldownsTimeout.delete(`empty_${oldState.guild.id}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (oldState.channelId && newState.channelId && oldState.channelId !== newState.channelId && newState.member.id === newState.guild.members.me.id) {
|
|
||||||
if (queue.connection && newState.member.id === newState.guild.members.me.id)
|
|
||||||
queue.connection.channel = newState.channel;
|
|
||||||
const emptyTimeout = queue._cooldownsTimeout.get(`empty_${oldState.guild.id}`);
|
|
||||||
const channelEmpty = Util_1.Util.isVoiceEmpty(queue.connection.channel);
|
|
||||||
if (!channelEmpty && emptyTimeout) {
|
|
||||||
clearTimeout(emptyTimeout);
|
|
||||||
queue._cooldownsTimeout.delete(`empty_${oldState.guild.id}`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
if (queue.connection && !Util_1.Util.isVoiceEmpty(queue.connection.channel))
|
|
||||||
return;
|
|
||||||
if (!this.queues.has(queue.guild.id))
|
|
||||||
return;
|
|
||||||
if (queue.options.leaveOnEmpty)
|
|
||||||
queue.destroy(true);
|
|
||||||
this.emit("channelEmpty", queue);
|
|
||||||
}, queue.options.leaveOnEmptyCooldown || 0).unref();
|
|
||||||
queue._cooldownsTimeout.set(`empty_${oldState.guild.id}`, timeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Creates a queue for a guild if not available, else returns existing queue
|
|
||||||
* @param {GuildResolvable} guild The guild
|
|
||||||
* @param {PlayerOptions} queueInitOptions Queue init options
|
|
||||||
* @returns {Queue}
|
|
||||||
*/
|
|
||||||
createQueue(guild, queueInitOptions = {}) {
|
|
||||||
guild = this.client.guilds.resolve(guild);
|
|
||||||
if (!guild)
|
|
||||||
throw new PlayerError_1.PlayerError("Unknown Guild", PlayerError_1.ErrorStatusCode.UNKNOWN_GUILD);
|
|
||||||
if (this.queues.has(guild.id))
|
|
||||||
return this.queues.get(guild.id);
|
|
||||||
const _meta = queueInitOptions.metadata;
|
|
||||||
delete queueInitOptions["metadata"];
|
|
||||||
queueInitOptions.volumeSmoothness ?? (queueInitOptions.volumeSmoothness = 0.08);
|
|
||||||
const queue = new Queue_1.Queue(this, guild, queueInitOptions);
|
|
||||||
queue.metadata = _meta;
|
|
||||||
this.queues.set(guild.id, queue);
|
|
||||||
return queue;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns the queue if available
|
|
||||||
* @param {GuildResolvable} guild The guild id
|
|
||||||
* @returns {Queue}
|
|
||||||
*/
|
|
||||||
getQueue(guild) {
|
|
||||||
guild = this.client.guilds.resolve(guild);
|
|
||||||
if (!guild)
|
|
||||||
throw new PlayerError_1.PlayerError("Unknown Guild", PlayerError_1.ErrorStatusCode.UNKNOWN_GUILD);
|
|
||||||
return this.queues.get(guild.id);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Deletes a queue and returns deleted queue object
|
|
||||||
* @param {GuildResolvable} guild The guild id to remove
|
|
||||||
* @returns {Queue}
|
|
||||||
*/
|
|
||||||
deleteQueue(guild) {
|
|
||||||
guild = this.client.guilds.resolve(guild);
|
|
||||||
if (!guild)
|
|
||||||
throw new PlayerError_1.PlayerError("Unknown Guild", PlayerError_1.ErrorStatusCode.UNKNOWN_GUILD);
|
|
||||||
const prev = this.getQueue(guild);
|
|
||||||
try {
|
|
||||||
prev.destroy();
|
|
||||||
}
|
|
||||||
catch { } // eslint-disable-line no-empty
|
|
||||||
this.queues.delete(guild.id);
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @typedef {object} PlayerSearchResult
|
|
||||||
* @property {Playlist} [playlist] The playlist (if any)
|
|
||||||
* @property {Track[]} tracks The tracks
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Search tracks
|
|
||||||
* @param {string|Track} query The search query
|
|
||||||
* @param {SearchOptions} options The search options
|
|
||||||
* @returns {Promise<PlayerSearchResult>}
|
|
||||||
*/
|
|
||||||
async search(query, options) {
|
|
||||||
if (query instanceof Track_1.default)
|
|
||||||
return { playlist: query.playlist || null, tracks: [query] };
|
|
||||||
if (!options)
|
|
||||||
throw new PlayerError_1.PlayerError("DiscordPlayer#search needs search options!", PlayerError_1.ErrorStatusCode.INVALID_ARG_TYPE);
|
|
||||||
options.requestedBy = this.client.users.resolve(options.requestedBy);
|
|
||||||
if (!("searchEngine" in options))
|
|
||||||
options.searchEngine = types_1.QueryType.AUTO;
|
|
||||||
if (typeof options.searchEngine === "string" && this.extractors.has(options.searchEngine)) {
|
|
||||||
const extractor = this.extractors.get(options.searchEngine);
|
|
||||||
if (!extractor.validate(query))
|
|
||||||
return { playlist: null, tracks: [] };
|
|
||||||
const data = await extractor.handle(query);
|
|
||||||
if (data && data.data.length) {
|
|
||||||
const playlist = !data.playlist
|
|
||||||
? null
|
|
||||||
: new Playlist_1.Playlist(this, {
|
|
||||||
...data.playlist,
|
|
||||||
tracks: []
|
|
||||||
});
|
|
||||||
const tracks = data.data.map((m) => new Track_1.default(this, {
|
|
||||||
...m,
|
|
||||||
requestedBy: options.requestedBy,
|
|
||||||
duration: Util_1.Util.buildTimeCode(Util_1.Util.parseMS(m.duration)),
|
|
||||||
playlist: playlist
|
|
||||||
}));
|
|
||||||
if (playlist)
|
|
||||||
playlist.tracks = tracks;
|
|
||||||
return { playlist: playlist, tracks: tracks };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
for (const [_, extractor] of this.extractors) {
|
|
||||||
if (options.blockExtractor)
|
|
||||||
break;
|
|
||||||
if (!extractor.validate(query))
|
|
||||||
continue;
|
|
||||||
const data = await extractor.handle(query);
|
|
||||||
if (data && data.data.length) {
|
|
||||||
const playlist = !data.playlist
|
|
||||||
? null
|
|
||||||
: new Playlist_1.Playlist(this, {
|
|
||||||
...data.playlist,
|
|
||||||
tracks: []
|
|
||||||
});
|
|
||||||
const tracks = data.data.map((m) => new Track_1.default(this, {
|
|
||||||
...m,
|
|
||||||
requestedBy: options.requestedBy,
|
|
||||||
duration: Util_1.Util.buildTimeCode(Util_1.Util.parseMS(m.duration)),
|
|
||||||
playlist: playlist
|
|
||||||
}));
|
|
||||||
if (playlist)
|
|
||||||
playlist.tracks = tracks;
|
|
||||||
return { playlist: playlist, tracks: tracks };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const qt = options.searchEngine === types_1.QueryType.AUTO ? await QueryResolver_1.QueryResolver.resolve(query) : options.searchEngine;
|
|
||||||
switch (qt) {
|
|
||||||
case types_1.QueryType.YOUTUBE_VIDEO: {
|
|
||||||
const info = await play_dl_1.default.video_info(query).catch(Util_1.Util.noop);
|
|
||||||
if (!info)
|
|
||||||
return { playlist: null, tracks: [] };
|
|
||||||
const track = new Track_1.default(this, {
|
|
||||||
title: info.video_details.title,
|
|
||||||
description: info.video_details.description,
|
|
||||||
author: info.video_details.channel?.name,
|
|
||||||
url: info.video_details.url,
|
|
||||||
requestedBy: options.requestedBy,
|
|
||||||
thumbnail: Util_1.Util.last(info.video_details.thumbnails)?.url,
|
|
||||||
views: info.video_details.views || 0,
|
|
||||||
duration: Util_1.Util.buildTimeCode(Util_1.Util.parseMS(info.video_details.durationInSec * 1000)),
|
|
||||||
source: "youtube",
|
|
||||||
raw: info
|
|
||||||
});
|
|
||||||
return { playlist: null, tracks: [track] };
|
|
||||||
}
|
|
||||||
case types_1.QueryType.YOUTUBE_SEARCH: {
|
|
||||||
const videos = await play_dl_1.default.search(query, {
|
|
||||||
limit: 10,
|
|
||||||
source: { youtube: "video" }
|
|
||||||
}).catch(Util_1.Util.noop);
|
|
||||||
if (!videos)
|
|
||||||
return { playlist: null, tracks: [] };
|
|
||||||
const tracks = videos.map(m => {
|
|
||||||
m.source = "youtube"; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
return new Track_1.default(this, {
|
|
||||||
title: m.title,
|
|
||||||
description: m.description,
|
|
||||||
author: m.channel?.name,
|
|
||||||
url: m.url,
|
|
||||||
requestedBy: options.requestedBy,
|
|
||||||
thumbnail: Util_1.Util.last(m.thumbnails).url,
|
|
||||||
views: m.views,
|
|
||||||
duration: m.durationRaw,
|
|
||||||
source: "youtube",
|
|
||||||
raw: m
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return { playlist: null, tracks, searched: true };
|
|
||||||
}
|
|
||||||
case types_1.QueryType.SOUNDCLOUD_TRACK:
|
|
||||||
case types_1.QueryType.SOUNDCLOUD_SEARCH: {
|
|
||||||
const result = await QueryResolver_1.QueryResolver.resolve(query) === types_1.QueryType.SOUNDCLOUD_TRACK ? [{ url: query }] : await play_dl_1.default.search(query, {
|
|
||||||
limit: 5,
|
|
||||||
source: { soundcloud: "tracks" }
|
|
||||||
}).catch(() => []);
|
|
||||||
if (!result || !result.length)
|
|
||||||
return { playlist: null, tracks: [] };
|
|
||||||
const res = [];
|
|
||||||
for (const r of result) {
|
|
||||||
const trackInfo = await play_dl_1.default.soundcloud(r.url).catch(Util_1.Util.noop);
|
|
||||||
if (!trackInfo)
|
|
||||||
continue;
|
|
||||||
const track = new Track_1.default(this, {
|
|
||||||
title: trackInfo.name,
|
|
||||||
url: trackInfo.url,
|
|
||||||
duration: Util_1.Util.buildTimeCode(Util_1.Util.parseMS(trackInfo.durationInMs)),
|
|
||||||
description: "",
|
|
||||||
thumbnail: trackInfo.user.thumbnail,
|
|
||||||
views: 0,
|
|
||||||
author: trackInfo.user.name,
|
|
||||||
requestedBy: options.requestedBy,
|
|
||||||
source: "soundcloud",
|
|
||||||
engine: trackInfo
|
|
||||||
});
|
|
||||||
res.push(track);
|
|
||||||
}
|
|
||||||
return { playlist: null, tracks: res };
|
|
||||||
}
|
|
||||||
case types_1.QueryType.SPOTIFY_SONG: {
|
|
||||||
const spotifyData = await (0, spotify_url_info_1.default)(await Util_1.Util.getFetch())
|
|
||||||
.getData(query)
|
|
||||||
.catch(Util_1.Util.noop);
|
|
||||||
if (!spotifyData)
|
|
||||||
return { playlist: null, tracks: [] };
|
|
||||||
const spotifyTrack = new Track_1.default(this, {
|
|
||||||
title: spotifyData.name,
|
|
||||||
description: spotifyData.description ?? "",
|
|
||||||
author: spotifyData.artists[0]?.name ?? "Unknown Artist",
|
|
||||||
url: spotifyData.external_urls?.spotify ?? query,
|
|
||||||
thumbnail: spotifyData.album?.images[0]?.url ?? spotifyData.preview_url?.length
|
|
||||||
? `https://i.scdn.co/image/${spotifyData.preview_url?.split("?cid=")[1]}`
|
|
||||||
: "https://www.scdn.co/i/_global/twitter_card-default.jpg",
|
|
||||||
duration: Util_1.Util.buildTimeCode(Util_1.Util.parseMS(spotifyData.duration_ms)),
|
|
||||||
views: 0,
|
|
||||||
requestedBy: options.requestedBy,
|
|
||||||
source: "spotify"
|
|
||||||
});
|
|
||||||
return { playlist: null, tracks: [spotifyTrack] };
|
|
||||||
}
|
|
||||||
case types_1.QueryType.SPOTIFY_PLAYLIST:
|
|
||||||
case types_1.QueryType.SPOTIFY_ALBUM: {
|
|
||||||
const spotifyPlaylist = await (0, spotify_url_info_1.default)(await Util_1.Util.getFetch())
|
|
||||||
.getData(query)
|
|
||||||
.catch(Util_1.Util.noop);
|
|
||||||
if (!spotifyPlaylist)
|
|
||||||
return { playlist: null, tracks: [] };
|
|
||||||
const playlist = new Playlist_1.Playlist(this, {
|
|
||||||
title: spotifyPlaylist.name ?? spotifyPlaylist.title,
|
|
||||||
description: spotifyPlaylist.description ?? "",
|
|
||||||
thumbnail: spotifyPlaylist.images[0]?.url ?? "https://www.scdn.co/i/_global/twitter_card-default.jpg",
|
|
||||||
type: spotifyPlaylist.type,
|
|
||||||
source: "spotify",
|
|
||||||
author: spotifyPlaylist.type !== "playlist"
|
|
||||||
? {
|
|
||||||
name: spotifyPlaylist.artists[0]?.name ?? "Unknown Artist",
|
|
||||||
url: spotifyPlaylist.artists[0]?.external_urls?.spotify ?? null
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
name: spotifyPlaylist.owner?.display_name ?? spotifyPlaylist.owner?.id ?? "Unknown Artist",
|
|
||||||
url: spotifyPlaylist.owner?.external_urls?.spotify ?? null
|
|
||||||
},
|
|
||||||
tracks: [],
|
|
||||||
id: spotifyPlaylist.id,
|
|
||||||
url: spotifyPlaylist.external_urls?.spotify ?? query,
|
|
||||||
rawPlaylist: spotifyPlaylist
|
|
||||||
});
|
|
||||||
if (spotifyPlaylist.type !== "playlist") {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
playlist.tracks = spotifyPlaylist.tracks.items.map((m) => {
|
|
||||||
const data = new Track_1.default(this, {
|
|
||||||
title: m.name ?? "",
|
|
||||||
description: m.description ?? "",
|
|
||||||
author: m.artists[0]?.name ?? "Unknown Artist",
|
|
||||||
url: m.external_urls?.spotify ?? query,
|
|
||||||
thumbnail: spotifyPlaylist.images[0]?.url ?? "https://www.scdn.co/i/_global/twitter_card-default.jpg",
|
|
||||||
duration: Util_1.Util.buildTimeCode(Util_1.Util.parseMS(m.duration_ms)),
|
|
||||||
views: 0,
|
|
||||||
requestedBy: options.requestedBy,
|
|
||||||
playlist,
|
|
||||||
source: "spotify"
|
|
||||||
});
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
playlist.tracks = spotifyPlaylist.tracks.items.map((m) => {
|
|
||||||
const data = new Track_1.default(this, {
|
|
||||||
title: m.track.name ?? "",
|
|
||||||
description: m.track.description ?? "",
|
|
||||||
author: m.track.artists?.[0]?.name ?? "Unknown Artist",
|
|
||||||
url: m.track.external_urls?.spotify ?? query,
|
|
||||||
thumbnail: m.track.album?.images?.[0]?.url ?? "https://www.scdn.co/i/_global/twitter_card-default.jpg",
|
|
||||||
duration: Util_1.Util.buildTimeCode(Util_1.Util.parseMS(m.track.duration_ms)),
|
|
||||||
views: 0,
|
|
||||||
requestedBy: options.requestedBy,
|
|
||||||
playlist,
|
|
||||||
source: "spotify"
|
|
||||||
});
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return { playlist: playlist, tracks: playlist.tracks };
|
|
||||||
}
|
|
||||||
case types_1.QueryType.SOUNDCLOUD_PLAYLIST: {
|
|
||||||
const data = await play_dl_1.default.soundcloud(query).catch(Util_1.Util.noop);
|
|
||||||
if (!data)
|
|
||||||
return { playlist: null, tracks: [] };
|
|
||||||
const res = new Playlist_1.Playlist(this, {
|
|
||||||
title: data.name,
|
|
||||||
description: "",
|
|
||||||
thumbnail: "https://soundcloud.com/pwa-icon-192.png",
|
|
||||||
type: "playlist",
|
|
||||||
source: "soundcloud",
|
|
||||||
author: {
|
|
||||||
name: data.user.name ?? "Unknown Owner",
|
|
||||||
url: data.user.url
|
|
||||||
},
|
|
||||||
tracks: [],
|
|
||||||
id: `${data.id}`,
|
|
||||||
url: data.url,
|
|
||||||
rawPlaylist: data
|
|
||||||
});
|
|
||||||
const songs = await data.all_tracks();
|
|
||||||
for (const song of songs) {
|
|
||||||
const track = new Track_1.default(this, {
|
|
||||||
title: song.name,
|
|
||||||
description: "",
|
|
||||||
author: song.publisher.name ?? "Unknown Publisher",
|
|
||||||
url: song.url,
|
|
||||||
thumbnail: song.thumbnail,
|
|
||||||
duration: Util_1.Util.buildTimeCode(Util_1.Util.parseMS(song.durationInMs)),
|
|
||||||
views: 0,
|
|
||||||
requestedBy: options.requestedBy,
|
|
||||||
playlist: res,
|
|
||||||
source: "soundcloud",
|
|
||||||
engine: song
|
|
||||||
});
|
|
||||||
res.tracks.push(track);
|
|
||||||
}
|
|
||||||
return { playlist: res, tracks: res.tracks };
|
|
||||||
}
|
|
||||||
case types_1.QueryType.YOUTUBE_PLAYLIST: {
|
|
||||||
const ytpl = await play_dl_1.default.playlist_info(query, { incomplete: true }).catch(Util_1.Util.noop);
|
|
||||||
if (!ytpl)
|
|
||||||
return { playlist: null, tracks: [] };
|
|
||||||
const playlist = new Playlist_1.Playlist(this, {
|
|
||||||
title: ytpl.title,
|
|
||||||
thumbnail: ytpl.thumbnail,
|
|
||||||
description: "",
|
|
||||||
type: "playlist",
|
|
||||||
source: "youtube",
|
|
||||||
author: {
|
|
||||||
name: ytpl.channel.name,
|
|
||||||
url: ytpl.channel.url
|
|
||||||
},
|
|
||||||
tracks: [],
|
|
||||||
id: ytpl.id,
|
|
||||||
url: ytpl.url,
|
|
||||||
rawPlaylist: ytpl
|
|
||||||
});
|
|
||||||
const videos = await ytpl.all_videos();
|
|
||||||
playlist.tracks = videos.map(video => new Track_1.default(this, {
|
|
||||||
title: video.title,
|
|
||||||
description: video.description,
|
|
||||||
author: video.channel?.name,
|
|
||||||
url: video.url,
|
|
||||||
requestedBy: options.requestedBy,
|
|
||||||
thumbnail: Util_1.Util.last(video.thumbnails).url,
|
|
||||||
views: video.views,
|
|
||||||
duration: video.durationRaw,
|
|
||||||
raw: video,
|
|
||||||
playlist: playlist,
|
|
||||||
source: "youtube"
|
|
||||||
}));
|
|
||||||
return { playlist: playlist, tracks: playlist.tracks };
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return { playlist: null, tracks: [] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Registers extractor
|
|
||||||
* @param {string} extractorName The extractor name
|
|
||||||
* @param {ExtractorModel|any} extractor The extractor object
|
|
||||||
* @param {boolean} [force=false] Overwrite existing extractor with this name (if available)
|
|
||||||
* @returns {ExtractorModel}
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
use(extractorName, extractor, force = false) {
|
|
||||||
if (!extractorName)
|
|
||||||
throw new PlayerError_1.PlayerError("Cannot use unknown extractor!", PlayerError_1.ErrorStatusCode.UNKNOWN_EXTRACTOR);
|
|
||||||
if (this.extractors.has(extractorName) && !force)
|
|
||||||
return this.extractors.get(extractorName);
|
|
||||||
if (extractor instanceof ExtractorModel_1.ExtractorModel) {
|
|
||||||
this.extractors.set(extractorName, extractor);
|
|
||||||
return extractor;
|
|
||||||
}
|
|
||||||
for (const method of ["validate", "getInfo"]) {
|
|
||||||
if (typeof extractor[method] !== "function")
|
|
||||||
throw new PlayerError_1.PlayerError("Invalid extractor data!", PlayerError_1.ErrorStatusCode.INVALID_EXTRACTOR);
|
|
||||||
}
|
|
||||||
const model = new ExtractorModel_1.ExtractorModel(extractorName, extractor);
|
|
||||||
this.extractors.set(model.name, model);
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Removes registered extractor
|
|
||||||
* @param {string} extractorName The extractor name
|
|
||||||
* @returns {ExtractorModel}
|
|
||||||
*/
|
|
||||||
unuse(extractorName) {
|
|
||||||
if (!this.extractors.has(extractorName))
|
|
||||||
throw new PlayerError_1.PlayerError(`Cannot find extractor "${extractorName}"`, PlayerError_1.ErrorStatusCode.UNKNOWN_EXTRACTOR);
|
|
||||||
const prev = this.extractors.get(extractorName);
|
|
||||||
this.extractors.delete(extractorName);
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Generates a report of the dependencies used by the `@discordjs/voice` module. Useful for debugging.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
scanDeps() {
|
|
||||||
const line = "-".repeat(50);
|
|
||||||
const depsReport = (0, voice_1.generateDependencyReport)();
|
|
||||||
const extractorReport = this.extractors
|
|
||||||
.map((m) => {
|
|
||||||
return `${m.name} :: ${m.version || "0.1.0"}`;
|
|
||||||
})
|
|
||||||
.join("\n");
|
|
||||||
return `${depsReport}\n${line}\nLoaded Extractors:\n${extractorReport || "None"}`;
|
|
||||||
}
|
|
||||||
emit(eventName, ...args) {
|
|
||||||
if (this.requiredEvents.includes(eventName) && !super.eventNames().includes(eventName)) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error(...args);
|
|
||||||
process.emitWarning(`[DiscordPlayerWarning] Unhandled "${eventName}" event! Events ${this.requiredEvents.map((m) => `"${m}"`).join(", ")} must have event listeners!`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return super.emit(eventName, ...args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Resolves queue
|
|
||||||
* @param {GuildResolvable|Queue} queueLike Queue like object
|
|
||||||
* @returns {Queue}
|
|
||||||
*/
|
|
||||||
resolveQueue(queueLike) {
|
|
||||||
return this.getQueue(queueLike instanceof Queue_1.Queue ? queueLike.guild : queueLike);
|
|
||||||
}
|
|
||||||
*[Symbol.iterator]() {
|
|
||||||
yield* Array.from(this.queues.values());
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Creates `Playlist` instance
|
|
||||||
* @param data The data to initialize a playlist
|
|
||||||
*/
|
|
||||||
createPlaylist(data) {
|
|
||||||
return new Playlist_1.Playlist(this, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.Player = Player;
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { ExtractorModelData } from "../types/types";
|
|
||||||
declare class ExtractorModel {
|
|
||||||
name: string;
|
|
||||||
private _raw;
|
|
||||||
/**
|
|
||||||
* Model for raw Discord Player extractors
|
|
||||||
* @param {string} extractorName Name of the extractor
|
|
||||||
* @param {object} data Extractor object
|
|
||||||
*/
|
|
||||||
constructor(extractorName: string, data: any);
|
|
||||||
/**
|
|
||||||
* Method to handle requests from `Player.play()`
|
|
||||||
* @param {string} query Query to handle
|
|
||||||
* @returns {Promise<ExtractorModelData>}
|
|
||||||
*/
|
|
||||||
handle(query: string): Promise<ExtractorModelData>;
|
|
||||||
/**
|
|
||||||
* Method used by Discord Player to validate query with this extractor
|
|
||||||
* @param {string} query The query to validate
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
validate(query: string): boolean;
|
|
||||||
/**
|
|
||||||
* The extractor version
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
get version(): string;
|
|
||||||
}
|
|
||||||
export { ExtractorModel };
|
|
65
helpers/Music/dist/Structures/ExtractorModel.js
vendored
65
helpers/Music/dist/Structures/ExtractorModel.js
vendored
|
@ -1,65 +0,0 @@
|
||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.ExtractorModel = void 0;
|
|
||||||
class ExtractorModel {
|
|
||||||
/**
|
|
||||||
* Model for raw Discord Player extractors
|
|
||||||
* @param {string} extractorName Name of the extractor
|
|
||||||
* @param {object} data Extractor object
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
constructor(extractorName, data) {
|
|
||||||
/**
|
|
||||||
* The extractor name
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.name = extractorName;
|
|
||||||
/**
|
|
||||||
* The raw model
|
|
||||||
* @name ExtractorModel#_raw
|
|
||||||
* @type {any}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
Object.defineProperty(this, "_raw", { value: data, configurable: false, writable: false, enumerable: false });
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Method to handle requests from `Player.play()`
|
|
||||||
* @param {string} query Query to handle
|
|
||||||
* @returns {Promise<ExtractorModelData>}
|
|
||||||
*/
|
|
||||||
async handle(query) {
|
|
||||||
const data = await this._raw.getInfo(query);
|
|
||||||
if (!data)
|
|
||||||
return null;
|
|
||||||
return {
|
|
||||||
playlist: data.playlist ?? null,
|
|
||||||
data: data.info?.map((m) => ({
|
|
||||||
title: m.title,
|
|
||||||
duration: m.duration,
|
|
||||||
thumbnail: m.thumbnail,
|
|
||||||
engine: m.engine,
|
|
||||||
views: m.views,
|
|
||||||
author: m.author,
|
|
||||||
description: m.description,
|
|
||||||
url: m.url,
|
|
||||||
source: m.source || "arbitrary"
|
|
||||||
})) ?? []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Method used by Discord Player to validate query with this extractor
|
|
||||||
* @param {string} query The query to validate
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
validate(query) {
|
|
||||||
return Boolean(this._raw.validate(query));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The extractor version
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
get version() {
|
|
||||||
return this._raw.version ?? "0.0.0";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.ExtractorModel = ExtractorModel;
|
|
31
helpers/Music/dist/Structures/PlayerError.d.ts
vendored
31
helpers/Music/dist/Structures/PlayerError.d.ts
vendored
|
@ -1,31 +0,0 @@
|
||||||
export declare enum ErrorStatusCode {
|
|
||||||
STREAM_ERROR = "StreamError",
|
|
||||||
AUDIO_PLAYER_ERROR = "AudioPlayerError",
|
|
||||||
PLAYER_ERROR = "PlayerError",
|
|
||||||
NO_AUDIO_RESOURCE = "NoAudioResource",
|
|
||||||
UNKNOWN_GUILD = "UnknownGuild",
|
|
||||||
INVALID_ARG_TYPE = "InvalidArgType",
|
|
||||||
UNKNOWN_EXTRACTOR = "UnknownExtractor",
|
|
||||||
INVALID_EXTRACTOR = "InvalidExtractor",
|
|
||||||
INVALID_CHANNEL_TYPE = "InvalidChannelType",
|
|
||||||
INVALID_TRACK = "InvalidTrack",
|
|
||||||
UNKNOWN_REPEAT_MODE = "UnknownRepeatMode",
|
|
||||||
TRACK_NOT_FOUND = "TrackNotFound",
|
|
||||||
NO_CONNECTION = "NoConnection",
|
|
||||||
DESTROYED_QUEUE = "DestroyedQueue"
|
|
||||||
}
|
|
||||||
export declare class PlayerError extends Error {
|
|
||||||
message: string;
|
|
||||||
statusCode: ErrorStatusCode;
|
|
||||||
createdAt: Date;
|
|
||||||
constructor(message: string, code?: ErrorStatusCode);
|
|
||||||
get createdTimestamp(): number;
|
|
||||||
valueOf(): ErrorStatusCode;
|
|
||||||
toJSON(): {
|
|
||||||
stack: string;
|
|
||||||
code: ErrorStatusCode;
|
|
||||||
message: string;
|
|
||||||
created: number;
|
|
||||||
};
|
|
||||||
toString(): string;
|
|
||||||
}
|
|
48
helpers/Music/dist/Structures/PlayerError.js
vendored
48
helpers/Music/dist/Structures/PlayerError.js
vendored
|
@ -1,48 +0,0 @@
|
||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.PlayerError = exports.ErrorStatusCode = void 0;
|
|
||||||
var ErrorStatusCode;
|
|
||||||
(function (ErrorStatusCode) {
|
|
||||||
ErrorStatusCode["STREAM_ERROR"] = "StreamError";
|
|
||||||
ErrorStatusCode["AUDIO_PLAYER_ERROR"] = "AudioPlayerError";
|
|
||||||
ErrorStatusCode["PLAYER_ERROR"] = "PlayerError";
|
|
||||||
ErrorStatusCode["NO_AUDIO_RESOURCE"] = "NoAudioResource";
|
|
||||||
ErrorStatusCode["UNKNOWN_GUILD"] = "UnknownGuild";
|
|
||||||
ErrorStatusCode["INVALID_ARG_TYPE"] = "InvalidArgType";
|
|
||||||
ErrorStatusCode["UNKNOWN_EXTRACTOR"] = "UnknownExtractor";
|
|
||||||
ErrorStatusCode["INVALID_EXTRACTOR"] = "InvalidExtractor";
|
|
||||||
ErrorStatusCode["INVALID_CHANNEL_TYPE"] = "InvalidChannelType";
|
|
||||||
ErrorStatusCode["INVALID_TRACK"] = "InvalidTrack";
|
|
||||||
ErrorStatusCode["UNKNOWN_REPEAT_MODE"] = "UnknownRepeatMode";
|
|
||||||
ErrorStatusCode["TRACK_NOT_FOUND"] = "TrackNotFound";
|
|
||||||
ErrorStatusCode["NO_CONNECTION"] = "NoConnection";
|
|
||||||
ErrorStatusCode["DESTROYED_QUEUE"] = "DestroyedQueue";
|
|
||||||
})(ErrorStatusCode = exports.ErrorStatusCode || (exports.ErrorStatusCode = {}));
|
|
||||||
class PlayerError extends Error {
|
|
||||||
constructor(message, code = ErrorStatusCode.PLAYER_ERROR) {
|
|
||||||
super();
|
|
||||||
this.createdAt = new Date();
|
|
||||||
this.message = `[${code}] ${message}`;
|
|
||||||
this.statusCode = code;
|
|
||||||
this.name = code;
|
|
||||||
Error.captureStackTrace(this);
|
|
||||||
}
|
|
||||||
get createdTimestamp() {
|
|
||||||
return this.createdAt.getTime();
|
|
||||||
}
|
|
||||||
valueOf() {
|
|
||||||
return this.statusCode;
|
|
||||||
}
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
stack: this.stack,
|
|
||||||
code: this.statusCode,
|
|
||||||
message: this.message,
|
|
||||||
created: this.createdTimestamp
|
|
||||||
};
|
|
||||||
}
|
|
||||||
toString() {
|
|
||||||
return this.stack;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.PlayerError = PlayerError;
|
|
33
helpers/Music/dist/Structures/Playlist.d.ts
vendored
33
helpers/Music/dist/Structures/Playlist.d.ts
vendored
|
@ -1,33 +0,0 @@
|
||||||
import { Player } from "../Player";
|
|
||||||
import { Track } from "./Track";
|
|
||||||
import { PlaylistInitData, PlaylistJSON, TrackSource } from "../types/types";
|
|
||||||
declare class Playlist {
|
|
||||||
readonly player: Player;
|
|
||||||
tracks: Track[];
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
thumbnail: string;
|
|
||||||
type: "album" | "playlist";
|
|
||||||
source: TrackSource;
|
|
||||||
author: {
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
};
|
|
||||||
id: string;
|
|
||||||
url: string;
|
|
||||||
readonly rawPlaylist?: any;
|
|
||||||
/**
|
|
||||||
* Playlist constructor
|
|
||||||
* @param {Player} player The player
|
|
||||||
* @param {PlaylistInitData} data The data
|
|
||||||
*/
|
|
||||||
constructor(player: Player, data: PlaylistInitData);
|
|
||||||
[Symbol.iterator](): Generator<Track, void, undefined>;
|
|
||||||
/**
|
|
||||||
* JSON representation of this playlist
|
|
||||||
* @param {boolean} [withTracks=true] If it should build json with tracks
|
|
||||||
* @returns {PlaylistJSON}
|
|
||||||
*/
|
|
||||||
toJSON(withTracks?: boolean): PlaylistJSON;
|
|
||||||
}
|
|
||||||
export { Playlist };
|
|
108
helpers/Music/dist/Structures/Playlist.js
vendored
108
helpers/Music/dist/Structures/Playlist.js
vendored
|
@ -1,108 +0,0 @@
|
||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.Playlist = void 0;
|
|
||||||
class Playlist {
|
|
||||||
/**
|
|
||||||
* Playlist constructor
|
|
||||||
* @param {Player} player The player
|
|
||||||
* @param {PlaylistInitData} data The data
|
|
||||||
*/
|
|
||||||
constructor(player, data) {
|
|
||||||
/**
|
|
||||||
* The player
|
|
||||||
* @name Playlist#player
|
|
||||||
* @type {Player}
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
this.player = player;
|
|
||||||
/**
|
|
||||||
* The tracks in this playlist
|
|
||||||
* @name Playlist#tracks
|
|
||||||
* @type {Track[]}
|
|
||||||
*/
|
|
||||||
this.tracks = data.tracks ?? [];
|
|
||||||
/**
|
|
||||||
* The author of this playlist
|
|
||||||
* @name Playlist#author
|
|
||||||
* @type {object}
|
|
||||||
*/
|
|
||||||
this.author = data.author;
|
|
||||||
/**
|
|
||||||
* The description
|
|
||||||
* @name Playlist#description
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.description = data.description;
|
|
||||||
/**
|
|
||||||
* The thumbnail of this playlist
|
|
||||||
* @name Playlist#thumbnail
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.thumbnail = data.thumbnail;
|
|
||||||
/**
|
|
||||||
* The playlist type:
|
|
||||||
* - `album`
|
|
||||||
* - `playlist`
|
|
||||||
* @name Playlist#type
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.type = data.type;
|
|
||||||
/**
|
|
||||||
* The source of this playlist:
|
|
||||||
* - `youtube`
|
|
||||||
* - `soundcloud`
|
|
||||||
* - `spotify`
|
|
||||||
* - `arbitrary`
|
|
||||||
* @name Playlist#source
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.source = data.source;
|
|
||||||
/**
|
|
||||||
* The playlist id
|
|
||||||
* @name Playlist#id
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.id = data.id;
|
|
||||||
/**
|
|
||||||
* The playlist url
|
|
||||||
* @name Playlist#url
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.url = data.url;
|
|
||||||
/**
|
|
||||||
* The playlist title
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.title = data.title;
|
|
||||||
/**
|
|
||||||
* @name Playlist#rawPlaylist
|
|
||||||
* @type {any}
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
*[Symbol.iterator]() {
|
|
||||||
yield* this.tracks;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* JSON representation of this playlist
|
|
||||||
* @param {boolean} [withTracks=true] If it should build json with tracks
|
|
||||||
* @returns {PlaylistJSON}
|
|
||||||
*/
|
|
||||||
toJSON(withTracks = true) {
|
|
||||||
const payload = {
|
|
||||||
id: this.id,
|
|
||||||
url: this.url,
|
|
||||||
title: this.title,
|
|
||||||
description: this.description,
|
|
||||||
thumbnail: this.thumbnail,
|
|
||||||
type: this.type,
|
|
||||||
source: this.source,
|
|
||||||
author: this.author,
|
|
||||||
tracks: []
|
|
||||||
};
|
|
||||||
if (withTracks)
|
|
||||||
payload.tracks = this.tracks.map((m) => m.toJSON(true));
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.Playlist = Playlist;
|
|
241
helpers/Music/dist/Structures/Queue.d.ts
vendored
241
helpers/Music/dist/Structures/Queue.d.ts
vendored
|
@ -1,241 +0,0 @@
|
||||||
/// <reference types="node" />
|
|
||||||
/// <reference types="node" />
|
|
||||||
import { Collection, Guild, GuildChannelResolvable } from "discord.js";
|
|
||||||
import { Player } from "../Player";
|
|
||||||
import { StreamDispatcher } from "../VoiceInterface/StreamDispatcher";
|
|
||||||
import Track from "./Track";
|
|
||||||
import { PlayerOptions, PlayerProgressbarOptions, PlayOptions, QueueFilters, QueueRepeatMode, TrackSource } from "../types/types";
|
|
||||||
import type { Readable } from "stream";
|
|
||||||
declare class Queue<T = unknown> {
|
|
||||||
#private;
|
|
||||||
readonly guild: Guild;
|
|
||||||
readonly player: Player;
|
|
||||||
connection: StreamDispatcher;
|
|
||||||
tracks: Track[];
|
|
||||||
previousTracks: Track[];
|
|
||||||
options: PlayerOptions;
|
|
||||||
playing: boolean;
|
|
||||||
metadata?: T;
|
|
||||||
repeatMode: QueueRepeatMode;
|
|
||||||
readonly id: string;
|
|
||||||
private _streamTime;
|
|
||||||
_cooldownsTimeout: Collection<string, NodeJS.Timeout>;
|
|
||||||
private _activeFilters;
|
|
||||||
private _filtersUpdate;
|
|
||||||
onBeforeCreateStream: (track: Track, source: TrackSource, queue: Queue) => Promise<Readable | undefined>;
|
|
||||||
/**
|
|
||||||
* Queue constructor
|
|
||||||
* @param {Player} player The player that instantiated this queue
|
|
||||||
* @param {Guild} guild The guild that instantiated this queue
|
|
||||||
* @param {PlayerOptions} [options] Player options for the queue
|
|
||||||
*/
|
|
||||||
constructor(player: Player, guild: Guild, options?: PlayerOptions);
|
|
||||||
/**
|
|
||||||
* Returns current track
|
|
||||||
* @type {Track}
|
|
||||||
*/
|
|
||||||
get current(): Track;
|
|
||||||
/**
|
|
||||||
* If this queue is destroyed
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
get destroyed(): boolean;
|
|
||||||
/**
|
|
||||||
* Returns current track
|
|
||||||
* @returns {Track}
|
|
||||||
*/
|
|
||||||
nowPlaying(): Track;
|
|
||||||
/**
|
|
||||||
* Connects to a voice channel
|
|
||||||
* @param {GuildChannelResolvable} channel The voice/stage channel
|
|
||||||
* @returns {Promise<Queue>}
|
|
||||||
*/
|
|
||||||
connect(channel: GuildChannelResolvable): Promise<this>;
|
|
||||||
/**
|
|
||||||
* Destroys this queue
|
|
||||||
* @param {boolean} [disconnect=this.options.leaveOnStop] If it should leave on destroy
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
destroy(disconnect?: boolean): void;
|
|
||||||
/**
|
|
||||||
* Skips current track
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
skip(): boolean;
|
|
||||||
/**
|
|
||||||
* Adds single track to the queue
|
|
||||||
* @param {Track} track The track to add
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
addTrack(track: Track): void;
|
|
||||||
/**
|
|
||||||
* Adds multiple tracks to the queue
|
|
||||||
* @param {Track[]} tracks Array of tracks to add
|
|
||||||
*/
|
|
||||||
addTracks(tracks: Track[]): void;
|
|
||||||
/**
|
|
||||||
* Sets paused state
|
|
||||||
* @param {boolean} paused The paused state
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
setPaused(paused?: boolean): boolean;
|
|
||||||
/**
|
|
||||||
* Sets bitrate
|
|
||||||
* @param {number|auto} bitrate bitrate to set
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
setBitrate(bitrate: number | "auto"): void;
|
|
||||||
/**
|
|
||||||
* Sets volume
|
|
||||||
* @param {number} amount The volume amount
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
setVolume(amount: number): boolean;
|
|
||||||
/**
|
|
||||||
* Sets repeat mode
|
|
||||||
* @param {QueueRepeatMode} mode The repeat mode
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
setRepeatMode(mode: QueueRepeatMode): boolean;
|
|
||||||
/**
|
|
||||||
* The current volume amount
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
get volume(): number;
|
|
||||||
set volume(amount: number);
|
|
||||||
/**
|
|
||||||
* The stream time of this queue
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
get streamTime(): number;
|
|
||||||
set streamTime(time: number);
|
|
||||||
/**
|
|
||||||
* Returns enabled filters
|
|
||||||
* @returns {AudioFilters}
|
|
||||||
*/
|
|
||||||
getFiltersEnabled(): (keyof QueueFilters)[];
|
|
||||||
/**
|
|
||||||
* Returns disabled filters
|
|
||||||
* @returns {AudioFilters}
|
|
||||||
*/
|
|
||||||
getFiltersDisabled(): (keyof QueueFilters)[];
|
|
||||||
/**
|
|
||||||
* Sets filters
|
|
||||||
* @param {QueueFilters} filters Queue filters
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
setFilters(filters?: QueueFilters): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Seeks to the given time
|
|
||||||
* @param {number} position The position
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
seek(position: number): Promise<boolean>;
|
|
||||||
/**
|
|
||||||
* Plays previous track
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
back(): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Clear this queue
|
|
||||||
*/
|
|
||||||
clear(): void;
|
|
||||||
/**
|
|
||||||
* Stops the player
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
stop(): void;
|
|
||||||
/**
|
|
||||||
* Shuffles this queue
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
shuffle(): boolean;
|
|
||||||
/**
|
|
||||||
* Removes a track from the queue
|
|
||||||
* @param {Track|string|number} track The track to remove
|
|
||||||
* @returns {Track}
|
|
||||||
*/
|
|
||||||
remove(track: Track | string | number): Track;
|
|
||||||
/**
|
|
||||||
* Returns the index of the specified track. If found, returns the track index else returns -1.
|
|
||||||
* @param {number|Track|string} track The track
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
getTrackPosition(track: number | Track | string): number;
|
|
||||||
/**
|
|
||||||
* Jumps to particular track
|
|
||||||
* @param {Track|number} track The track
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
jump(track: Track | number): void;
|
|
||||||
/**
|
|
||||||
* Jumps to particular track, removing other tracks on the way
|
|
||||||
* @param {Track|number} track The track
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
skipTo(track: Track | number): void;
|
|
||||||
/**
|
|
||||||
* Inserts the given track to specified index
|
|
||||||
* @param {Track} track The track to insert
|
|
||||||
* @param {number} [index=0] The index where this track should be
|
|
||||||
*/
|
|
||||||
insert(track: Track, index?: number): void;
|
|
||||||
/**
|
|
||||||
* @typedef {object} PlayerTimestamp
|
|
||||||
* @property {string} current The current progress
|
|
||||||
* @property {string} end The total time
|
|
||||||
* @property {number} progress Progress in %
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Returns player stream timestamp
|
|
||||||
* @returns {PlayerTimestamp}
|
|
||||||
*/
|
|
||||||
getPlayerTimestamp(): {
|
|
||||||
current: string;
|
|
||||||
end: string;
|
|
||||||
progress: number;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Creates progress bar string
|
|
||||||
* @param {PlayerProgressbarOptions} options The progress bar options
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
createProgressBar(options?: PlayerProgressbarOptions): string;
|
|
||||||
/**
|
|
||||||
* Total duration
|
|
||||||
* @type {Number}
|
|
||||||
*/
|
|
||||||
get totalTime(): number;
|
|
||||||
/**
|
|
||||||
* Play stream in a voice/stage channel
|
|
||||||
* @param {Track} [src] The track to play (if empty, uses first track from the queue)
|
|
||||||
* @param {PlayOptions} [options] The options
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
play(src?: Track, options?: PlayOptions): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Private method to handle autoplay
|
|
||||||
* @param {Track} track The source track to find its similar track for autoplay
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private _handleAutoplay;
|
|
||||||
[Symbol.iterator](): Generator<Track, void, undefined>;
|
|
||||||
/**
|
|
||||||
* JSON representation of this queue
|
|
||||||
* @returns {object}
|
|
||||||
*/
|
|
||||||
toJSON(): {
|
|
||||||
id: string;
|
|
||||||
guild: string;
|
|
||||||
voiceChannel: string;
|
|
||||||
options: PlayerOptions;
|
|
||||||
tracks: import("../types/types").TrackJSON[];
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* String representation of this queue
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
toString(): string;
|
|
||||||
}
|
|
||||||
export { Queue };
|
|
761
helpers/Music/dist/Structures/Queue.js
vendored
761
helpers/Music/dist/Structures/Queue.js
vendored
|
@ -1,761 +0,0 @@
|
||||||
"use strict";
|
|
||||||
var _Queue_instances, _Queue_lastVolume, _Queue_destroyed, _Queue_watchDestroyed, _Queue_getBufferingTimeout;
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.Queue = void 0;
|
|
||||||
const tslib_1 = require("tslib");
|
|
||||||
const discord_js_1 = require("discord.js");
|
|
||||||
const Track_1 = tslib_1.__importDefault(require("./Track"));
|
|
||||||
const types_1 = require("../types/types");
|
|
||||||
const voice_1 = require("@discordjs/voice");
|
|
||||||
const play_dl_1 = tslib_1.__importDefault(require("play-dl"));
|
|
||||||
const Util_1 = require("../utils/Util");
|
|
||||||
const AudioFilters_1 = tslib_1.__importDefault(require("../utils/AudioFilters"));
|
|
||||||
const PlayerError_1 = require("./PlayerError");
|
|
||||||
const FFmpegStream_1 = require("../utils/FFmpegStream");
|
|
||||||
class Queue {
|
|
||||||
/**
|
|
||||||
* Queue constructor
|
|
||||||
* @param {Player} player The player that instantiated this queue
|
|
||||||
* @param {Guild} guild The guild that instantiated this queue
|
|
||||||
* @param {PlayerOptions} [options] Player options for the queue
|
|
||||||
*/
|
|
||||||
constructor(player, guild, options = {}) {
|
|
||||||
_Queue_instances.add(this);
|
|
||||||
this.tracks = [];
|
|
||||||
this.previousTracks = [];
|
|
||||||
this.playing = false;
|
|
||||||
this.metadata = null;
|
|
||||||
this.repeatMode = 0;
|
|
||||||
this.id = discord_js_1.SnowflakeUtil.generate().toString();
|
|
||||||
this._streamTime = 0;
|
|
||||||
this._cooldownsTimeout = new discord_js_1.Collection();
|
|
||||||
this._activeFilters = []; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
this._filtersUpdate = false;
|
|
||||||
_Queue_lastVolume.set(this, 0);
|
|
||||||
_Queue_destroyed.set(this, false);
|
|
||||||
this.onBeforeCreateStream = null;
|
|
||||||
/**
|
|
||||||
* The player that instantiated this queue
|
|
||||||
* @type {Player}
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
this.player = player;
|
|
||||||
/**
|
|
||||||
* The guild that instantiated this queue
|
|
||||||
* @type {Guild}
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
this.guild = guild;
|
|
||||||
/**
|
|
||||||
* The player options for this queue
|
|
||||||
* @type {PlayerOptions}
|
|
||||||
*/
|
|
||||||
this.options = {};
|
|
||||||
/**
|
|
||||||
* Queue repeat mode
|
|
||||||
* @type {QueueRepeatMode}
|
|
||||||
* @name Queue#repeatMode
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Queue metadata
|
|
||||||
* @type {any}
|
|
||||||
* @name Queue#metadata
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Previous tracks
|
|
||||||
* @type {Track[]}
|
|
||||||
* @name Queue#previousTracks
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Regular tracks
|
|
||||||
* @type {Track[]}
|
|
||||||
* @name Queue#tracks
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* The connection
|
|
||||||
* @type {StreamDispatcher}
|
|
||||||
* @name Queue#connection
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* The ID of this queue
|
|
||||||
* @type {Snowflake}
|
|
||||||
* @name Queue#id
|
|
||||||
*/
|
|
||||||
Object.assign(this.options, {
|
|
||||||
leaveOnEnd: true,
|
|
||||||
leaveOnStop: true,
|
|
||||||
leaveOnEmpty: true,
|
|
||||||
leaveOnEmptyCooldown: 1000,
|
|
||||||
autoSelfDeaf: true,
|
|
||||||
ytdlOptions: {
|
|
||||||
highWaterMark: 1 << 25
|
|
||||||
},
|
|
||||||
initialVolume: 100,
|
|
||||||
bufferingTimeout: 3000,
|
|
||||||
spotifyBridge: true,
|
|
||||||
disableVolume: false
|
|
||||||
}, options);
|
|
||||||
if ("onBeforeCreateStream" in this.options)
|
|
||||||
this.onBeforeCreateStream = this.options.onBeforeCreateStream;
|
|
||||||
this.player.emit("debug", this, `Queue initialized:\n\n${this.player.scanDeps()}`);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns current track
|
|
||||||
* @type {Track}
|
|
||||||
*/
|
|
||||||
get current() {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
return this.connection.audioResource?.metadata ?? this.tracks[0];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* If this queue is destroyed
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
get destroyed() {
|
|
||||||
return tslib_1.__classPrivateFieldGet(this, _Queue_destroyed, "f");
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns current track
|
|
||||||
* @returns {Track}
|
|
||||||
*/
|
|
||||||
nowPlaying() {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
return this.current;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Connects to a voice channel
|
|
||||||
* @param {GuildChannelResolvable} channel The voice/stage channel
|
|
||||||
* @returns {Promise<Queue>}
|
|
||||||
*/
|
|
||||||
async connect(channel) {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
const _channel = this.guild.channels.resolve(channel);
|
|
||||||
if (![discord_js_1.ChannelType.GuildStageVoice, discord_js_1.ChannelType.GuildVoice].includes(_channel?.type))
|
|
||||||
throw new PlayerError_1.PlayerError(`Channel type must be GuildVoice or GuildStageVoice, got ${_channel?.type}!`, PlayerError_1.ErrorStatusCode.INVALID_ARG_TYPE);
|
|
||||||
const connection = await this.player.voiceUtils.connect(_channel, {
|
|
||||||
deaf: this.options.autoSelfDeaf
|
|
||||||
});
|
|
||||||
this.connection = connection;
|
|
||||||
if (_channel.type === discord_js_1.ChannelType.GuildStageVoice) {
|
|
||||||
await _channel.guild.members.me.voice.setSuppressed(false).catch(async () => {
|
|
||||||
return await _channel.guild.members.me.voice.setRequestToSpeak(true).catch(Util_1.Util.noop);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.connection.on("error", (err) => {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this, false))
|
|
||||||
return;
|
|
||||||
this.player.emit("connectionError", this, err);
|
|
||||||
});
|
|
||||||
this.connection.on("debug", (msg) => {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this, false))
|
|
||||||
return;
|
|
||||||
this.player.emit("debug", this, msg);
|
|
||||||
});
|
|
||||||
this.player.emit("connectionCreate", this, this.connection);
|
|
||||||
this.connection.on("start", (resource) => {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this, false))
|
|
||||||
return;
|
|
||||||
this.playing = true;
|
|
||||||
if (!this._filtersUpdate)
|
|
||||||
this.player.emit("trackStart", this, resource?.metadata ?? this.current);
|
|
||||||
this._filtersUpdate = false;
|
|
||||||
});
|
|
||||||
this.connection.on("finish", async (resource) => {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this, false))
|
|
||||||
return;
|
|
||||||
this.playing = false;
|
|
||||||
if (this._filtersUpdate)
|
|
||||||
return;
|
|
||||||
this._streamTime = 0;
|
|
||||||
if (resource?.metadata)
|
|
||||||
this.previousTracks.push(resource.metadata);
|
|
||||||
this.player.emit("trackEnd", this, resource.metadata);
|
|
||||||
if (!this.tracks.length && this.repeatMode === types_1.QueueRepeatMode.OFF) {
|
|
||||||
if (this.options.leaveOnEnd)
|
|
||||||
this.destroy();
|
|
||||||
this.player.emit("queueEnd", this);
|
|
||||||
}
|
|
||||||
else if (!this.tracks.length && this.repeatMode === types_1.QueueRepeatMode.AUTOPLAY) {
|
|
||||||
this._handleAutoplay(Util_1.Util.last(this.previousTracks));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (this.repeatMode === types_1.QueueRepeatMode.TRACK)
|
|
||||||
return void this.play(Util_1.Util.last(this.previousTracks), { immediate: true });
|
|
||||||
if (this.repeatMode === types_1.QueueRepeatMode.QUEUE)
|
|
||||||
this.tracks.push(Util_1.Util.last(this.previousTracks));
|
|
||||||
const nextTrack = this.tracks.shift();
|
|
||||||
this.play(nextTrack, { immediate: true });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Destroys this queue
|
|
||||||
* @param {boolean} [disconnect=this.options.leaveOnStop] If it should leave on destroy
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
destroy(disconnect = this.options.leaveOnStop) {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
if (this.connection)
|
|
||||||
this.connection.end();
|
|
||||||
if (disconnect)
|
|
||||||
this.connection?.disconnect();
|
|
||||||
this.player.queues.delete(this.guild.id);
|
|
||||||
this.player.voiceUtils.cache.delete(this.guild.id);
|
|
||||||
tslib_1.__classPrivateFieldSet(this, _Queue_destroyed, true, "f");
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Skips current track
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
skip() {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
if (!this.connection)
|
|
||||||
return false;
|
|
||||||
this._filtersUpdate = false;
|
|
||||||
this.connection.end();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Adds single track to the queue
|
|
||||||
* @param {Track} track The track to add
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
addTrack(track) {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
if (!(track instanceof Track_1.default))
|
|
||||||
throw new PlayerError_1.PlayerError("invalid track", PlayerError_1.ErrorStatusCode.INVALID_TRACK);
|
|
||||||
this.tracks.push(track);
|
|
||||||
this.player.emit("trackAdd", this, track);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Adds multiple tracks to the queue
|
|
||||||
* @param {Track[]} tracks Array of tracks to add
|
|
||||||
*/
|
|
||||||
addTracks(tracks) {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
if (!tracks.every((y) => y instanceof Track_1.default))
|
|
||||||
throw new PlayerError_1.PlayerError("invalid track", PlayerError_1.ErrorStatusCode.INVALID_TRACK);
|
|
||||||
this.tracks.push(...tracks);
|
|
||||||
this.player.emit("tracksAdd", this, tracks);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Sets paused state
|
|
||||||
* @param {boolean} paused The paused state
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
setPaused(paused) {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
if (!this.connection)
|
|
||||||
return false;
|
|
||||||
return paused ? this.connection.pause(true) : this.connection.resume();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Sets bitrate
|
|
||||||
* @param {number|auto} bitrate bitrate to set
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
setBitrate(bitrate) {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
if (!this.connection?.audioResource?.encoder)
|
|
||||||
return;
|
|
||||||
if (bitrate === "auto")
|
|
||||||
bitrate = this.connection.channel?.bitrate ?? 64000;
|
|
||||||
this.connection.audioResource.encoder.setBitrate(bitrate);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Sets volume
|
|
||||||
* @param {number} amount The volume amount
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
setVolume(amount) {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
if (!this.connection)
|
|
||||||
return false;
|
|
||||||
tslib_1.__classPrivateFieldSet(this, _Queue_lastVolume, amount, "f");
|
|
||||||
this.options.initialVolume = amount;
|
|
||||||
return this.connection.setVolume(amount);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Sets repeat mode
|
|
||||||
* @param {QueueRepeatMode} mode The repeat mode
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
setRepeatMode(mode) {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
if (![types_1.QueueRepeatMode.OFF, types_1.QueueRepeatMode.QUEUE, types_1.QueueRepeatMode.TRACK, types_1.QueueRepeatMode.AUTOPLAY].includes(mode))
|
|
||||||
throw new PlayerError_1.PlayerError(`Unknown repeat mode "${mode}"!`, PlayerError_1.ErrorStatusCode.UNKNOWN_REPEAT_MODE);
|
|
||||||
if (mode === this.repeatMode)
|
|
||||||
return false;
|
|
||||||
this.repeatMode = mode;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The current volume amount
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
get volume() {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
if (!this.connection)
|
|
||||||
return 100;
|
|
||||||
return this.connection.volume;
|
|
||||||
}
|
|
||||||
set volume(amount) {
|
|
||||||
this.setVolume(amount);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The stream time of this queue
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
get streamTime() {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
if (!this.connection)
|
|
||||||
return 0;
|
|
||||||
const playbackTime = this._streamTime + this.connection.streamTime;
|
|
||||||
const NC = this._activeFilters.includes("nightcore") ? 1.25 : null;
|
|
||||||
const VW = this._activeFilters.includes("vaporwave") ? 0.8 : null;
|
|
||||||
if (NC && VW)
|
|
||||||
return playbackTime * (NC + VW);
|
|
||||||
return NC ? playbackTime * NC : VW ? playbackTime * VW : playbackTime;
|
|
||||||
}
|
|
||||||
set streamTime(time) {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
this.seek(time);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns enabled filters
|
|
||||||
* @returns {AudioFilters}
|
|
||||||
*/
|
|
||||||
getFiltersEnabled() {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
return AudioFilters_1.default.names.filter((x) => this._activeFilters.includes(x));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns disabled filters
|
|
||||||
* @returns {AudioFilters}
|
|
||||||
*/
|
|
||||||
getFiltersDisabled() {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
return AudioFilters_1.default.names.filter((x) => !this._activeFilters.includes(x));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Sets filters
|
|
||||||
* @param {QueueFilters} filters Queue filters
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async setFilters(filters) {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
if (!filters || !Object.keys(filters).length) {
|
|
||||||
// reset filters
|
|
||||||
const streamTime = this.streamTime;
|
|
||||||
this._activeFilters = [];
|
|
||||||
return await this.play(this.current, {
|
|
||||||
immediate: true,
|
|
||||||
filtersUpdate: true,
|
|
||||||
seek: streamTime,
|
|
||||||
encoderArgs: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const _filters = []; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
for (const filter in filters) {
|
|
||||||
if (filters[filter] === true)
|
|
||||||
_filters.push(filter);
|
|
||||||
}
|
|
||||||
if (this._activeFilters.join("") === _filters.join(""))
|
|
||||||
return;
|
|
||||||
const newFilters = AudioFilters_1.default.create(_filters).trim();
|
|
||||||
const streamTime = this.streamTime;
|
|
||||||
this._activeFilters = _filters;
|
|
||||||
return await this.play(this.current, {
|
|
||||||
immediate: true,
|
|
||||||
filtersUpdate: true,
|
|
||||||
seek: streamTime,
|
|
||||||
encoderArgs: !_filters.length ? undefined : ["-af", newFilters]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Seeks to the given time
|
|
||||||
* @param {number} position The position
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
async seek(position) {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
if (!this.playing || !this.current)
|
|
||||||
return false;
|
|
||||||
if (position < 1)
|
|
||||||
position = 0;
|
|
||||||
if (position >= this.current.durationMS)
|
|
||||||
return this.skip();
|
|
||||||
await this.play(this.current, {
|
|
||||||
immediate: true,
|
|
||||||
filtersUpdate: true,
|
|
||||||
seek: position
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Plays previous track
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async back() {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
const prev = this.previousTracks[this.previousTracks.length - 2]; // because last item is the current track
|
|
||||||
if (!prev)
|
|
||||||
throw new PlayerError_1.PlayerError("Could not find previous track", PlayerError_1.ErrorStatusCode.TRACK_NOT_FOUND);
|
|
||||||
return await this.play(prev, { immediate: true });
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Clear this queue
|
|
||||||
*/
|
|
||||||
clear() {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
this.tracks = [];
|
|
||||||
this.previousTracks = [];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Stops the player
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
stop() {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
return this.destroy();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Shuffles this queue
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
shuffle() {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
if (!this.tracks.length || this.tracks.length < 2)
|
|
||||||
return false;
|
|
||||||
for (let i = this.tracks.length - 1; i > 0; i--) {
|
|
||||||
const j = Math.floor(Math.random() * (i + 1));
|
|
||||||
[this.tracks[i], this.tracks[j]] = [this.tracks[j], this.tracks[i]];
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Removes a track from the queue
|
|
||||||
* @param {Track|string|number} track The track to remove
|
|
||||||
* @returns {Track}
|
|
||||||
*/
|
|
||||||
remove(track) {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
let trackFound = null;
|
|
||||||
if (typeof track === "number") {
|
|
||||||
trackFound = this.tracks[track];
|
|
||||||
if (trackFound) {
|
|
||||||
this.tracks = this.tracks.filter((t) => t.id !== trackFound.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
trackFound = this.tracks.find((s) => s.id === (track instanceof Track_1.default ? track.id : track));
|
|
||||||
if (trackFound) {
|
|
||||||
this.tracks = this.tracks.filter((s) => s.id !== trackFound.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return trackFound;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns the index of the specified track. If found, returns the track index else returns -1.
|
|
||||||
* @param {number|Track|string} track The track
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
getTrackPosition(track) {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
if (typeof track === "number")
|
|
||||||
return this.tracks[track] != null ? track : -1;
|
|
||||||
return this.tracks.findIndex((pred) => pred.id === (track instanceof Track_1.default ? track.id : track));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Jumps to particular track
|
|
||||||
* @param {Track|number} track The track
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
jump(track) {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
const foundTrack = this.remove(track);
|
|
||||||
if (!foundTrack)
|
|
||||||
throw new PlayerError_1.PlayerError("Track not found", PlayerError_1.ErrorStatusCode.TRACK_NOT_FOUND);
|
|
||||||
this.tracks.splice(0, 0, foundTrack);
|
|
||||||
return void this.skip();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Jumps to particular track, removing other tracks on the way
|
|
||||||
* @param {Track|number} track The track
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
skipTo(track) {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
const trackIndex = this.getTrackPosition(track);
|
|
||||||
const removedTrack = this.remove(track);
|
|
||||||
if (!removedTrack)
|
|
||||||
throw new PlayerError_1.PlayerError("Track not found", PlayerError_1.ErrorStatusCode.TRACK_NOT_FOUND);
|
|
||||||
this.tracks.splice(0, trackIndex, removedTrack);
|
|
||||||
return void this.skip();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Inserts the given track to specified index
|
|
||||||
* @param {Track} track The track to insert
|
|
||||||
* @param {number} [index=0] The index where this track should be
|
|
||||||
*/
|
|
||||||
insert(track, index = 0) {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
if (!track || !(track instanceof Track_1.default))
|
|
||||||
throw new PlayerError_1.PlayerError("track must be the instance of Track", PlayerError_1.ErrorStatusCode.INVALID_TRACK);
|
|
||||||
if (typeof index !== "number" || index < 0 || !Number.isFinite(index))
|
|
||||||
throw new PlayerError_1.PlayerError(`Invalid index "${index}"`, PlayerError_1.ErrorStatusCode.INVALID_ARG_TYPE);
|
|
||||||
this.tracks.splice(index, 0, track);
|
|
||||||
this.player.emit("trackAdd", this, track);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @typedef {object} PlayerTimestamp
|
|
||||||
* @property {string} current The current progress
|
|
||||||
* @property {string} end The total time
|
|
||||||
* @property {number} progress Progress in %
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Returns player stream timestamp
|
|
||||||
* @returns {PlayerTimestamp}
|
|
||||||
*/
|
|
||||||
getPlayerTimestamp() {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
const currentStreamTime = this.streamTime;
|
|
||||||
const totalTime = this.current.durationMS;
|
|
||||||
const currentTimecode = Util_1.Util.buildTimeCode(Util_1.Util.parseMS(currentStreamTime));
|
|
||||||
const endTimecode = Util_1.Util.buildTimeCode(Util_1.Util.parseMS(totalTime));
|
|
||||||
return {
|
|
||||||
current: currentTimecode,
|
|
||||||
end: endTimecode,
|
|
||||||
progress: Math.round((currentStreamTime / totalTime) * 100)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Creates progress bar string
|
|
||||||
* @param {PlayerProgressbarOptions} options The progress bar options
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
createProgressBar(options = { timecodes: true }) {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
const length = typeof options.length === "number" ? (options.length <= 0 || options.length === Infinity ? 15 : options.length) : 15;
|
|
||||||
const index = Math.round((this.streamTime / this.current.durationMS) * length);
|
|
||||||
const indicator = typeof options.indicator === "string" && options.indicator.length > 0 ? options.indicator : "🔘";
|
|
||||||
const line = typeof options.line === "string" && options.line.length > 0 ? options.line : "▬";
|
|
||||||
if (index >= 1 && index <= length) {
|
|
||||||
const bar = line.repeat(length - 1).split("");
|
|
||||||
bar.splice(index, 0, indicator);
|
|
||||||
if (options.timecodes) {
|
|
||||||
const timestamp = this.getPlayerTimestamp();
|
|
||||||
return `${timestamp.current} ┃ ${bar.join("")} ┃ ${timestamp.end}`;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return `${bar.join("")}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (options.timecodes) {
|
|
||||||
const timestamp = this.getPlayerTimestamp();
|
|
||||||
return `${timestamp.current} ┃ ${indicator}${line.repeat(length - 1)} ┃ ${timestamp.end}`;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return `${indicator}${line.repeat(length - 1)}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Total duration
|
|
||||||
* @type {Number}
|
|
||||||
*/
|
|
||||||
get totalTime() {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
return this.tracks.length > 0 ? this.tracks.map((t) => t.durationMS).reduce((p, c) => p + c) : 0;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Play stream in a voice/stage channel
|
|
||||||
* @param {Track} [src] The track to play (if empty, uses first track from the queue)
|
|
||||||
* @param {PlayOptions} [options] The options
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async play(src, options = {}) {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this, false))
|
|
||||||
return;
|
|
||||||
if (!this.connection || !this.connection.voiceConnection)
|
|
||||||
throw new PlayerError_1.PlayerError("Voice connection is not available, use <Queue>.connect()!", PlayerError_1.ErrorStatusCode.NO_CONNECTION);
|
|
||||||
if (src && (this.playing || this.tracks.length) && !options.immediate)
|
|
||||||
return this.addTrack(src);
|
|
||||||
const track = options.filtersUpdate && !options.immediate ? src || this.current : src ?? this.tracks.shift();
|
|
||||||
if (!track)
|
|
||||||
return;
|
|
||||||
this.player.emit("debug", this, "Received play request");
|
|
||||||
if (!options.filtersUpdate) {
|
|
||||||
this.previousTracks = this.previousTracks.filter((x) => x.id !== track.id);
|
|
||||||
this.previousTracks.push(track);
|
|
||||||
}
|
|
||||||
let stream = null;
|
|
||||||
const hasCustomDownloader = typeof this.onBeforeCreateStream === "function";
|
|
||||||
if (["youtube", "spotify"].includes(track.raw.source)) {
|
|
||||||
let spotifyResolved = false;
|
|
||||||
if (this.options.spotifyBridge && track.raw.source === "spotify" && !track.raw.engine) {
|
|
||||||
track.raw.engine = await play_dl_1.default.search(`${track.author} ${track.title}`, { source: { youtube: "video" } })
|
|
||||||
.then(res => res[0].url)
|
|
||||||
.catch(() => null);
|
|
||||||
spotifyResolved = true;
|
|
||||||
}
|
|
||||||
const url = track.raw.source === "spotify" ? track.raw.engine : track.url;
|
|
||||||
if (!url)
|
|
||||||
return void this.play(this.tracks.shift(), { immediate: true });
|
|
||||||
if (hasCustomDownloader) {
|
|
||||||
stream = (await this.onBeforeCreateStream(track, spotifyResolved ? "youtube" : track.raw.source, this)) || null;
|
|
||||||
}
|
|
||||||
if (!stream) {
|
|
||||||
stream = (await play_dl_1.default.stream(url, { discordPlayerCompatibility: true })).stream;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const arbitraryStream = (hasCustomDownloader && (await this.onBeforeCreateStream(track, track.raw.source || track.raw.engine, this))) || null;
|
|
||||||
stream =
|
|
||||||
arbitraryStream || (track.raw.source === "soundcloud" && typeof track.raw.engine?.downloadProgressive === "function")
|
|
||||||
? await track.raw.engine.downloadProgressive()
|
|
||||||
: typeof track.raw.engine === "function"
|
|
||||||
? await track.raw.engine()
|
|
||||||
: track.raw.engine;
|
|
||||||
}
|
|
||||||
const ffmpegStream = (0, FFmpegStream_1.createFFmpegStream)(stream, {
|
|
||||||
encoderArgs: options.encoderArgs || [],
|
|
||||||
seek: options.seek ? options.seek / 1000 : 0,
|
|
||||||
fmt: "s16le"
|
|
||||||
}).on("error", (err) => {
|
|
||||||
if (!`${err}`.toLowerCase().includes("premature close"))
|
|
||||||
this.player.emit("error", this, err);
|
|
||||||
});
|
|
||||||
const resource = this.connection.createStream(ffmpegStream, {
|
|
||||||
type: voice_1.StreamType.Raw,
|
|
||||||
data: track,
|
|
||||||
disableVolume: Boolean(this.options.disableVolume)
|
|
||||||
});
|
|
||||||
if (options.seek)
|
|
||||||
this._streamTime = options.seek;
|
|
||||||
this._filtersUpdate = options.filtersUpdate;
|
|
||||||
const volumeTransformer = resource.volume;
|
|
||||||
if (volumeTransformer && typeof this.options.initialVolume === "number")
|
|
||||||
Reflect.set(volumeTransformer, "volume", Math.pow(this.options.initialVolume / 100, 1.660964));
|
|
||||||
if (volumeTransformer?.hasSmoothness && typeof this.options.volumeSmoothness === "number") {
|
|
||||||
if (typeof volumeTransformer.setSmoothness === "function")
|
|
||||||
volumeTransformer.setSmoothness(this.options.volumeSmoothness || 0);
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
this.connection.playStream(resource);
|
|
||||||
}, tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_getBufferingTimeout).call(this)).unref();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Private method to handle autoplay
|
|
||||||
* @param {Track} track The source track to find its similar track for autoplay
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
async _handleAutoplay(track) {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
if (!track || ![track.source, track.raw?.source].includes("youtube")) {
|
|
||||||
if (this.options.leaveOnEnd)
|
|
||||||
this.destroy();
|
|
||||||
return void this.player.emit("queueEnd", this);
|
|
||||||
}
|
|
||||||
const info = await play_dl_1.default.video_info(track.url)
|
|
||||||
.catch(Util_1.Util.noop);
|
|
||||||
if (!info) {
|
|
||||||
if (this.options.leaveOnEnd)
|
|
||||||
this.destroy();
|
|
||||||
return void this.player.emit("queueEnd", this);
|
|
||||||
}
|
|
||||||
const randomRelated = await play_dl_1.default.video_info(info.related_videos[0]);
|
|
||||||
const nextTrack = new Track_1.default(this.player, {
|
|
||||||
title: randomRelated.video_details.title,
|
|
||||||
url: randomRelated.video_details.url,
|
|
||||||
duration: randomRelated.video_details.durationRaw ? Util_1.Util.buildTimeCode(Util_1.Util.parseMS(randomRelated.video_details.durationInSec * 1000)) : "0:00",
|
|
||||||
description: "",
|
|
||||||
thumbnail: Util_1.Util.last(randomRelated.video_details.thumbnails).url,
|
|
||||||
views: randomRelated.video_details.views,
|
|
||||||
author: randomRelated.video_details.channel.name,
|
|
||||||
requestedBy: track.requestedBy,
|
|
||||||
source: "youtube"
|
|
||||||
});
|
|
||||||
this.play(nextTrack, { immediate: true });
|
|
||||||
}
|
|
||||||
*[(_Queue_lastVolume = new WeakMap(), _Queue_destroyed = new WeakMap(), _Queue_instances = new WeakSet(), Symbol.iterator)]() {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
yield* this.tracks;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* JSON representation of this queue
|
|
||||||
* @returns {object}
|
|
||||||
*/
|
|
||||||
toJSON() {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
return {
|
|
||||||
id: this.id,
|
|
||||||
guild: this.guild.id,
|
|
||||||
voiceChannel: this.connection?.channel?.id,
|
|
||||||
options: this.options,
|
|
||||||
tracks: this.tracks.map((m) => m.toJSON())
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* String representation of this queue
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
toString() {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
|
||||||
return;
|
|
||||||
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")}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.Queue = Queue;
|
|
||||||
_Queue_watchDestroyed = function _Queue_watchDestroyed(emit = true) {
|
|
||||||
if (tslib_1.__classPrivateFieldGet(this, _Queue_destroyed, "f")) {
|
|
||||||
if (emit)
|
|
||||||
this.player.emit("error", this, new PlayerError_1.PlayerError("Cannot use destroyed queue", PlayerError_1.ErrorStatusCode.DESTROYED_QUEUE));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}, _Queue_getBufferingTimeout = function _Queue_getBufferingTimeout() {
|
|
||||||
const timeout = this.options.bufferingTimeout;
|
|
||||||
if (isNaN(timeout) || timeout < 0 || !Number.isFinite(timeout))
|
|
||||||
return 1000;
|
|
||||||
return timeout;
|
|
||||||
};
|
|
53
helpers/Music/dist/Structures/Track.d.ts
vendored
53
helpers/Music/dist/Structures/Track.d.ts
vendored
|
@ -1,53 +0,0 @@
|
||||||
import { User } from "discord.js";
|
|
||||||
import { Player } from "../Player";
|
|
||||||
import { RawTrackData, TrackJSON } from "../types/types";
|
|
||||||
import { Playlist } from "./Playlist";
|
|
||||||
import { Queue } from "./Queue";
|
|
||||||
declare class Track {
|
|
||||||
player: Player;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
author: string;
|
|
||||||
url: string;
|
|
||||||
thumbnail: string;
|
|
||||||
duration: string;
|
|
||||||
views: number;
|
|
||||||
requestedBy: User;
|
|
||||||
playlist?: Playlist;
|
|
||||||
readonly raw: RawTrackData;
|
|
||||||
readonly id: string;
|
|
||||||
/**
|
|
||||||
* Track constructor
|
|
||||||
* @param {Player} player The player that instantiated this Track
|
|
||||||
* @param {RawTrackData} data Track data
|
|
||||||
*/
|
|
||||||
constructor(player: Player, data: RawTrackData);
|
|
||||||
private _patch;
|
|
||||||
/**
|
|
||||||
* The queue in which this track is located
|
|
||||||
* @type {Queue}
|
|
||||||
*/
|
|
||||||
get queue(): Queue;
|
|
||||||
/**
|
|
||||||
* The track duration in millisecond
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
get durationMS(): number;
|
|
||||||
/**
|
|
||||||
* Returns source of this track
|
|
||||||
* @type {TrackSource}
|
|
||||||
*/
|
|
||||||
get source(): import("../types/types").TrackSource;
|
|
||||||
/**
|
|
||||||
* String representation of this track
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
toString(): string;
|
|
||||||
/**
|
|
||||||
* Raw JSON representation of this track
|
|
||||||
* @returns {TrackJSON}
|
|
||||||
*/
|
|
||||||
toJSON(hidePlaylist?: boolean): TrackJSON;
|
|
||||||
}
|
|
||||||
export default Track;
|
|
||||||
export { Track };
|
|
156
helpers/Music/dist/Structures/Track.js
vendored
156
helpers/Music/dist/Structures/Track.js
vendored
|
@ -1,156 +0,0 @@
|
||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.Track = void 0;
|
|
||||||
const discord_js_1 = require("discord.js");
|
|
||||||
class Track {
|
|
||||||
/**
|
|
||||||
* Track constructor
|
|
||||||
* @param {Player} player The player that instantiated this Track
|
|
||||||
* @param {RawTrackData} data Track data
|
|
||||||
*/
|
|
||||||
constructor(player, data) {
|
|
||||||
this.raw = {};
|
|
||||||
this.id = discord_js_1.SnowflakeUtil.generate().toString();
|
|
||||||
/**
|
|
||||||
* The player that instantiated this Track
|
|
||||||
* @name Track#player
|
|
||||||
* @type {Player}
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
Object.defineProperty(this, "player", { value: player, enumerable: false });
|
|
||||||
/**
|
|
||||||
* Title of this track
|
|
||||||
* @name Track#title
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Description of this track
|
|
||||||
* @name Track#description
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Author of this track
|
|
||||||
* @name Track#author
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* URL of this track
|
|
||||||
* @name Track#url
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Thumbnail of this track
|
|
||||||
* @name Track#thumbnail
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Duration of this track
|
|
||||||
* @name Track#duration
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Views count of this track
|
|
||||||
* @name Track#views
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Person who requested this track
|
|
||||||
* @name Track#requestedBy
|
|
||||||
* @type {User}
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* If this track belongs to playlist
|
|
||||||
* @name Track#fromPlaylist
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Raw track data
|
|
||||||
* @name Track#raw
|
|
||||||
* @type {RawTrackData}
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* The track id
|
|
||||||
* @name Track#id
|
|
||||||
* @type {Snowflake}
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* The playlist which track belongs
|
|
||||||
* @name Track#playlist
|
|
||||||
* @type {Playlist}
|
|
||||||
*/
|
|
||||||
void this._patch(data);
|
|
||||||
}
|
|
||||||
_patch(data) {
|
|
||||||
this.title = (0, discord_js_1.escapeMarkdown)(data.title ?? "");
|
|
||||||
this.description = data.description ?? "";
|
|
||||||
this.author = data.author ?? "";
|
|
||||||
this.url = data.url ?? "";
|
|
||||||
this.thumbnail = data.thumbnail ?? "";
|
|
||||||
this.duration = data.duration ?? "";
|
|
||||||
this.views = data.views ?? 0;
|
|
||||||
this.requestedBy = data.requestedBy;
|
|
||||||
this.playlist = data.playlist;
|
|
||||||
// raw
|
|
||||||
Object.defineProperty(this, "raw", { value: Object.assign({}, { source: data.raw?.source ?? data.source }, data.raw ?? data), enumerable: false });
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The queue in which this track is located
|
|
||||||
* @type {Queue}
|
|
||||||
*/
|
|
||||||
get queue() {
|
|
||||||
return this.player.queues.find((q) => q.tracks.some((ab) => ab.id === this.id));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The track duration in millisecond
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
get durationMS() {
|
|
||||||
const times = (n, t) => {
|
|
||||||
let tn = 1;
|
|
||||||
for (let i = 0; i < t; i++)
|
|
||||||
tn *= n;
|
|
||||||
return t <= 0 ? 1000 : tn * 1000;
|
|
||||||
};
|
|
||||||
return this.duration
|
|
||||||
.split(":")
|
|
||||||
.reverse()
|
|
||||||
.map((m, i) => parseInt(m) * times(60, i))
|
|
||||||
.reduce((a, c) => a + c, 0);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns source of this track
|
|
||||||
* @type {TrackSource}
|
|
||||||
*/
|
|
||||||
get source() {
|
|
||||||
return this.raw.source ?? "arbitrary";
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* String representation of this track
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
toString() {
|
|
||||||
return `${this.title} by ${this.author}`;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Raw JSON representation of this track
|
|
||||||
* @returns {TrackJSON}
|
|
||||||
*/
|
|
||||||
toJSON(hidePlaylist) {
|
|
||||||
return {
|
|
||||||
id: this.id,
|
|
||||||
title: this.title,
|
|
||||||
description: this.description,
|
|
||||||
author: this.author,
|
|
||||||
url: this.url,
|
|
||||||
thumbnail: this.thumbnail,
|
|
||||||
duration: this.duration,
|
|
||||||
durationMS: this.durationMS,
|
|
||||||
views: this.views,
|
|
||||||
requestedBy: this.requestedBy?.id,
|
|
||||||
playlist: hidePlaylist ? null : this.playlist?.toJSON() ?? null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.Track = Track;
|
|
||||||
exports.default = Track;
|
|
|
@ -1,88 +0,0 @@
|
||||||
/// <reference types="node" />
|
|
||||||
import { AudioPlayer, AudioPlayerError, AudioPlayerStatus, AudioResource, StreamType, VoiceConnection } from "@discordjs/voice";
|
|
||||||
import { StageChannel, VoiceChannel } from "discord.js";
|
|
||||||
import { Duplex, Readable } from "stream";
|
|
||||||
import { TypedEmitter as EventEmitter } from "tiny-typed-emitter";
|
|
||||||
import Track from "../Structures/Track";
|
|
||||||
export interface VoiceEvents {
|
|
||||||
error: (error: AudioPlayerError) => any;
|
|
||||||
debug: (message: string) => any;
|
|
||||||
start: (resource: AudioResource<Track>) => any;
|
|
||||||
finish: (resource: AudioResource<Track>) => any;
|
|
||||||
}
|
|
||||||
declare class StreamDispatcher extends EventEmitter<VoiceEvents> {
|
|
||||||
readonly connectionTimeout: number;
|
|
||||||
readonly voiceConnection: VoiceConnection;
|
|
||||||
readonly audioPlayer: AudioPlayer;
|
|
||||||
channel: VoiceChannel | StageChannel;
|
|
||||||
audioResource?: AudioResource<Track>;
|
|
||||||
private readyLock;
|
|
||||||
paused: boolean;
|
|
||||||
/**
|
|
||||||
* Creates new connection object
|
|
||||||
* @param {VoiceConnection} connection The connection
|
|
||||||
* @param {VoiceChannel|StageChannel} channel The connected channel
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
constructor(connection: VoiceConnection, channel: VoiceChannel | StageChannel, connectionTimeout?: number);
|
|
||||||
/**
|
|
||||||
* Creates stream
|
|
||||||
* @param {Readable|Duplex|string} src The stream source
|
|
||||||
* @param {object} [ops] Options
|
|
||||||
* @returns {AudioResource}
|
|
||||||
*/
|
|
||||||
createStream(src: Readable | Duplex | string, ops?: {
|
|
||||||
type?: StreamType;
|
|
||||||
data?: any;
|
|
||||||
disableVolume?: boolean;
|
|
||||||
}): AudioResource<Track>;
|
|
||||||
/**
|
|
||||||
* The player status
|
|
||||||
* @type {AudioPlayerStatus}
|
|
||||||
*/
|
|
||||||
get status(): AudioPlayerStatus;
|
|
||||||
/**
|
|
||||||
* Disconnects from voice
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
disconnect(): void;
|
|
||||||
/**
|
|
||||||
* Stops the player
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
end(): void;
|
|
||||||
/**
|
|
||||||
* Pauses the stream playback
|
|
||||||
* @param {boolean} [interpolateSilence=false] If true, the player will play 5 packets of silence after pausing to prevent audio glitches.
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
pause(interpolateSilence?: boolean): boolean;
|
|
||||||
/**
|
|
||||||
* Resumes the stream playback
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
resume(): boolean;
|
|
||||||
/**
|
|
||||||
* Play stream
|
|
||||||
* @param {AudioResource<Track>} [resource=this.audioResource] The audio resource to play
|
|
||||||
* @returns {Promise<StreamDispatcher>}
|
|
||||||
*/
|
|
||||||
playStream(resource?: AudioResource<Track>): Promise<this>;
|
|
||||||
/**
|
|
||||||
* Sets playback volume
|
|
||||||
* @param {number} value The volume amount
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
setVolume(value: number): boolean;
|
|
||||||
/**
|
|
||||||
* The current volume
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
get volume(): number;
|
|
||||||
/**
|
|
||||||
* The playback time
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
get streamTime(): number;
|
|
||||||
}
|
|
||||||
export { StreamDispatcher as StreamDispatcher };
|
|
|
@ -1,225 +0,0 @@
|
||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.StreamDispatcher = void 0;
|
|
||||||
const voice_1 = require("@discordjs/voice");
|
|
||||||
const tiny_typed_emitter_1 = require("tiny-typed-emitter");
|
|
||||||
const Util_1 = require("../utils/Util");
|
|
||||||
const PlayerError_1 = require("../Structures/PlayerError");
|
|
||||||
class StreamDispatcher extends tiny_typed_emitter_1.TypedEmitter {
|
|
||||||
/**
|
|
||||||
* Creates new connection object
|
|
||||||
* @param {VoiceConnection} connection The connection
|
|
||||||
* @param {VoiceChannel|StageChannel} channel The connected channel
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
constructor(connection, channel, connectionTimeout = 20000) {
|
|
||||||
super();
|
|
||||||
this.connectionTimeout = connectionTimeout;
|
|
||||||
this.readyLock = false;
|
|
||||||
/**
|
|
||||||
* The voice connection
|
|
||||||
* @type {VoiceConnection}
|
|
||||||
*/
|
|
||||||
this.voiceConnection = connection;
|
|
||||||
/**
|
|
||||||
* The audio player
|
|
||||||
* @type {AudioPlayer}
|
|
||||||
*/
|
|
||||||
this.audioPlayer = (0, voice_1.createAudioPlayer)();
|
|
||||||
/**
|
|
||||||
* The voice channel
|
|
||||||
* @type {VoiceChannel|StageChannel}
|
|
||||||
*/
|
|
||||||
this.channel = channel;
|
|
||||||
/**
|
|
||||||
* The paused state
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
this.paused = false;
|
|
||||||
this.voiceConnection.on("stateChange", async (_, newState) => {
|
|
||||||
if (newState.status === voice_1.VoiceConnectionStatus.Disconnected) {
|
|
||||||
if (newState.reason === voice_1.VoiceConnectionDisconnectReason.WebSocketClose && newState.closeCode === 4014) {
|
|
||||||
try {
|
|
||||||
await (0, voice_1.entersState)(this.voiceConnection, voice_1.VoiceConnectionStatus.Connecting, this.connectionTimeout);
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
try {
|
|
||||||
this.voiceConnection.destroy();
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
this.emit("error", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (this.voiceConnection.rejoinAttempts < 5) {
|
|
||||||
await Util_1.Util.wait((this.voiceConnection.rejoinAttempts + 1) * 5000);
|
|
||||||
this.voiceConnection.rejoin();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
try {
|
|
||||||
this.voiceConnection.destroy();
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
this.emit("error", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (newState.status === voice_1.VoiceConnectionStatus.Destroyed) {
|
|
||||||
this.end();
|
|
||||||
}
|
|
||||||
else if (!this.readyLock && (newState.status === voice_1.VoiceConnectionStatus.Connecting || newState.status === voice_1.VoiceConnectionStatus.Signalling)) {
|
|
||||||
this.readyLock = true;
|
|
||||||
try {
|
|
||||||
await (0, voice_1.entersState)(this.voiceConnection, voice_1.VoiceConnectionStatus.Ready, this.connectionTimeout);
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
if (this.voiceConnection.state.status !== voice_1.VoiceConnectionStatus.Destroyed) {
|
|
||||||
try {
|
|
||||||
this.voiceConnection.destroy();
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
this.emit("error", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
this.readyLock = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.audioPlayer.on("stateChange", (oldState, newState) => {
|
|
||||||
if (newState.status === voice_1.AudioPlayerStatus.Playing) {
|
|
||||||
if (!this.paused)
|
|
||||||
return void this.emit("start", this.audioResource);
|
|
||||||
}
|
|
||||||
else if (newState.status === voice_1.AudioPlayerStatus.Idle && oldState.status !== voice_1.AudioPlayerStatus.Idle) {
|
|
||||||
if (!this.paused) {
|
|
||||||
void this.emit("finish", this.audioResource);
|
|
||||||
this.audioResource = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.audioPlayer.on("debug", (m) => void this.emit("debug", m));
|
|
||||||
this.audioPlayer.on("error", (error) => void this.emit("error", error));
|
|
||||||
this.voiceConnection.subscribe(this.audioPlayer);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Creates stream
|
|
||||||
* @param {Readable|Duplex|string} src The stream source
|
|
||||||
* @param {object} [ops] Options
|
|
||||||
* @returns {AudioResource}
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
createStream(src, ops) {
|
|
||||||
this.audioResource = (0, voice_1.createAudioResource)(src, {
|
|
||||||
inputType: ops?.type ?? voice_1.StreamType.Arbitrary,
|
|
||||||
metadata: ops?.data,
|
|
||||||
// eslint-disable-next-line no-extra-boolean-cast
|
|
||||||
inlineVolume: !Boolean(ops?.disableVolume)
|
|
||||||
});
|
|
||||||
return this.audioResource;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The player status
|
|
||||||
* @type {AudioPlayerStatus}
|
|
||||||
*/
|
|
||||||
get status() {
|
|
||||||
return this.audioPlayer.state.status;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Disconnects from voice
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
disconnect() {
|
|
||||||
try {
|
|
||||||
this.audioPlayer.stop(true);
|
|
||||||
this.voiceConnection.destroy();
|
|
||||||
}
|
|
||||||
catch { } // eslint-disable-line no-empty
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Stops the player
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
end() {
|
|
||||||
this.audioPlayer.stop();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Pauses the stream playback
|
|
||||||
* @param {boolean} [interpolateSilence=false] If true, the player will play 5 packets of silence after pausing to prevent audio glitches.
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
pause(interpolateSilence) {
|
|
||||||
const success = this.audioPlayer.pause(interpolateSilence);
|
|
||||||
this.paused = success;
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Resumes the stream playback
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
resume() {
|
|
||||||
const success = this.audioPlayer.unpause();
|
|
||||||
this.paused = !success;
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Play stream
|
|
||||||
* @param {AudioResource<Track>} [resource=this.audioResource] The audio resource to play
|
|
||||||
* @returns {Promise<StreamDispatcher>}
|
|
||||||
*/
|
|
||||||
async playStream(resource = this.audioResource) {
|
|
||||||
if (!resource)
|
|
||||||
throw new PlayerError_1.PlayerError("Audio resource is not available!", PlayerError_1.ErrorStatusCode.NO_AUDIO_RESOURCE);
|
|
||||||
if (resource.ended)
|
|
||||||
return void this.emit("error", new PlayerError_1.PlayerError("Cannot play a resource that has already ended."));
|
|
||||||
if (!this.audioResource)
|
|
||||||
this.audioResource = resource;
|
|
||||||
if (this.voiceConnection.state.status !== voice_1.VoiceConnectionStatus.Ready) {
|
|
||||||
try {
|
|
||||||
await (0, voice_1.entersState)(this.voiceConnection, voice_1.VoiceConnectionStatus.Ready, this.connectionTimeout);
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
return void this.emit("error", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
this.audioPlayer.play(resource);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
this.emit("error", e);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Sets playback volume
|
|
||||||
* @param {number} value The volume amount
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
setVolume(value) {
|
|
||||||
if (!this.audioResource?.volume || isNaN(value) || value < 0 || value > Infinity)
|
|
||||||
return false;
|
|
||||||
this.audioResource.volume.setVolumeLogarithmic(value / 100);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The current volume
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
get volume() {
|
|
||||||
if (!this.audioResource?.volume)
|
|
||||||
return 100;
|
|
||||||
const currentVol = this.audioResource.volume.volume;
|
|
||||||
return Math.round(Math.pow(currentVol, 1 / 1.660964) * 100);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The playback time
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
get streamTime() {
|
|
||||||
if (!this.audioResource)
|
|
||||||
return 0;
|
|
||||||
return this.audioResource.playbackDuration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.StreamDispatcher = StreamDispatcher;
|
|
|
@ -1,44 +0,0 @@
|
||||||
import { VoiceChannel, StageChannel, Collection, Snowflake } from "discord.js";
|
|
||||||
import { VoiceConnection } from "@discordjs/voice";
|
|
||||||
import { StreamDispatcher } from "./StreamDispatcher";
|
|
||||||
declare class VoiceUtils {
|
|
||||||
cache: Collection<Snowflake, StreamDispatcher>;
|
|
||||||
/**
|
|
||||||
* The voice utils
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
constructor();
|
|
||||||
/**
|
|
||||||
* Joins a voice channel, creating basic stream dispatch manager
|
|
||||||
* @param {StageChannel|VoiceChannel} channel The voice channel
|
|
||||||
* @param {object} [options] Join options
|
|
||||||
* @returns {Promise<StreamDispatcher>}
|
|
||||||
*/
|
|
||||||
connect(channel: VoiceChannel | StageChannel, options?: {
|
|
||||||
deaf?: boolean;
|
|
||||||
maxTime?: number;
|
|
||||||
}): Promise<StreamDispatcher>;
|
|
||||||
/**
|
|
||||||
* Joins a voice channel
|
|
||||||
* @param {StageChannel|VoiceChannel} [channel] The voice/stage channel to join
|
|
||||||
* @param {object} [options] Join options
|
|
||||||
* @returns {VoiceConnection}
|
|
||||||
*/
|
|
||||||
join(channel: VoiceChannel | StageChannel, options?: {
|
|
||||||
deaf?: boolean;
|
|
||||||
maxTime?: number;
|
|
||||||
}): Promise<VoiceConnection>;
|
|
||||||
/**
|
|
||||||
* Disconnects voice connection
|
|
||||||
* @param {VoiceConnection} connection The voice connection
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
disconnect(connection: VoiceConnection | StreamDispatcher): void;
|
|
||||||
/**
|
|
||||||
* Returns Discord Player voice connection
|
|
||||||
* @param {Snowflake} guild The guild id
|
|
||||||
* @returns {StreamDispatcher}
|
|
||||||
*/
|
|
||||||
getConnection(guild: Snowflake): StreamDispatcher;
|
|
||||||
}
|
|
||||||
export { VoiceUtils };
|
|
65
helpers/Music/dist/VoiceInterface/VoiceUtils.js
vendored
65
helpers/Music/dist/VoiceInterface/VoiceUtils.js
vendored
|
@ -1,65 +0,0 @@
|
||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.VoiceUtils = void 0;
|
|
||||||
const discord_js_1 = require("discord.js");
|
|
||||||
const voice_1 = require("@discordjs/voice");
|
|
||||||
const StreamDispatcher_1 = require("./StreamDispatcher");
|
|
||||||
class VoiceUtils {
|
|
||||||
/**
|
|
||||||
* The voice utils
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
/**
|
|
||||||
* The cache where voice utils stores stream managers
|
|
||||||
* @type {Collection<Snowflake, StreamDispatcher>}
|
|
||||||
*/
|
|
||||||
this.cache = new discord_js_1.Collection();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Joins a voice channel, creating basic stream dispatch manager
|
|
||||||
* @param {StageChannel|VoiceChannel} channel The voice channel
|
|
||||||
* @param {object} [options] Join options
|
|
||||||
* @returns {Promise<StreamDispatcher>}
|
|
||||||
*/
|
|
||||||
async connect(channel, options) {
|
|
||||||
const conn = await this.join(channel, options);
|
|
||||||
const sub = new StreamDispatcher_1.StreamDispatcher(conn, channel, options.maxTime);
|
|
||||||
this.cache.set(channel.guild.id, sub);
|
|
||||||
return sub;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Joins a voice channel
|
|
||||||
* @param {StageChannel|VoiceChannel} [channel] The voice/stage channel to join
|
|
||||||
* @param {object} [options] Join options
|
|
||||||
* @returns {VoiceConnection}
|
|
||||||
*/
|
|
||||||
async join(channel, options) {
|
|
||||||
const conn = (0, voice_1.joinVoiceChannel)({
|
|
||||||
guildId: channel.guild.id,
|
|
||||||
channelId: channel.id,
|
|
||||||
adapterCreator: channel.guild.voiceAdapterCreator,
|
|
||||||
selfDeaf: Boolean(options.deaf)
|
|
||||||
});
|
|
||||||
return conn;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Disconnects voice connection
|
|
||||||
* @param {VoiceConnection} connection The voice connection
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
disconnect(connection) {
|
|
||||||
if (connection instanceof StreamDispatcher_1.StreamDispatcher)
|
|
||||||
return connection.voiceConnection.destroy();
|
|
||||||
return connection.destroy();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns Discord Player voice connection
|
|
||||||
* @param {Snowflake} guild The guild id
|
|
||||||
* @returns {StreamDispatcher}
|
|
||||||
*/
|
|
||||||
getConnection(guild) {
|
|
||||||
return this.cache.get(guild);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.VoiceUtils = VoiceUtils;
|
|
|
@ -1,34 +0,0 @@
|
||||||
/// <reference types="node" />
|
|
||||||
/// <reference types="node" />
|
|
||||||
import { Transform, TransformOptions } from "stream";
|
|
||||||
export interface VolumeTransformerOptions extends TransformOptions {
|
|
||||||
type?: "s16le" | "s16be" | "s32le" | "s32be";
|
|
||||||
smoothness?: number;
|
|
||||||
volume?: number;
|
|
||||||
}
|
|
||||||
export declare class VolumeTransformer extends Transform {
|
|
||||||
private _bits;
|
|
||||||
private _smoothing;
|
|
||||||
private _bytes;
|
|
||||||
private _extremum;
|
|
||||||
private _chunk;
|
|
||||||
volume: number;
|
|
||||||
private _targetVolume;
|
|
||||||
type: "s16le" | "s32le" | "s16be" | "s32be";
|
|
||||||
constructor(options?: VolumeTransformerOptions);
|
|
||||||
_readInt(buffer: Buffer, index: number): number;
|
|
||||||
_writeInt(buffer: Buffer, int: number, index: number): number;
|
|
||||||
_applySmoothness(): void;
|
|
||||||
_transform(chunk: Buffer, encoding: BufferEncoding, done: () => unknown): unknown;
|
|
||||||
_destroy(err: Error, cb: (error: Error) => void): void;
|
|
||||||
setVolume(volume: number): void;
|
|
||||||
setVolumeDecibels(db: number): void;
|
|
||||||
setVolumeLogarithmic(value: number): void;
|
|
||||||
get volumeDecibels(): number;
|
|
||||||
get volumeLogarithmic(): number;
|
|
||||||
get smoothness(): number;
|
|
||||||
setSmoothness(smoothness: number): void;
|
|
||||||
smoothingEnabled(): boolean;
|
|
||||||
get hasSmoothness(): boolean;
|
|
||||||
static get hasSmoothing(): boolean;
|
|
||||||
}
|
|
|
@ -1,120 +0,0 @@
|
||||||
"use strict";
|
|
||||||
// prism's volume transformer with smooth volume support
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.VolumeTransformer = void 0;
|
|
||||||
const stream_1 = require("stream");
|
|
||||||
class VolumeTransformer extends stream_1.Transform {
|
|
||||||
constructor(options = {}) {
|
|
||||||
super(options);
|
|
||||||
switch (options.type) {
|
|
||||||
case "s16le":
|
|
||||||
this._readInt = (buffer, index) => buffer.readInt16LE(index);
|
|
||||||
this._writeInt = (buffer, int, index) => buffer.writeInt16LE(int, index);
|
|
||||||
this._bits = 16;
|
|
||||||
break;
|
|
||||||
case "s16be":
|
|
||||||
this._readInt = (buffer, index) => buffer.readInt16BE(index);
|
|
||||||
this._writeInt = (buffer, int, index) => buffer.writeInt16BE(int, index);
|
|
||||||
this._bits = 16;
|
|
||||||
break;
|
|
||||||
case "s32le":
|
|
||||||
this._readInt = (buffer, index) => buffer.readInt32LE(index);
|
|
||||||
this._writeInt = (buffer, int, index) => buffer.writeInt32LE(int, index);
|
|
||||||
this._bits = 32;
|
|
||||||
break;
|
|
||||||
case "s32be":
|
|
||||||
this._readInt = (buffer, index) => buffer.readInt32BE(index);
|
|
||||||
this._writeInt = (buffer, int, index) => buffer.writeInt32BE(int, index);
|
|
||||||
this._bits = 32;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error("VolumeTransformer type should be one of s16le, s16be, s32le, s32be");
|
|
||||||
}
|
|
||||||
this.type = options.type;
|
|
||||||
this._bytes = this._bits / 8;
|
|
||||||
this._extremum = Math.pow(2, this._bits - 1);
|
|
||||||
this.volume = Number.isNaN(options.volume) ? 1 : Number(options.volume);
|
|
||||||
if (!Number.isFinite(this.volume))
|
|
||||||
this.volume = 1;
|
|
||||||
this._targetVolume = this.volume;
|
|
||||||
this._chunk = Buffer.alloc(0);
|
|
||||||
this._smoothing = options.smoothness || 0;
|
|
||||||
}
|
|
||||||
_readInt(buffer, index) {
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
_writeInt(buffer, int, index) {
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
_applySmoothness() {
|
|
||||||
if (this.volume < this._targetVolume) {
|
|
||||||
this.volume = this.volume + this._smoothing >= this._targetVolume ? this._targetVolume : this.volume + this._smoothing;
|
|
||||||
}
|
|
||||||
else if (this.volume > this._targetVolume) {
|
|
||||||
this.volume = this.volume - this._smoothing <= this._targetVolume ? this._targetVolume : this.volume - this._smoothing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_transform(chunk, encoding, done) {
|
|
||||||
if (this.smoothingEnabled() && this.volume !== this._targetVolume)
|
|
||||||
this._applySmoothness();
|
|
||||||
if (this.volume === 1) {
|
|
||||||
this.push(chunk);
|
|
||||||
return done();
|
|
||||||
}
|
|
||||||
const { _bytes, _extremum } = this;
|
|
||||||
chunk = this._chunk = Buffer.concat([this._chunk, chunk]);
|
|
||||||
if (chunk.length < _bytes)
|
|
||||||
return done();
|
|
||||||
const complete = Math.floor(chunk.length / _bytes) * _bytes;
|
|
||||||
for (let i = 0; i < complete; i += _bytes) {
|
|
||||||
const int = Math.min(_extremum - 1, Math.max(-_extremum, Math.floor(this.volume * this._readInt(chunk, i))));
|
|
||||||
this._writeInt(chunk, int, i);
|
|
||||||
}
|
|
||||||
this._chunk = chunk.slice(complete);
|
|
||||||
this.push(chunk.slice(0, complete));
|
|
||||||
return done();
|
|
||||||
}
|
|
||||||
_destroy(err, cb) {
|
|
||||||
super._destroy(err, cb);
|
|
||||||
this._chunk = null;
|
|
||||||
}
|
|
||||||
setVolume(volume) {
|
|
||||||
if (Number.isNaN(volume))
|
|
||||||
volume = 1;
|
|
||||||
if (typeof volume !== "number")
|
|
||||||
volume = Number(volume);
|
|
||||||
if (!Number.isFinite(volume))
|
|
||||||
volume = volume < 0 ? 0 : 1;
|
|
||||||
this._targetVolume = volume;
|
|
||||||
if (this._smoothing <= 0)
|
|
||||||
this.volume = volume;
|
|
||||||
}
|
|
||||||
setVolumeDecibels(db) {
|
|
||||||
this.setVolume(Math.pow(10, db / 20));
|
|
||||||
}
|
|
||||||
setVolumeLogarithmic(value) {
|
|
||||||
this.setVolume(Math.pow(value, 1.660964));
|
|
||||||
}
|
|
||||||
get volumeDecibels() {
|
|
||||||
return Math.log10(this.volume) * 20;
|
|
||||||
}
|
|
||||||
get volumeLogarithmic() {
|
|
||||||
return Math.pow(this.volume, 1 / 1.660964);
|
|
||||||
}
|
|
||||||
get smoothness() {
|
|
||||||
return this._smoothing;
|
|
||||||
}
|
|
||||||
setSmoothness(smoothness) {
|
|
||||||
this._smoothing = smoothness;
|
|
||||||
}
|
|
||||||
smoothingEnabled() {
|
|
||||||
return Number.isFinite(this._smoothing) && this._smoothing > 0;
|
|
||||||
}
|
|
||||||
get hasSmoothness() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
static get hasSmoothing() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.VolumeTransformer = VolumeTransformer;
|
|
15
helpers/Music/dist/index.d.ts
vendored
15
helpers/Music/dist/index.d.ts
vendored
|
@ -1,15 +0,0 @@
|
||||||
import "./smoothVolume";
|
|
||||||
export { AudioFilters } from "./utils/AudioFilters";
|
|
||||||
export { ExtractorModel } from "./Structures/ExtractorModel";
|
|
||||||
export { Playlist } from "./Structures/Playlist";
|
|
||||||
export { Player } from "./Player";
|
|
||||||
export { PlayerError, ErrorStatusCode } from "./Structures/PlayerError";
|
|
||||||
export { QueryResolver } from "./utils/QueryResolver";
|
|
||||||
export { Queue } from "./Structures/Queue";
|
|
||||||
export { Track } from "./Structures/Track";
|
|
||||||
export { VoiceUtils } from "./VoiceInterface/VoiceUtils";
|
|
||||||
export { VoiceEvents, StreamDispatcher } from "./VoiceInterface/StreamDispatcher";
|
|
||||||
export { Util } from "./utils/Util";
|
|
||||||
export * from "./types/types";
|
|
||||||
export * from "./utils/FFmpegStream";
|
|
||||||
export declare const version: string;
|
|
33
helpers/Music/dist/index.js
vendored
33
helpers/Music/dist/index.js
vendored
|
@ -1,33 +0,0 @@
|
||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.version = exports.Util = exports.StreamDispatcher = exports.VoiceUtils = exports.Track = exports.Queue = exports.QueryResolver = exports.ErrorStatusCode = exports.PlayerError = exports.Player = exports.Playlist = exports.ExtractorModel = exports.AudioFilters = void 0;
|
|
||||||
const tslib_1 = require("tslib");
|
|
||||||
// try applying smooth volume patch on load
|
|
||||||
require("./smoothVolume");
|
|
||||||
var AudioFilters_1 = require("./utils/AudioFilters");
|
|
||||||
Object.defineProperty(exports, "AudioFilters", { enumerable: true, get: function () { return AudioFilters_1.AudioFilters; } });
|
|
||||||
var ExtractorModel_1 = require("./Structures/ExtractorModel");
|
|
||||||
Object.defineProperty(exports, "ExtractorModel", { enumerable: true, get: function () { return ExtractorModel_1.ExtractorModel; } });
|
|
||||||
var Playlist_1 = require("./Structures/Playlist");
|
|
||||||
Object.defineProperty(exports, "Playlist", { enumerable: true, get: function () { return Playlist_1.Playlist; } });
|
|
||||||
var Player_1 = require("./Player");
|
|
||||||
Object.defineProperty(exports, "Player", { enumerable: true, get: function () { return Player_1.Player; } });
|
|
||||||
var PlayerError_1 = require("./Structures/PlayerError");
|
|
||||||
Object.defineProperty(exports, "PlayerError", { enumerable: true, get: function () { return PlayerError_1.PlayerError; } });
|
|
||||||
Object.defineProperty(exports, "ErrorStatusCode", { enumerable: true, get: function () { return PlayerError_1.ErrorStatusCode; } });
|
|
||||||
var QueryResolver_1 = require("./utils/QueryResolver");
|
|
||||||
Object.defineProperty(exports, "QueryResolver", { enumerable: true, get: function () { return QueryResolver_1.QueryResolver; } });
|
|
||||||
var Queue_1 = require("./Structures/Queue");
|
|
||||||
Object.defineProperty(exports, "Queue", { enumerable: true, get: function () { return Queue_1.Queue; } });
|
|
||||||
var Track_1 = require("./Structures/Track");
|
|
||||||
Object.defineProperty(exports, "Track", { enumerable: true, get: function () { return Track_1.Track; } });
|
|
||||||
var VoiceUtils_1 = require("./VoiceInterface/VoiceUtils");
|
|
||||||
Object.defineProperty(exports, "VoiceUtils", { enumerable: true, get: function () { return VoiceUtils_1.VoiceUtils; } });
|
|
||||||
var StreamDispatcher_1 = require("./VoiceInterface/StreamDispatcher");
|
|
||||||
Object.defineProperty(exports, "StreamDispatcher", { enumerable: true, get: function () { return StreamDispatcher_1.StreamDispatcher; } });
|
|
||||||
var Util_1 = require("./utils/Util");
|
|
||||||
Object.defineProperty(exports, "Util", { enumerable: true, get: function () { return Util_1.Util; } });
|
|
||||||
tslib_1.__exportStar(require("./types/types"), exports);
|
|
||||||
tslib_1.__exportStar(require("./utils/FFmpegStream"), exports);
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
exports.version = require(`${__dirname}/../package.json`).version;
|
|
21
helpers/Music/dist/index.mjs
vendored
21
helpers/Music/dist/index.mjs
vendored
|
@ -1,21 +0,0 @@
|
||||||
import mod from "./index.js";
|
|
||||||
|
|
||||||
export default mod;
|
|
||||||
export const AudioFilters = mod.AudioFilters;
|
|
||||||
export const ErrorStatusCode = mod.ErrorStatusCode;
|
|
||||||
export const ExtractorModel = mod.ExtractorModel;
|
|
||||||
export const FFMPEG_ARGS_PIPED = mod.FFMPEG_ARGS_PIPED;
|
|
||||||
export const FFMPEG_ARGS_STRING = mod.FFMPEG_ARGS_STRING;
|
|
||||||
export const Player = mod.Player;
|
|
||||||
export const PlayerError = mod.PlayerError;
|
|
||||||
export const Playlist = mod.Playlist;
|
|
||||||
export const QueryResolver = mod.QueryResolver;
|
|
||||||
export const QueryType = mod.QueryType;
|
|
||||||
export const Queue = mod.Queue;
|
|
||||||
export const QueueRepeatMode = mod.QueueRepeatMode;
|
|
||||||
export const StreamDispatcher = mod.StreamDispatcher;
|
|
||||||
export const Track = mod.Track;
|
|
||||||
export const Util = mod.Util;
|
|
||||||
export const VoiceUtils = mod.VoiceUtils;
|
|
||||||
export const createFFmpegStream = mod.createFFmpegStream;
|
|
||||||
export const version = mod.version;
|
|
1
helpers/Music/dist/smoothVolume.d.ts
vendored
1
helpers/Music/dist/smoothVolume.d.ts
vendored
|
@ -1 +0,0 @@
|
||||||
export {};
|
|
13
helpers/Music/dist/smoothVolume.js
vendored
13
helpers/Music/dist/smoothVolume.js
vendored
|
@ -1,13 +0,0 @@
|
||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
const VolumeTransformer_1 = require("./VoiceInterface/VolumeTransformer");
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
const mod = require("prism-media");
|
|
||||||
if (typeof mod.VolumeTransformer.hasSmoothing !== "boolean") {
|
|
||||||
Reflect.set(mod, "VolumeTransformer", VolumeTransformer_1.VolumeTransformer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
/* do nothing */
|
|
||||||
}
|
|
453
helpers/Music/dist/types/types.d.ts
vendored
453
helpers/Music/dist/types/types.d.ts
vendored
|
@ -1,453 +0,0 @@
|
||||||
/// <reference types="node" />
|
|
||||||
import { Snowflake, User, UserResolvable } from "discord.js";
|
|
||||||
import { Readable, Duplex } from "stream";
|
|
||||||
import { Queue } from "../Structures/Queue";
|
|
||||||
import Track from "../Structures/Track";
|
|
||||||
import { Playlist } from "../Structures/Playlist";
|
|
||||||
import { StreamDispatcher } from "../VoiceInterface/StreamDispatcher";
|
|
||||||
export declare type FiltersName = keyof QueueFilters;
|
|
||||||
export interface PlayerSearchResult {
|
|
||||||
playlist: Playlist | null;
|
|
||||||
tracks: Track[];
|
|
||||||
searched?: boolean;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @typedef {AudioFilters} QueueFilters
|
|
||||||
*/
|
|
||||||
export interface QueueFilters {
|
|
||||||
bassboost_low?: boolean;
|
|
||||||
bassboost?: boolean;
|
|
||||||
bassboost_high?: boolean;
|
|
||||||
"8D"?: boolean;
|
|
||||||
vaporwave?: boolean;
|
|
||||||
nightcore?: boolean;
|
|
||||||
phaser?: boolean;
|
|
||||||
tremolo?: boolean;
|
|
||||||
vibrato?: boolean;
|
|
||||||
reverse?: boolean;
|
|
||||||
treble?: boolean;
|
|
||||||
normalizer?: boolean;
|
|
||||||
normalizer2?: boolean;
|
|
||||||
surrounding?: boolean;
|
|
||||||
pulsator?: boolean;
|
|
||||||
subboost?: boolean;
|
|
||||||
karaoke?: boolean;
|
|
||||||
flanger?: boolean;
|
|
||||||
gate?: boolean;
|
|
||||||
haas?: boolean;
|
|
||||||
mcompand?: boolean;
|
|
||||||
mono?: boolean;
|
|
||||||
mstlr?: boolean;
|
|
||||||
mstrr?: boolean;
|
|
||||||
compressor?: boolean;
|
|
||||||
expander?: boolean;
|
|
||||||
softlimiter?: boolean;
|
|
||||||
chorus?: boolean;
|
|
||||||
chorus2d?: boolean;
|
|
||||||
chorus3d?: boolean;
|
|
||||||
fadein?: boolean;
|
|
||||||
dim?: boolean;
|
|
||||||
earrape?: boolean;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The track source:
|
|
||||||
* - soundcloud
|
|
||||||
* - youtube
|
|
||||||
* - spotify
|
|
||||||
* - arbitrary
|
|
||||||
* @typedef {string} TrackSource
|
|
||||||
*/
|
|
||||||
export declare type TrackSource = "soundcloud" | "youtube" | "spotify" | "arbitrary";
|
|
||||||
/**
|
|
||||||
* @typedef {object} RawTrackData
|
|
||||||
* @property {string} title The title
|
|
||||||
* @property {string} description The description
|
|
||||||
* @property {string} author The author
|
|
||||||
* @property {string} url The url
|
|
||||||
* @property {string} thumbnail The thumbnail
|
|
||||||
* @property {string} duration The duration
|
|
||||||
* @property {number | boolean} views The views
|
|
||||||
* @property {User} requestedBy The user who requested this track
|
|
||||||
* @property {Playlist} [playlist] The playlist
|
|
||||||
* @property {TrackSource} [source="arbitrary"] The source
|
|
||||||
* @property {any} [engine] The engine
|
|
||||||
* @property {boolean} [live] If this track is live
|
|
||||||
* @property {any} [raw] The raw data
|
|
||||||
*/
|
|
||||||
export interface RawTrackData {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
author: string;
|
|
||||||
url: string;
|
|
||||||
thumbnail: string;
|
|
||||||
duration: string;
|
|
||||||
views: number;
|
|
||||||
requestedBy: User;
|
|
||||||
playlist?: Playlist;
|
|
||||||
source?: TrackSource;
|
|
||||||
engine?: any;
|
|
||||||
live?: boolean;
|
|
||||||
raw?: any;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @typedef {object} TimeData
|
|
||||||
* @property {number} days Time in days
|
|
||||||
* @property {number} hours Time in hours
|
|
||||||
* @property {number} minutes Time in minutes
|
|
||||||
* @property {number} seconds Time in seconds
|
|
||||||
*/
|
|
||||||
export interface TimeData {
|
|
||||||
days: number;
|
|
||||||
hours: number;
|
|
||||||
minutes: number;
|
|
||||||
seconds: number;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @typedef {object} PlayerProgressbarOptions
|
|
||||||
* @property {boolean} [timecodes] If it should render time codes
|
|
||||||
* @property {boolean} [queue] If it should create progress bar for the whole queue
|
|
||||||
* @property {number} [length] The bar length
|
|
||||||
* @property {string} [line] The bar track
|
|
||||||
* @property {string} [indicator] The indicator
|
|
||||||
*/
|
|
||||||
export interface PlayerProgressbarOptions {
|
|
||||||
timecodes?: boolean;
|
|
||||||
length?: number;
|
|
||||||
line?: string;
|
|
||||||
indicator?: string;
|
|
||||||
queue?: boolean;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @typedef {object} PlayerOptions
|
|
||||||
* @property {boolean} [leaveOnEnd=true] If it should leave on end
|
|
||||||
* @property {boolean} [leaveOnStop=true] If it should leave on stop
|
|
||||||
* @property {boolean} [leaveOnEmpty=true] If it should leave on empty
|
|
||||||
* @property {number} [leaveOnEmptyCooldown=1000] The cooldown in ms
|
|
||||||
* @property {boolean} [autoSelfDeaf=true] If it should set the bot in deaf mode
|
|
||||||
* @property {YTDLDownloadOptions} [ytdlOptions] The youtube download options
|
|
||||||
* @property {number} [initialVolume=100] The initial player volume
|
|
||||||
* @property {number} [bufferingTimeout=3000] Buffering timeout for the stream
|
|
||||||
* @property {boolean} [spotifyBridge=true] If player should bridge spotify source to youtube
|
|
||||||
* @property {boolean} [disableVolume=false] If player should disable inline volume
|
|
||||||
* @property {number} [volumeSmoothness=0] The volume transition smoothness between volume changes (lower the value to get better result)
|
|
||||||
* Setting this or leaving this empty will disable this effect. Example: `volumeSmoothness: 0.1`
|
|
||||||
* @property {Function} [onBeforeCreateStream] Runs before creating stream
|
|
||||||
*/
|
|
||||||
export interface PlayerOptions {
|
|
||||||
leaveOnEnd?: boolean;
|
|
||||||
leaveOnStop?: boolean;
|
|
||||||
leaveOnEmpty?: boolean;
|
|
||||||
leaveOnEmptyCooldown?: number;
|
|
||||||
autoSelfDeaf?: boolean;
|
|
||||||
initialVolume?: number;
|
|
||||||
bufferingTimeout?: number;
|
|
||||||
spotifyBridge?: boolean;
|
|
||||||
disableVolume?: boolean;
|
|
||||||
volumeSmoothness?: number;
|
|
||||||
onBeforeCreateStream?: (track: Track, source: TrackSource, queue: Queue) => Promise<Readable>;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @typedef {object} ExtractorModelData
|
|
||||||
* @property {object} [playlist] The playlist info (if any)
|
|
||||||
* @property {string} [playlist.title] The playlist title
|
|
||||||
* @property {string} [playlist.description] The playlist description
|
|
||||||
* @property {string} [playlist.thumbnail] The playlist thumbnail
|
|
||||||
* @property {album|playlist} [playlist.type] The playlist type: `album` | `playlist`
|
|
||||||
* @property {TrackSource} [playlist.source] The playlist source
|
|
||||||
* @property {object} [playlist.author] The playlist author
|
|
||||||
* @property {string} [playlist.author.name] The author name
|
|
||||||
* @property {string} [playlist.author.url] The author url
|
|
||||||
* @property {string} [playlist.id] The playlist id
|
|
||||||
* @property {string} [playlist.url] The playlist url
|
|
||||||
* @property {any} [playlist.rawPlaylist] The raw data
|
|
||||||
* @property {ExtractorData[]} data The data
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @typedef {object} ExtractorData
|
|
||||||
* @property {string} title The title
|
|
||||||
* @property {number} duration The duration
|
|
||||||
* @property {string} thumbnail The thumbnail
|
|
||||||
* @property {string|Readable|Duplex} engine The stream engine
|
|
||||||
* @property {number} views The views count
|
|
||||||
* @property {string} author The author
|
|
||||||
* @property {string} description The description
|
|
||||||
* @property {string} url The url
|
|
||||||
* @property {string} [version] The extractor version
|
|
||||||
* @property {TrackSource} [source="arbitrary"] The source
|
|
||||||
*/
|
|
||||||
export interface ExtractorModelData {
|
|
||||||
playlist?: {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
thumbnail: string;
|
|
||||||
type: "album" | "playlist";
|
|
||||||
source: TrackSource;
|
|
||||||
author: {
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
};
|
|
||||||
id: string;
|
|
||||||
url: string;
|
|
||||||
rawPlaylist?: any;
|
|
||||||
};
|
|
||||||
data: {
|
|
||||||
title: string;
|
|
||||||
duration: number;
|
|
||||||
thumbnail: string;
|
|
||||||
engine: string | Readable | Duplex;
|
|
||||||
views: number;
|
|
||||||
author: string;
|
|
||||||
description: string;
|
|
||||||
url: string;
|
|
||||||
version?: string;
|
|
||||||
source?: TrackSource;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The search query type
|
|
||||||
* This can be one of:
|
|
||||||
* - AUTO
|
|
||||||
* - YOUTUBE
|
|
||||||
* - YOUTUBE_PLAYLIST
|
|
||||||
* - SOUNDCLOUD_TRACK
|
|
||||||
* - SOUNDCLOUD_PLAYLIST
|
|
||||||
* - SOUNDCLOUD
|
|
||||||
* - SPOTIFY_SONG
|
|
||||||
* - SPOTIFY_ALBUM
|
|
||||||
* - SPOTIFY_PLAYLIST
|
|
||||||
* - FACEBOOK
|
|
||||||
* - VIMEO
|
|
||||||
* - ARBITRARY
|
|
||||||
* - REVERBNATION
|
|
||||||
* - YOUTUBE_SEARCH
|
|
||||||
* - YOUTUBE_VIDEO
|
|
||||||
* - SOUNDCLOUD_SEARCH
|
|
||||||
* @typedef {number} QueryType
|
|
||||||
*/
|
|
||||||
export declare enum QueryType {
|
|
||||||
AUTO = 0,
|
|
||||||
YOUTUBE = 1,
|
|
||||||
YOUTUBE_PLAYLIST = 2,
|
|
||||||
SOUNDCLOUD_TRACK = 3,
|
|
||||||
SOUNDCLOUD_PLAYLIST = 4,
|
|
||||||
SOUNDCLOUD = 5,
|
|
||||||
SPOTIFY_SONG = 6,
|
|
||||||
SPOTIFY_ALBUM = 7,
|
|
||||||
SPOTIFY_PLAYLIST = 8,
|
|
||||||
FACEBOOK = 9,
|
|
||||||
VIMEO = 10,
|
|
||||||
ARBITRARY = 11,
|
|
||||||
REVERBNATION = 12,
|
|
||||||
YOUTUBE_SEARCH = 13,
|
|
||||||
YOUTUBE_VIDEO = 14,
|
|
||||||
SOUNDCLOUD_SEARCH = 15
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Emitted when bot gets disconnected from a voice channel
|
|
||||||
* @event Player#botDisconnect
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Emitted when the voice channel is empty
|
|
||||||
* @event Player#channelEmpty
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Emitted when bot connects to a voice channel
|
|
||||||
* @event Player#connectionCreate
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
* @param {StreamDispatcher} connection The discord player connection object
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Debug information
|
|
||||||
* @event Player#debug
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
* @param {string} message The message
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Emitted on error
|
|
||||||
* <warn>This event should handled properly otherwise it may crash your process!</warn>
|
|
||||||
* @event Player#error
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
* @param {Error} error The error
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Emitted on connection error. Sometimes stream errors are emitted here as well.
|
|
||||||
* @event Player#connectionError
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
* @param {Error} error The error
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Emitted when queue ends
|
|
||||||
* @event Player#queueEnd
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Emitted when a single track is added
|
|
||||||
* @event Player#trackAdd
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
* @param {Track} track The track
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Emitted when multiple tracks are added
|
|
||||||
* @event Player#tracksAdd
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
* @param {Track[]} tracks The tracks
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Emitted when a track starts playing
|
|
||||||
* @event Player#trackStart
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
* @param {Track} track The track
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Emitted when a track ends
|
|
||||||
* @event Player#trackEnd
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
* @param {Track} track The track
|
|
||||||
*/
|
|
||||||
export interface PlayerEvents {
|
|
||||||
botDisconnect: (queue: Queue) => any;
|
|
||||||
channelEmpty: (queue: Queue) => any;
|
|
||||||
connectionCreate: (queue: Queue, connection: StreamDispatcher) => any;
|
|
||||||
debug: (queue: Queue, message: string) => any;
|
|
||||||
error: (queue: Queue, error: Error) => any;
|
|
||||||
connectionError: (queue: Queue, error: Error) => any;
|
|
||||||
queueEnd: (queue: Queue) => any;
|
|
||||||
trackAdd: (queue: Queue, track: Track) => any;
|
|
||||||
tracksAdd: (queue: Queue, track: Track[]) => any;
|
|
||||||
trackStart: (queue: Queue, track: Track) => any;
|
|
||||||
trackEnd: (queue: Queue, track: Track) => any;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @typedef {object} PlayOptions
|
|
||||||
* @property {boolean} [filtersUpdate=false] If this play was triggered for filters update
|
|
||||||
* @property {string[]} [encoderArgs=[]] FFmpeg args passed to encoder
|
|
||||||
* @property {number} [seek] Time to seek to before playing
|
|
||||||
* @property {boolean} [immediate=false] If it should start playing the provided track immediately
|
|
||||||
*/
|
|
||||||
export interface PlayOptions {
|
|
||||||
filtersUpdate?: boolean;
|
|
||||||
encoderArgs?: string[];
|
|
||||||
seek?: number;
|
|
||||||
immediate?: boolean;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @typedef {object} SearchOptions
|
|
||||||
* @property {UserResolvable} requestedBy The user who requested this search
|
|
||||||
* @property {QueryType|string} [searchEngine=QueryType.AUTO] The query search engine, can be extractor name to target specific one (custom)
|
|
||||||
* @property {boolean} [blockExtractor=false] If it should block custom extractors
|
|
||||||
*/
|
|
||||||
export interface SearchOptions {
|
|
||||||
requestedBy: UserResolvable;
|
|
||||||
searchEngine?: QueryType | string;
|
|
||||||
blockExtractor?: boolean;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The queue repeat mode. This can be one of:
|
|
||||||
* - OFF
|
|
||||||
* - TRACK
|
|
||||||
* - QUEUE
|
|
||||||
* - AUTOPLAY
|
|
||||||
* @typedef {number} QueueRepeatMode
|
|
||||||
*/
|
|
||||||
export declare enum QueueRepeatMode {
|
|
||||||
OFF = 0,
|
|
||||||
TRACK = 1,
|
|
||||||
QUEUE = 2,
|
|
||||||
AUTOPLAY = 3
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @typedef {object} PlaylistInitData
|
|
||||||
* @property {Track[]} tracks The tracks of this playlist
|
|
||||||
* @property {string} title The playlist title
|
|
||||||
* @property {string} description The description
|
|
||||||
* @property {string} thumbnail The thumbnail
|
|
||||||
* @property {album|playlist} type The playlist type: `album` | `playlist`
|
|
||||||
* @property {TrackSource} source The playlist source
|
|
||||||
* @property {object} author The playlist author
|
|
||||||
* @property {string} [author.name] The author name
|
|
||||||
* @property {string} [author.url] The author url
|
|
||||||
* @property {string} id The playlist id
|
|
||||||
* @property {string} url The playlist url
|
|
||||||
* @property {any} [rawPlaylist] The raw playlist data
|
|
||||||
*/
|
|
||||||
export interface PlaylistInitData {
|
|
||||||
tracks: Track[];
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
thumbnail: string;
|
|
||||||
type: "album" | "playlist";
|
|
||||||
source: TrackSource;
|
|
||||||
author: {
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
};
|
|
||||||
id: string;
|
|
||||||
url: string;
|
|
||||||
rawPlaylist?: any;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @typedef {object} TrackJSON
|
|
||||||
* @property {string} title The track title
|
|
||||||
* @property {string} description The track description
|
|
||||||
* @property {string} author The author
|
|
||||||
* @property {string} url The url
|
|
||||||
* @property {string} thumbnail The thumbnail
|
|
||||||
* @property {string} duration The duration
|
|
||||||
* @property {number} durationMS The duration in ms
|
|
||||||
* @property {number} views The views count
|
|
||||||
* @property {Snowflake} requestedBy The id of the user who requested this track
|
|
||||||
* @property {PlaylistJSON} [playlist] The playlist info (if any)
|
|
||||||
*/
|
|
||||||
export interface TrackJSON {
|
|
||||||
id: Snowflake;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
author: string;
|
|
||||||
url: string;
|
|
||||||
thumbnail: string;
|
|
||||||
duration: string;
|
|
||||||
durationMS: number;
|
|
||||||
views: number;
|
|
||||||
requestedBy: Snowflake;
|
|
||||||
playlist?: PlaylistJSON;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @typedef {object} PlaylistJSON
|
|
||||||
* @property {string} id The playlist id
|
|
||||||
* @property {string} url The playlist url
|
|
||||||
* @property {string} title The playlist title
|
|
||||||
* @property {string} description The playlist description
|
|
||||||
* @property {string} thumbnail The thumbnail
|
|
||||||
* @property {album|playlist} type The playlist type: `album` | `playlist`
|
|
||||||
* @property {TrackSource} source The track source
|
|
||||||
* @property {object} author The playlist author
|
|
||||||
* @property {string} [author.name] The author name
|
|
||||||
* @property {string} [author.url] The author url
|
|
||||||
* @property {TrackJSON[]} tracks The tracks data (if any)
|
|
||||||
*/
|
|
||||||
export interface PlaylistJSON {
|
|
||||||
id: string;
|
|
||||||
url: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
thumbnail: string;
|
|
||||||
type: "album" | "playlist";
|
|
||||||
source: TrackSource;
|
|
||||||
author: {
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
};
|
|
||||||
tracks: TrackJSON[];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @typedef {object} PlayerInitOptions
|
|
||||||
* @property {boolean} [autoRegisterExtractor=true] If it should automatically register `@discord-player/extractor`
|
|
||||||
* @property {YTDLDownloadOptions} [ytdlOptions] The options passed to `ytdl-core`
|
|
||||||
* @property {number} [connectionTimeout=20000] The voice connection timeout
|
|
||||||
*/
|
|
||||||
export interface PlayerInitOptions {
|
|
||||||
autoRegisterExtractor?: boolean;
|
|
||||||
connectionTimeout?: number;
|
|
||||||
}
|
|
58
helpers/Music/dist/types/types.js
vendored
58
helpers/Music/dist/types/types.js
vendored
|
@ -1,58 +0,0 @@
|
||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.QueueRepeatMode = exports.QueryType = void 0;
|
|
||||||
/**
|
|
||||||
* The search query type
|
|
||||||
* This can be one of:
|
|
||||||
* - AUTO
|
|
||||||
* - YOUTUBE
|
|
||||||
* - YOUTUBE_PLAYLIST
|
|
||||||
* - SOUNDCLOUD_TRACK
|
|
||||||
* - SOUNDCLOUD_PLAYLIST
|
|
||||||
* - SOUNDCLOUD
|
|
||||||
* - SPOTIFY_SONG
|
|
||||||
* - SPOTIFY_ALBUM
|
|
||||||
* - SPOTIFY_PLAYLIST
|
|
||||||
* - FACEBOOK
|
|
||||||
* - VIMEO
|
|
||||||
* - ARBITRARY
|
|
||||||
* - REVERBNATION
|
|
||||||
* - YOUTUBE_SEARCH
|
|
||||||
* - YOUTUBE_VIDEO
|
|
||||||
* - SOUNDCLOUD_SEARCH
|
|
||||||
* @typedef {number} QueryType
|
|
||||||
*/
|
|
||||||
var QueryType;
|
|
||||||
(function (QueryType) {
|
|
||||||
QueryType[QueryType["AUTO"] = 0] = "AUTO";
|
|
||||||
QueryType[QueryType["YOUTUBE"] = 1] = "YOUTUBE";
|
|
||||||
QueryType[QueryType["YOUTUBE_PLAYLIST"] = 2] = "YOUTUBE_PLAYLIST";
|
|
||||||
QueryType[QueryType["SOUNDCLOUD_TRACK"] = 3] = "SOUNDCLOUD_TRACK";
|
|
||||||
QueryType[QueryType["SOUNDCLOUD_PLAYLIST"] = 4] = "SOUNDCLOUD_PLAYLIST";
|
|
||||||
QueryType[QueryType["SOUNDCLOUD"] = 5] = "SOUNDCLOUD";
|
|
||||||
QueryType[QueryType["SPOTIFY_SONG"] = 6] = "SPOTIFY_SONG";
|
|
||||||
QueryType[QueryType["SPOTIFY_ALBUM"] = 7] = "SPOTIFY_ALBUM";
|
|
||||||
QueryType[QueryType["SPOTIFY_PLAYLIST"] = 8] = "SPOTIFY_PLAYLIST";
|
|
||||||
QueryType[QueryType["FACEBOOK"] = 9] = "FACEBOOK";
|
|
||||||
QueryType[QueryType["VIMEO"] = 10] = "VIMEO";
|
|
||||||
QueryType[QueryType["ARBITRARY"] = 11] = "ARBITRARY";
|
|
||||||
QueryType[QueryType["REVERBNATION"] = 12] = "REVERBNATION";
|
|
||||||
QueryType[QueryType["YOUTUBE_SEARCH"] = 13] = "YOUTUBE_SEARCH";
|
|
||||||
QueryType[QueryType["YOUTUBE_VIDEO"] = 14] = "YOUTUBE_VIDEO";
|
|
||||||
QueryType[QueryType["SOUNDCLOUD_SEARCH"] = 15] = "SOUNDCLOUD_SEARCH";
|
|
||||||
})(QueryType = exports.QueryType || (exports.QueryType = {}));
|
|
||||||
/**
|
|
||||||
* The queue repeat mode. This can be one of:
|
|
||||||
* - OFF
|
|
||||||
* - TRACK
|
|
||||||
* - QUEUE
|
|
||||||
* - AUTOPLAY
|
|
||||||
* @typedef {number} QueueRepeatMode
|
|
||||||
*/
|
|
||||||
var QueueRepeatMode;
|
|
||||||
(function (QueueRepeatMode) {
|
|
||||||
QueueRepeatMode[QueueRepeatMode["OFF"] = 0] = "OFF";
|
|
||||||
QueueRepeatMode[QueueRepeatMode["TRACK"] = 1] = "TRACK";
|
|
||||||
QueueRepeatMode[QueueRepeatMode["QUEUE"] = 2] = "QUEUE";
|
|
||||||
QueueRepeatMode[QueueRepeatMode["AUTOPLAY"] = 3] = "AUTOPLAY";
|
|
||||||
})(QueueRepeatMode = exports.QueueRepeatMode || (exports.QueueRepeatMode = {}));
|
|
36
helpers/Music/dist/utils/AudioFilters.d.ts
vendored
36
helpers/Music/dist/utils/AudioFilters.d.ts
vendored
|
@ -1,36 +0,0 @@
|
||||||
import { FiltersName } from "../types/types";
|
|
||||||
declare class AudioFilters {
|
|
||||||
constructor();
|
|
||||||
static get filters(): Record<FiltersName, string>;
|
|
||||||
static get<K extends FiltersName>(name: K): Record<keyof import("../types/types").QueueFilters, string>[K];
|
|
||||||
static has<K extends FiltersName>(name: K): boolean;
|
|
||||||
static [Symbol.iterator](): IterableIterator<{
|
|
||||||
name: FiltersName;
|
|
||||||
value: string;
|
|
||||||
}>;
|
|
||||||
static get names(): (keyof import("../types/types").QueueFilters)[];
|
|
||||||
static get length(): number;
|
|
||||||
static toString(): string;
|
|
||||||
/**
|
|
||||||
* Create ffmpeg args from the specified filters name
|
|
||||||
* @param filter The filter name
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
static create<K extends FiltersName>(filters?: K[]): string;
|
|
||||||
/**
|
|
||||||
* Defines audio filter
|
|
||||||
* @param filterName The name of the filter
|
|
||||||
* @param value The ffmpeg args
|
|
||||||
*/
|
|
||||||
static define(filterName: string, value: string): void;
|
|
||||||
/**
|
|
||||||
* Defines multiple audio filters
|
|
||||||
* @param filtersArray Array of filters containing the filter name and ffmpeg args
|
|
||||||
*/
|
|
||||||
static defineBulk(filtersArray: {
|
|
||||||
name: string;
|
|
||||||
value: string;
|
|
||||||
}[]): void;
|
|
||||||
}
|
|
||||||
export default AudioFilters;
|
|
||||||
export { AudioFilters };
|
|
97
helpers/Music/dist/utils/AudioFilters.js
vendored
97
helpers/Music/dist/utils/AudioFilters.js
vendored
|
@ -1,97 +0,0 @@
|
||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.AudioFilters = void 0;
|
|
||||||
const bass = (g) => `bass=g=${g}:f=110:w=0.3`;
|
|
||||||
class AudioFilters {
|
|
||||||
constructor() {
|
|
||||||
return AudioFilters;
|
|
||||||
}
|
|
||||||
static get filters() {
|
|
||||||
return {
|
|
||||||
bassboost_low: bass(15),
|
|
||||||
bassboost: bass(20),
|
|
||||||
bassboost_high: bass(30),
|
|
||||||
"8D": "apulsator=hz=0.09",
|
|
||||||
vaporwave: "aresample=48000,asetrate=48000*0.8",
|
|
||||||
nightcore: "aresample=48000,asetrate=48000*1.25",
|
|
||||||
phaser: "aphaser=in_gain=0.4",
|
|
||||||
tremolo: "tremolo",
|
|
||||||
vibrato: "vibrato=f=6.5",
|
|
||||||
reverse: "areverse",
|
|
||||||
treble: "treble=g=5",
|
|
||||||
normalizer2: "dynaudnorm=g=101",
|
|
||||||
normalizer: "acompressor",
|
|
||||||
surrounding: "surround",
|
|
||||||
pulsator: "apulsator=hz=1",
|
|
||||||
subboost: "asubboost",
|
|
||||||
karaoke: "stereotools=mlev=0.03",
|
|
||||||
flanger: "flanger",
|
|
||||||
gate: "agate",
|
|
||||||
haas: "haas",
|
|
||||||
mcompand: "mcompand",
|
|
||||||
mono: "pan=mono|c0=.5*c0+.5*c1",
|
|
||||||
mstlr: "stereotools=mode=ms>lr",
|
|
||||||
mstrr: "stereotools=mode=ms>rr",
|
|
||||||
compressor: "compand=points=-80/-105|-62/-80|-15.4/-15.4|0/-12|20/-7.6",
|
|
||||||
expander: "compand=attacks=0:points=-80/-169|-54/-80|-49.5/-64.6|-41.1/-41.1|-25.8/-15|-10.8/-4.5|0/0|20/8.3",
|
|
||||||
softlimiter: "compand=attacks=0:points=-80/-80|-12.4/-12.4|-6/-8|0/-6.8|20/-2.8",
|
|
||||||
chorus: "chorus=0.7:0.9:55:0.4:0.25:2",
|
|
||||||
chorus2d: "chorus=0.6:0.9:50|60:0.4|0.32:0.25|0.4:2|1.3",
|
|
||||||
chorus3d: "chorus=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3",
|
|
||||||
fadein: "afade=t=in:ss=0:d=10",
|
|
||||||
dim: `afftfilt="'real=re * (1-clip((b/nb)*b,0,1))':imag='im * (1-clip((b/nb)*b,0,1))'"`,
|
|
||||||
earrape: "channelsplit,sidechaingate=level_in=64"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
static get(name) {
|
|
||||||
return this.filters[name];
|
|
||||||
}
|
|
||||||
static has(name) {
|
|
||||||
return name in this.filters;
|
|
||||||
}
|
|
||||||
static *[Symbol.iterator]() {
|
|
||||||
for (const [k, v] of Object.entries(this.filters)) {
|
|
||||||
yield { name: k, value: v };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
static get names() {
|
|
||||||
return Object.keys(this.filters);
|
|
||||||
}
|
|
||||||
// @ts-expect-error AudioFilters.length
|
|
||||||
static get length() {
|
|
||||||
return this.names.length;
|
|
||||||
}
|
|
||||||
static toString() {
|
|
||||||
return this.names.map((m) => this[m]).join(","); // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Create ffmpeg args from the specified filters name
|
|
||||||
* @param filter The filter name
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
static create(filters) {
|
|
||||||
if (!filters || !Array.isArray(filters))
|
|
||||||
return this.toString();
|
|
||||||
return filters
|
|
||||||
.filter((predicate) => typeof predicate === "string")
|
|
||||||
.map((m) => this.get(m))
|
|
||||||
.join(",");
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Defines audio filter
|
|
||||||
* @param filterName The name of the filter
|
|
||||||
* @param value The ffmpeg args
|
|
||||||
*/
|
|
||||||
static define(filterName, value) {
|
|
||||||
this.filters[filterName] = value;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Defines multiple audio filters
|
|
||||||
* @param filtersArray Array of filters containing the filter name and ffmpeg args
|
|
||||||
*/
|
|
||||||
static defineBulk(filtersArray) {
|
|
||||||
filtersArray.forEach((arr) => this.define(arr.name, arr.value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.AudioFilters = AudioFilters;
|
|
||||||
exports.default = AudioFilters;
|
|
16
helpers/Music/dist/utils/FFmpegStream.d.ts
vendored
16
helpers/Music/dist/utils/FFmpegStream.d.ts
vendored
|
@ -1,16 +0,0 @@
|
||||||
/// <reference types="node" />
|
|
||||||
import type { Duplex, Readable } from "stream";
|
|
||||||
export interface FFmpegStreamOptions {
|
|
||||||
fmt?: string;
|
|
||||||
encoderArgs?: string[];
|
|
||||||
seek?: number;
|
|
||||||
skip?: boolean;
|
|
||||||
}
|
|
||||||
export declare function FFMPEG_ARGS_STRING(stream: string, fmt?: string): string[];
|
|
||||||
export declare function FFMPEG_ARGS_PIPED(fmt?: string): string[];
|
|
||||||
/**
|
|
||||||
* Creates FFmpeg stream
|
|
||||||
* @param stream The source stream
|
|
||||||
* @param options FFmpeg stream options
|
|
||||||
*/
|
|
||||||
export declare function createFFmpegStream(stream: Readable | Duplex | string, options?: FFmpegStreamOptions): Readable | Duplex;
|
|
53
helpers/Music/dist/utils/FFmpegStream.js
vendored
53
helpers/Music/dist/utils/FFmpegStream.js
vendored
|
@ -1,53 +0,0 @@
|
||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.createFFmpegStream = exports.FFMPEG_ARGS_PIPED = exports.FFMPEG_ARGS_STRING = void 0;
|
|
||||||
const prism_media_1 = require("prism-media");
|
|
||||||
function FFMPEG_ARGS_STRING(stream, fmt) {
|
|
||||||
// prettier-ignore
|
|
||||||
return [
|
|
||||||
"-reconnect", "1",
|
|
||||||
"-reconnect_streamed", "1",
|
|
||||||
"-reconnect_delay_max", "5",
|
|
||||||
"-i", stream,
|
|
||||||
"-analyzeduration", "0",
|
|
||||||
"-loglevel", "0",
|
|
||||||
"-f", `${typeof fmt === "string" ? fmt : "s16le"}`,
|
|
||||||
"-ar", "48000",
|
|
||||||
"-ac", "2"
|
|
||||||
];
|
|
||||||
}
|
|
||||||
exports.FFMPEG_ARGS_STRING = FFMPEG_ARGS_STRING;
|
|
||||||
function FFMPEG_ARGS_PIPED(fmt) {
|
|
||||||
// prettier-ignore
|
|
||||||
return [
|
|
||||||
"-analyzeduration", "0",
|
|
||||||
"-loglevel", "0",
|
|
||||||
"-f", `${typeof fmt === "string" ? fmt : "s16le"}`,
|
|
||||||
"-ar", "48000",
|
|
||||||
"-ac", "2"
|
|
||||||
];
|
|
||||||
}
|
|
||||||
exports.FFMPEG_ARGS_PIPED = FFMPEG_ARGS_PIPED;
|
|
||||||
/**
|
|
||||||
* Creates FFmpeg stream
|
|
||||||
* @param stream The source stream
|
|
||||||
* @param options FFmpeg stream options
|
|
||||||
*/
|
|
||||||
function createFFmpegStream(stream, options) {
|
|
||||||
if (options.skip && typeof stream !== "string")
|
|
||||||
return stream;
|
|
||||||
options ?? (options = {});
|
|
||||||
const args = typeof stream === "string" ? FFMPEG_ARGS_STRING(stream, options.fmt) : FFMPEG_ARGS_PIPED(options.fmt);
|
|
||||||
if (!Number.isNaN(options.seek))
|
|
||||||
args.unshift("-ss", String(options.seek));
|
|
||||||
if (Array.isArray(options.encoderArgs))
|
|
||||||
args.push(...options.encoderArgs);
|
|
||||||
const transcoder = new prism_media_1.FFmpeg({ shell: false, args });
|
|
||||||
transcoder.on("close", () => transcoder.destroy());
|
|
||||||
if (typeof stream !== "string") {
|
|
||||||
stream.on("error", () => transcoder.destroy());
|
|
||||||
stream.pipe(transcoder);
|
|
||||||
}
|
|
||||||
return transcoder;
|
|
||||||
}
|
|
||||||
exports.createFFmpegStream = createFFmpegStream;
|
|
20
helpers/Music/dist/utils/QueryResolver.d.ts
vendored
20
helpers/Music/dist/utils/QueryResolver.d.ts
vendored
|
@ -1,20 +0,0 @@
|
||||||
import { QueryType } from "../types/types";
|
|
||||||
declare class QueryResolver {
|
|
||||||
/**
|
|
||||||
* Query resolver
|
|
||||||
*/
|
|
||||||
private constructor();
|
|
||||||
/**
|
|
||||||
* Resolves the given search query
|
|
||||||
* @param {string} query The query
|
|
||||||
* @returns {QueryType}
|
|
||||||
*/
|
|
||||||
static resolve(query: string): Promise<QueryType>;
|
|
||||||
/**
|
|
||||||
* Parses vimeo id from url
|
|
||||||
* @param {string} query The query
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
static getVimeoID(query: string): Promise<string>;
|
|
||||||
}
|
|
||||||
export { QueryResolver };
|
|
66
helpers/Music/dist/utils/QueryResolver.js
vendored
66
helpers/Music/dist/utils/QueryResolver.js
vendored
|
@ -1,66 +0,0 @@
|
||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.QueryResolver = void 0;
|
|
||||||
const tslib_1 = require("tslib");
|
|
||||||
const types_1 = require("../types/types");
|
|
||||||
const play_dl_1 = tslib_1.__importDefault(require("play-dl"));
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// scary things below *sigh*
|
|
||||||
const spotifySongRegex = /https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:track\/|\?uri=spotify:track:)((\w|-){22})/;
|
|
||||||
const spotifyPlaylistRegex = /https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:playlist\/|\?uri=spotify:playlist:)((\w|-){22})/;
|
|
||||||
const spotifyAlbumRegex = /https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:album\/|\?uri=spotify:album:)((\w|-){22})/;
|
|
||||||
const vimeoRegex = /(http|https)?:\/\/(www\.|player\.)?vimeo\.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|video\/|)(\d+)(?:|\/\?)/;
|
|
||||||
const facebookRegex = /(https?:\/\/)(www\.|m\.)?(facebook|fb).com\/.*\/videos\/.*/;
|
|
||||||
const reverbnationRegex = /https:\/\/(www.)?reverbnation.com\/(.+)\/song\/(.+)/;
|
|
||||||
const attachmentRegex = /^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/;
|
|
||||||
// scary things above *sigh*
|
|
||||||
class QueryResolver {
|
|
||||||
/**
|
|
||||||
* Query resolver
|
|
||||||
*/
|
|
||||||
constructor() { } // eslint-disable-line @typescript-eslint/no-empty-function
|
|
||||||
/**
|
|
||||||
* Resolves the given search query
|
|
||||||
* @param {string} query The query
|
|
||||||
* @returns {QueryType}
|
|
||||||
*/
|
|
||||||
static async resolve(query) {
|
|
||||||
if (await play_dl_1.default.so_validate(query) === "track")
|
|
||||||
return types_1.QueryType.SOUNDCLOUD_TRACK;
|
|
||||||
if (await play_dl_1.default.so_validate(query) === "playlist" || query.includes("/sets/"))
|
|
||||||
return types_1.QueryType.SOUNDCLOUD_PLAYLIST;
|
|
||||||
if (play_dl_1.default.yt_validate(query) === "playlist")
|
|
||||||
return types_1.QueryType.YOUTUBE_PLAYLIST;
|
|
||||||
if (play_dl_1.default.yt_validate(query) === "video")
|
|
||||||
return types_1.QueryType.YOUTUBE_VIDEO;
|
|
||||||
if (spotifySongRegex.test(query))
|
|
||||||
return types_1.QueryType.SPOTIFY_SONG;
|
|
||||||
if (spotifyPlaylistRegex.test(query))
|
|
||||||
return types_1.QueryType.SPOTIFY_PLAYLIST;
|
|
||||||
if (spotifyAlbumRegex.test(query))
|
|
||||||
return types_1.QueryType.SPOTIFY_ALBUM;
|
|
||||||
if (vimeoRegex.test(query))
|
|
||||||
return types_1.QueryType.VIMEO;
|
|
||||||
if (facebookRegex.test(query))
|
|
||||||
return types_1.QueryType.FACEBOOK;
|
|
||||||
if (reverbnationRegex.test(query))
|
|
||||||
return types_1.QueryType.REVERBNATION;
|
|
||||||
if (attachmentRegex.test(query))
|
|
||||||
return types_1.QueryType.ARBITRARY;
|
|
||||||
return types_1.QueryType.YOUTUBE_SEARCH;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Parses vimeo id from url
|
|
||||||
* @param {string} query The query
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
static async getVimeoID(query) {
|
|
||||||
return await QueryResolver.resolve(query) === types_1.QueryType.VIMEO
|
|
||||||
? query
|
|
||||||
.split("/")
|
|
||||||
.filter((x) => !!x)
|
|
||||||
.pop()
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.QueryResolver = QueryResolver;
|
|
53
helpers/Music/dist/utils/Util.d.ts
vendored
53
helpers/Music/dist/utils/Util.d.ts
vendored
|
@ -1,53 +0,0 @@
|
||||||
import { StageChannel, VoiceChannel } from "discord.js";
|
|
||||||
import { TimeData } from "../types/types";
|
|
||||||
declare class Util {
|
|
||||||
/**
|
|
||||||
* Utils
|
|
||||||
*/
|
|
||||||
private constructor();
|
|
||||||
/**
|
|
||||||
* Creates duration string
|
|
||||||
* @param {object} durObj The duration object
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
static durationString(durObj: Record<string, number>): string;
|
|
||||||
/**
|
|
||||||
* Parses milliseconds to consumable time object
|
|
||||||
* @param {number} milliseconds The time in ms
|
|
||||||
* @returns {TimeData}
|
|
||||||
*/
|
|
||||||
static parseMS(milliseconds: number): TimeData;
|
|
||||||
/**
|
|
||||||
* Builds time code
|
|
||||||
* @param {TimeData} duration The duration object
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
static buildTimeCode(duration: TimeData): string;
|
|
||||||
/**
|
|
||||||
* Picks last item of the given array
|
|
||||||
* @param {any[]} arr The array
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
static last<T = any>(arr: T[]): T;
|
|
||||||
/**
|
|
||||||
* Checks if the voice channel is empty
|
|
||||||
* @param {VoiceChannel|StageChannel} channel The voice channel
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
static isVoiceEmpty(channel: VoiceChannel | StageChannel): boolean;
|
|
||||||
/**
|
|
||||||
* Safer require
|
|
||||||
* @param {string} id Node require id
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
static require(id: string): any;
|
|
||||||
/**
|
|
||||||
* Asynchronous timeout
|
|
||||||
* @param {number} time The time in ms to wait
|
|
||||||
* @returns {Promise<unknown>}
|
|
||||||
*/
|
|
||||||
static wait(time: number): Promise<unknown>;
|
|
||||||
static noop(): void;
|
|
||||||
static getFetch(): Promise<any>;
|
|
||||||
}
|
|
||||||
export { Util };
|
|
133
helpers/Music/dist/utils/Util.js
vendored
133
helpers/Music/dist/utils/Util.js
vendored
|
@ -1,133 +0,0 @@
|
||||||
"use strict";
|
|
||||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
||||||
if (k2 === undefined) k2 = k;
|
|
||||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
||||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
||||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
||||||
}
|
|
||||||
Object.defineProperty(o, k2, desc);
|
|
||||||
}) : (function(o, m, k, k2) {
|
|
||||||
if (k2 === undefined) k2 = k;
|
|
||||||
o[k2] = m[k];
|
|
||||||
}));
|
|
||||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
||||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
||||||
}) : function(o, v) {
|
|
||||||
o["default"] = v;
|
|
||||||
});
|
|
||||||
var __importStar = (this && this.__importStar) || function (mod) {
|
|
||||||
if (mod && mod.__esModule) return mod;
|
|
||||||
var result = {};
|
|
||||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
||||||
__setModuleDefault(result, mod);
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.Util = void 0;
|
|
||||||
class Util {
|
|
||||||
/**
|
|
||||||
* Utils
|
|
||||||
*/
|
|
||||||
constructor() { } // eslint-disable-line @typescript-eslint/no-empty-function
|
|
||||||
/**
|
|
||||||
* Creates duration string
|
|
||||||
* @param {object} durObj The duration object
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
static durationString(durObj) {
|
|
||||||
return Object.values(durObj)
|
|
||||||
.map((m) => (isNaN(m) ? 0 : m))
|
|
||||||
.join(":");
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Parses milliseconds to consumable time object
|
|
||||||
* @param {number} milliseconds The time in ms
|
|
||||||
* @returns {TimeData}
|
|
||||||
*/
|
|
||||||
static parseMS(milliseconds) {
|
|
||||||
const round = milliseconds > 0 ? Math.floor : Math.ceil;
|
|
||||||
return {
|
|
||||||
days: round(milliseconds / 86400000),
|
|
||||||
hours: round(milliseconds / 3600000) % 24,
|
|
||||||
minutes: round(milliseconds / 60000) % 60,
|
|
||||||
seconds: round(milliseconds / 1000) % 60
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Builds time code
|
|
||||||
* @param {TimeData} duration The duration object
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
static buildTimeCode(duration) {
|
|
||||||
const items = Object.keys(duration);
|
|
||||||
const required = ["days", "hours", "minutes", "seconds"];
|
|
||||||
const parsed = items.filter((x) => required.includes(x)).map((m) => duration[m]);
|
|
||||||
const final = parsed
|
|
||||||
.slice(parsed.findIndex((x) => x !== 0))
|
|
||||||
.map((x) => x.toString().padStart(2, "0"))
|
|
||||||
.join(":");
|
|
||||||
return final.length <= 3 ? `0:${final.padStart(2, "0") || 0}` : final;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Picks last item of the given array
|
|
||||||
* @param {any[]} arr The array
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
static last(arr) {
|
|
||||||
if (!Array.isArray(arr))
|
|
||||||
return;
|
|
||||||
return arr[arr.length - 1];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Checks if the voice channel is empty
|
|
||||||
* @param {VoiceChannel|StageChannel} channel The voice channel
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
static isVoiceEmpty(channel) {
|
|
||||||
return channel.members.filter((member) => !member.user.bot).size === 0;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Safer require
|
|
||||||
* @param {string} id Node require id
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
static require(id) {
|
|
||||||
try {
|
|
||||||
return require(id);
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Asynchronous timeout
|
|
||||||
* @param {number} time The time in ms to wait
|
|
||||||
* @returns {Promise<unknown>}
|
|
||||||
*/
|
|
||||||
static wait(time) {
|
|
||||||
return new Promise((r) => setTimeout(r, time).unref());
|
|
||||||
}
|
|
||||||
static noop() { } // eslint-disable-line @typescript-eslint/no-empty-function
|
|
||||||
static async getFetch() {
|
|
||||||
if ("fetch" in globalThis)
|
|
||||||
return globalThis.fetch;
|
|
||||||
for (const lib of ["undici", "node-fetch"]) {
|
|
||||||
try {
|
|
||||||
return await Promise.resolve().then(() => __importStar(require(lib))).then((res) => res.fetch || res.default?.fetch || res.default);
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
const res = require(lib);
|
|
||||||
if (res)
|
|
||||||
return res.fetch || res.default?.fetch || res.default;
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
// no?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.Util = Util;
|
|
4577
helpers/Music/package-lock.json
generated
4577
helpers/Music/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,61 +0,0 @@
|
||||||
{
|
|
||||||
"name": "music-player",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "music player",
|
|
||||||
"main": "dist/index.js",
|
|
||||||
"types": "dist/index.d.ts",
|
|
||||||
"files": [
|
|
||||||
"dist/"
|
|
||||||
],
|
|
||||||
"module": "dist/index.mjs",
|
|
||||||
"exports": {
|
|
||||||
".": {
|
|
||||||
"require": "./dist/index.js",
|
|
||||||
"import": "./dist/index.mjs"
|
|
||||||
},
|
|
||||||
"./smoothVolume": "./dist/smoothVolume.js",
|
|
||||||
"./src/*": "./dist/*",
|
|
||||||
"./dist/*": "./dist/*"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"dev": "cd examples/test && ts-node index.ts",
|
|
||||||
"build": "rimraf dist && tsc && npm run build:esm",
|
|
||||||
"build:check": "tsc --noEmit --incremental false",
|
|
||||||
"prepublishOnly": "rollup-type-bundler -e stream",
|
|
||||||
"build:esm": "gen-esm-wrapper ./dist/index.js ./dist/index.mjs",
|
|
||||||
"format": "prettier --write \"src/**/*.ts\"",
|
|
||||||
"docs": "typedoc --json docs/typedoc.json src/index.ts",
|
|
||||||
"postdocs": "node scripts/docgen.js",
|
|
||||||
"lint": "eslint src --ext .ts",
|
|
||||||
"prepare": "husky install",
|
|
||||||
"lint:fix": "eslint src --ext .ts --fix"
|
|
||||||
},
|
|
||||||
"author": "Jonny_Bro",
|
|
||||||
"license": "NO LICENSE",
|
|
||||||
"dependencies": {
|
|
||||||
"@discordjs/voice": "^0.11.0",
|
|
||||||
"libsodium-wrappers": "^0.7.10",
|
|
||||||
"spotify-url-info": "^3.1.2",
|
|
||||||
"tiny-typed-emitter": "^2.1.0",
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@discordjs/ts-docgen": "^0.4.1",
|
|
||||||
"@favware/rollup-type-bundler": "^1.0.10",
|
|
||||||
"@types/node": "^18.6.3",
|
|
||||||
"@types/ws": "^8.5.3",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^5.32.0",
|
|
||||||
"@typescript-eslint/parser": "^5.32.0",
|
|
||||||
"discord-api-types": "^0.37.0",
|
|
||||||
"discord.js": "^14.1.2",
|
|
||||||
"eslint": "^8.21.0",
|
|
||||||
"gen-esm-wrapper": "^1.1.3",
|
|
||||||
"husky": "^8.0.1",
|
|
||||||
"opusscript": "^0.0.8",
|
|
||||||
"prettier": "^2.7.1",
|
|
||||||
"rimraf": "^3.0.2",
|
|
||||||
"ts-node": "^10.9.1",
|
|
||||||
"typedoc": "^0.23.10",
|
|
||||||
"typescript": "^4.7.4"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,607 +0,0 @@
|
||||||
import { Client, Collection, GuildResolvable, Snowflake, User, VoiceState, IntentsBitField } from "discord.js";
|
|
||||||
import { TypedEmitter as EventEmitter } from "tiny-typed-emitter";
|
|
||||||
import { Queue } from "./Structures/Queue";
|
|
||||||
import { VoiceUtils } from "./VoiceInterface/VoiceUtils";
|
|
||||||
import { PlayerEvents, PlayerOptions, QueryType, SearchOptions, PlayerInitOptions, PlayerSearchResult, PlaylistInitData } from "./types/types";
|
|
||||||
import Track from "./Structures/Track";
|
|
||||||
import play, { SoundCloudPlaylist, YouTubePlayList } from "play-dl";
|
|
||||||
import Spotify from "spotify-url-info";
|
|
||||||
import { QueryResolver } from "./utils/QueryResolver";
|
|
||||||
import { Util } from "./utils/Util";
|
|
||||||
import { PlayerError, ErrorStatusCode } from "./Structures/PlayerError";
|
|
||||||
import { Playlist } from "./Structures/Playlist";
|
|
||||||
import { ExtractorModel } from "./Structures/ExtractorModel";
|
|
||||||
import { generateDependencyReport } from "@discordjs/voice";
|
|
||||||
|
|
||||||
class Player extends EventEmitter<PlayerEvents> {
|
|
||||||
public readonly client: Client;
|
|
||||||
public readonly options: PlayerInitOptions = {
|
|
||||||
autoRegisterExtractor: true,
|
|
||||||
connectionTimeout: 20000
|
|
||||||
};
|
|
||||||
public readonly queues = new Collection<Snowflake, Queue>();
|
|
||||||
public readonly voiceUtils = new VoiceUtils();
|
|
||||||
public readonly extractors = new Collection<string, ExtractorModel>();
|
|
||||||
public requiredEvents = ["error", "connectionError"] as string[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates new Discord Player
|
|
||||||
* @param {Client} client The Discord Client
|
|
||||||
* @param {PlayerInitOptions} [options] The player init options
|
|
||||||
*/
|
|
||||||
constructor(client: Client, options: PlayerInitOptions = {}) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The discord.js client
|
|
||||||
* @type {Client}
|
|
||||||
*/
|
|
||||||
this.client = client;
|
|
||||||
|
|
||||||
if (this.client?.options?.intents && !new IntentsBitField(this.client?.options?.intents).has(IntentsBitField.Flags.GuildVoiceStates)) {
|
|
||||||
throw new PlayerError('client is missing "GuildVoiceStates" intent');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The extractors collection
|
|
||||||
* @type {ExtractorModel}
|
|
||||||
*/
|
|
||||||
this.options = Object.assign(this.options, options);
|
|
||||||
|
|
||||||
this.client.on("voiceStateUpdate", this._handleVoiceState.bind(this));
|
|
||||||
|
|
||||||
if (this.options?.autoRegisterExtractor) {
|
|
||||||
let nv: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
|
|
||||||
if ((nv = Util.require("@discord-player/extractor"))) {
|
|
||||||
["Attachment", "Facebook", "Reverbnation", "Vimeo"].forEach((ext) => void this.use(ext, nv[ext]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles voice state update
|
|
||||||
* @param {VoiceState} oldState The old voice state
|
|
||||||
* @param {VoiceState} newState The new voice state
|
|
||||||
* @returns {void}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private _handleVoiceState(oldState: VoiceState, newState: VoiceState): void {
|
|
||||||
const queue = this.getQueue(oldState.guild.id);
|
|
||||||
if (!queue || !queue.connection) return;
|
|
||||||
|
|
||||||
if (oldState.channelId && !newState.channelId && newState.member.id === newState.guild.members.me.id) {
|
|
||||||
try {
|
|
||||||
queue.destroy();
|
|
||||||
} catch {
|
|
||||||
/* noop */
|
|
||||||
}
|
|
||||||
return void this.emit("botDisconnect", queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!oldState.channelId && newState.channelId && newState.member.id === newState.guild.members.me.id) {
|
|
||||||
if (!oldState.serverMute && newState.serverMute) {
|
|
||||||
// state.serverMute can be null
|
|
||||||
queue.setPaused(!!newState.serverMute);
|
|
||||||
} else if (!oldState.suppress && newState.suppress) {
|
|
||||||
// state.suppress can be null
|
|
||||||
queue.setPaused(!!newState.suppress);
|
|
||||||
if (newState.suppress) {
|
|
||||||
newState.guild.members.me.voice.setRequestToSpeak(true).catch(Util.noop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldState.channelId === newState.channelId && newState.member.id === newState.guild.members.me.id) {
|
|
||||||
if (!oldState.serverMute && newState.serverMute) {
|
|
||||||
// state.serverMute can be null
|
|
||||||
queue.setPaused(!!newState.serverMute);
|
|
||||||
} else if (!oldState.suppress && newState.suppress) {
|
|
||||||
// state.suppress can be null
|
|
||||||
queue.setPaused(!!newState.suppress);
|
|
||||||
if (newState.suppress) {
|
|
||||||
newState.guild.members.me.voice.setRequestToSpeak(true).catch(Util.noop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (queue.connection && !newState.channelId && oldState.channelId === queue.connection.channel.id) {
|
|
||||||
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;
|
|
||||||
if (queue.options.leaveOnEmpty) queue.destroy(true);
|
|
||||||
this.emit("channelEmpty", queue);
|
|
||||||
}, queue.options.leaveOnEmptyCooldown || 0).unref();
|
|
||||||
queue._cooldownsTimeout.set(`empty_${oldState.guild.id}`, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (queue.connection && newState.channelId && newState.channelId === queue.connection.channel.id) {
|
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldState.channelId && newState.channelId && oldState.channelId !== newState.channelId && newState.member.id === newState.guild.members.me.id) {
|
|
||||||
if (queue.connection && newState.member.id === newState.guild.members.me.id) queue.connection.channel = newState.channel;
|
|
||||||
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 {
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
if (queue.connection && !Util.isVoiceEmpty(queue.connection.channel)) return;
|
|
||||||
if (!this.queues.has(queue.guild.id)) return;
|
|
||||||
if (queue.options.leaveOnEmpty) queue.destroy(true);
|
|
||||||
this.emit("channelEmpty", queue);
|
|
||||||
}, queue.options.leaveOnEmptyCooldown || 0).unref();
|
|
||||||
queue._cooldownsTimeout.set(`empty_${oldState.guild.id}`, timeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a queue for a guild if not available, else returns existing queue
|
|
||||||
* @param {GuildResolvable} guild The guild
|
|
||||||
* @param {PlayerOptions} queueInitOptions Queue init options
|
|
||||||
* @returns {Queue}
|
|
||||||
*/
|
|
||||||
createQueue<T = unknown>(guild: GuildResolvable, queueInitOptions: PlayerOptions & { metadata?: T } = {}): Queue<T> {
|
|
||||||
guild = this.client.guilds.resolve(guild);
|
|
||||||
if (!guild) throw new PlayerError("Unknown Guild", ErrorStatusCode.UNKNOWN_GUILD);
|
|
||||||
if (this.queues.has(guild.id)) return this.queues.get(guild.id) as Queue<T>;
|
|
||||||
|
|
||||||
const _meta = queueInitOptions.metadata;
|
|
||||||
delete queueInitOptions["metadata"];
|
|
||||||
queueInitOptions.volumeSmoothness ??= 0.08;
|
|
||||||
const queue = new Queue(this, guild, queueInitOptions);
|
|
||||||
queue.metadata = _meta;
|
|
||||||
this.queues.set(guild.id, queue);
|
|
||||||
|
|
||||||
return queue as Queue<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the queue if available
|
|
||||||
* @param {GuildResolvable} guild The guild id
|
|
||||||
* @returns {Queue}
|
|
||||||
*/
|
|
||||||
getQueue<T = unknown>(guild: GuildResolvable) {
|
|
||||||
guild = this.client.guilds.resolve(guild);
|
|
||||||
if (!guild) throw new PlayerError("Unknown Guild", ErrorStatusCode.UNKNOWN_GUILD);
|
|
||||||
return this.queues.get(guild.id) as Queue<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a queue and returns deleted queue object
|
|
||||||
* @param {GuildResolvable} guild The guild id to remove
|
|
||||||
* @returns {Queue}
|
|
||||||
*/
|
|
||||||
deleteQueue<T = unknown>(guild: GuildResolvable) {
|
|
||||||
guild = this.client.guilds.resolve(guild);
|
|
||||||
if (!guild) throw new PlayerError("Unknown Guild", ErrorStatusCode.UNKNOWN_GUILD);
|
|
||||||
const prev = this.getQueue<T>(guild);
|
|
||||||
|
|
||||||
try {
|
|
||||||
prev.destroy();
|
|
||||||
} catch {} // eslint-disable-line no-empty
|
|
||||||
this.queues.delete(guild.id);
|
|
||||||
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} PlayerSearchResult
|
|
||||||
* @property {Playlist} [playlist] The playlist (if any)
|
|
||||||
* @property {Track[]} tracks The tracks
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Search tracks
|
|
||||||
* @param {string|Track} query The search query
|
|
||||||
* @param {SearchOptions} options The search options
|
|
||||||
* @returns {Promise<PlayerSearchResult>}
|
|
||||||
*/
|
|
||||||
async search(query: string | Track, options: SearchOptions): Promise<PlayerSearchResult> {
|
|
||||||
if (query instanceof Track) return { playlist: query.playlist || null, tracks: [query] };
|
|
||||||
if (!options) throw new PlayerError("DiscordPlayer#search needs search options!", ErrorStatusCode.INVALID_ARG_TYPE);
|
|
||||||
options.requestedBy = this.client.users.resolve(options.requestedBy);
|
|
||||||
if (!("searchEngine" in options)) options.searchEngine = QueryType.AUTO;
|
|
||||||
if (typeof options.searchEngine === "string" && this.extractors.has(options.searchEngine)) {
|
|
||||||
const extractor = this.extractors.get(options.searchEngine);
|
|
||||||
if (!extractor.validate(query)) return { playlist: null, tracks: [] };
|
|
||||||
const data = await extractor.handle(query);
|
|
||||||
if (data && data.data.length) {
|
|
||||||
const playlist = !data.playlist
|
|
||||||
? null
|
|
||||||
: new Playlist(this, {
|
|
||||||
...data.playlist,
|
|
||||||
tracks: []
|
|
||||||
});
|
|
||||||
|
|
||||||
const tracks = data.data.map(
|
|
||||||
(m) =>
|
|
||||||
new Track(this, {
|
|
||||||
...m,
|
|
||||||
requestedBy: options.requestedBy as User,
|
|
||||||
duration: Util.buildTimeCode(Util.parseMS(m.duration)),
|
|
||||||
playlist: playlist
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (playlist) playlist.tracks = tracks;
|
|
||||||
|
|
||||||
return { playlist: playlist, tracks: tracks };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
for (const [_, extractor] of this.extractors) {
|
|
||||||
if (options.blockExtractor) break;
|
|
||||||
if (!extractor.validate(query)) continue;
|
|
||||||
const data = await extractor.handle(query);
|
|
||||||
if (data && data.data.length) {
|
|
||||||
const playlist = !data.playlist
|
|
||||||
? null
|
|
||||||
: new Playlist(this, {
|
|
||||||
...data.playlist,
|
|
||||||
tracks: []
|
|
||||||
});
|
|
||||||
|
|
||||||
const tracks = data.data.map(
|
|
||||||
(m) =>
|
|
||||||
new Track(this, {
|
|
||||||
...m,
|
|
||||||
requestedBy: options.requestedBy as User,
|
|
||||||
duration: Util.buildTimeCode(Util.parseMS(m.duration)),
|
|
||||||
playlist: playlist
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (playlist) playlist.tracks = tracks;
|
|
||||||
|
|
||||||
return { playlist: playlist, tracks: tracks };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const qt = options.searchEngine === QueryType.AUTO ? await QueryResolver.resolve(query) : options.searchEngine;
|
|
||||||
switch (qt) {
|
|
||||||
case QueryType.YOUTUBE_VIDEO: {
|
|
||||||
const info = await play.video_info(query).catch(Util.noop);
|
|
||||||
if (!info) return { playlist: null, tracks: [] };
|
|
||||||
|
|
||||||
const track = new Track(this, {
|
|
||||||
title: info.video_details.title,
|
|
||||||
description: info.video_details.description,
|
|
||||||
author: info.video_details.channel?.name,
|
|
||||||
url: info.video_details.url,
|
|
||||||
requestedBy: options.requestedBy as User,
|
|
||||||
thumbnail: Util.last(info.video_details.thumbnails)?.url,
|
|
||||||
views: info.video_details.views || 0,
|
|
||||||
duration: Util.buildTimeCode(Util.parseMS(info.video_details.durationInSec * 1000)),
|
|
||||||
source: "youtube",
|
|
||||||
raw: info
|
|
||||||
});
|
|
||||||
|
|
||||||
return { playlist: null, tracks: [track] };
|
|
||||||
}
|
|
||||||
case QueryType.YOUTUBE_SEARCH: {
|
|
||||||
const videos = await play.search(query, {
|
|
||||||
limit: 10,
|
|
||||||
source: { youtube: "video" }
|
|
||||||
}).catch(Util.noop);
|
|
||||||
if (!videos) return { playlist: null, tracks: [] };
|
|
||||||
|
|
||||||
const tracks = videos.map(m => {
|
|
||||||
(m as any).source = "youtube"; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
return new Track(this, {
|
|
||||||
title: m.title,
|
|
||||||
description: m.description,
|
|
||||||
author: m.channel?.name,
|
|
||||||
url: m.url,
|
|
||||||
requestedBy: options.requestedBy as User,
|
|
||||||
thumbnail: Util.last(m.thumbnails).url,
|
|
||||||
views: m.views,
|
|
||||||
duration: m.durationRaw,
|
|
||||||
source: "youtube",
|
|
||||||
raw: m
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return { playlist: null, tracks, searched: true };
|
|
||||||
}
|
|
||||||
case QueryType.SOUNDCLOUD_TRACK:
|
|
||||||
case QueryType.SOUNDCLOUD_SEARCH: {
|
|
||||||
const result = await QueryResolver.resolve(query) === QueryType.SOUNDCLOUD_TRACK ? [{ url: query }] : await play.search(query, {
|
|
||||||
limit: 5,
|
|
||||||
source: { soundcloud: "tracks" }
|
|
||||||
}).catch(() => []);
|
|
||||||
if (!result || !result.length) return { playlist: null, tracks: [] };
|
|
||||||
const res: Track[] = [];
|
|
||||||
|
|
||||||
for (const r of result) {
|
|
||||||
const trackInfo = await play.soundcloud(r.url).catch(Util.noop);
|
|
||||||
if (!trackInfo) continue;
|
|
||||||
|
|
||||||
const track = new Track(this, {
|
|
||||||
title: trackInfo.name,
|
|
||||||
url: trackInfo.url,
|
|
||||||
duration: Util.buildTimeCode(Util.parseMS(trackInfo.durationInMs)),
|
|
||||||
description: "",
|
|
||||||
thumbnail: trackInfo.user.thumbnail,
|
|
||||||
views: 0,
|
|
||||||
author: trackInfo.user.name,
|
|
||||||
requestedBy: options.requestedBy,
|
|
||||||
source: "soundcloud",
|
|
||||||
engine: trackInfo
|
|
||||||
});
|
|
||||||
|
|
||||||
res.push(track);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { playlist: null, tracks: res };
|
|
||||||
}
|
|
||||||
case QueryType.SPOTIFY_SONG: {
|
|
||||||
const spotifyData = await Spotify(await Util.getFetch())
|
|
||||||
.getData(query)
|
|
||||||
.catch(Util.noop);
|
|
||||||
if (!spotifyData) return { playlist: null, tracks: [] };
|
|
||||||
const spotifyTrack = new Track(this, {
|
|
||||||
title: spotifyData.name,
|
|
||||||
description: spotifyData.description ?? "",
|
|
||||||
author: spotifyData.artists[0]?.name ?? "Unknown Artist",
|
|
||||||
url: spotifyData.external_urls?.spotify ?? query,
|
|
||||||
thumbnail:
|
|
||||||
spotifyData.album?.images[0]?.url ?? spotifyData.preview_url?.length
|
|
||||||
? `https://i.scdn.co/image/${spotifyData.preview_url?.split("?cid=")[1]}`
|
|
||||||
: "https://www.scdn.co/i/_global/twitter_card-default.jpg",
|
|
||||||
duration: Util.buildTimeCode(Util.parseMS(spotifyData.duration_ms)),
|
|
||||||
views: 0,
|
|
||||||
requestedBy: options.requestedBy,
|
|
||||||
source: "spotify"
|
|
||||||
});
|
|
||||||
|
|
||||||
return { playlist: null, tracks: [spotifyTrack] };
|
|
||||||
}
|
|
||||||
case QueryType.SPOTIFY_PLAYLIST:
|
|
||||||
case QueryType.SPOTIFY_ALBUM: {
|
|
||||||
const spotifyPlaylist = await Spotify(await Util.getFetch())
|
|
||||||
.getData(query)
|
|
||||||
.catch(Util.noop);
|
|
||||||
if (!spotifyPlaylist) return { playlist: null, tracks: [] };
|
|
||||||
|
|
||||||
const playlist = new Playlist(this, {
|
|
||||||
title: spotifyPlaylist.name ?? spotifyPlaylist.title,
|
|
||||||
description: spotifyPlaylist.description ?? "",
|
|
||||||
thumbnail: spotifyPlaylist.images[0]?.url ?? "https://www.scdn.co/i/_global/twitter_card-default.jpg",
|
|
||||||
type: spotifyPlaylist.type,
|
|
||||||
source: "spotify",
|
|
||||||
author:
|
|
||||||
spotifyPlaylist.type !== "playlist"
|
|
||||||
? {
|
|
||||||
name: spotifyPlaylist.artists[0]?.name ?? "Unknown Artist",
|
|
||||||
url: spotifyPlaylist.artists[0]?.external_urls?.spotify ?? null
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
name: spotifyPlaylist.owner?.display_name ?? spotifyPlaylist.owner?.id ?? "Unknown Artist",
|
|
||||||
url: spotifyPlaylist.owner?.external_urls?.spotify ?? null
|
|
||||||
},
|
|
||||||
tracks: [],
|
|
||||||
id: spotifyPlaylist.id,
|
|
||||||
url: spotifyPlaylist.external_urls?.spotify ?? query,
|
|
||||||
rawPlaylist: spotifyPlaylist
|
|
||||||
});
|
|
||||||
|
|
||||||
if (spotifyPlaylist.type !== "playlist") {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
playlist.tracks = spotifyPlaylist.tracks.items.map((m: any) => {
|
|
||||||
const data = new Track(this, {
|
|
||||||
title: m.name ?? "",
|
|
||||||
description: m.description ?? "",
|
|
||||||
author: m.artists[0]?.name ?? "Unknown Artist",
|
|
||||||
url: m.external_urls?.spotify ?? query,
|
|
||||||
thumbnail: spotifyPlaylist.images[0]?.url ?? "https://www.scdn.co/i/_global/twitter_card-default.jpg",
|
|
||||||
duration: Util.buildTimeCode(Util.parseMS(m.duration_ms)),
|
|
||||||
views: 0,
|
|
||||||
requestedBy: options.requestedBy as User,
|
|
||||||
playlist,
|
|
||||||
source: "spotify"
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}) as Track[];
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
playlist.tracks = spotifyPlaylist.tracks.items.map((m: any) => {
|
|
||||||
const data = new Track(this, {
|
|
||||||
title: m.track.name ?? "",
|
|
||||||
description: m.track.description ?? "",
|
|
||||||
author: m.track.artists?.[0]?.name ?? "Unknown Artist",
|
|
||||||
url: m.track.external_urls?.spotify ?? query,
|
|
||||||
thumbnail: m.track.album?.images?.[0]?.url ?? "https://www.scdn.co/i/_global/twitter_card-default.jpg",
|
|
||||||
duration: Util.buildTimeCode(Util.parseMS(m.track.duration_ms)),
|
|
||||||
views: 0,
|
|
||||||
requestedBy: options.requestedBy as User,
|
|
||||||
playlist,
|
|
||||||
source: "spotify"
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}) as Track[];
|
|
||||||
}
|
|
||||||
|
|
||||||
return { playlist: playlist, tracks: playlist.tracks };
|
|
||||||
}
|
|
||||||
case QueryType.SOUNDCLOUD_PLAYLIST: {
|
|
||||||
const data = await play.soundcloud(query).catch(Util.noop) as unknown as SoundCloudPlaylist;
|
|
||||||
if (!data) return { playlist: null, tracks: [] };
|
|
||||||
|
|
||||||
const res = new Playlist(this, {
|
|
||||||
title: data.name,
|
|
||||||
description: "",
|
|
||||||
thumbnail: "https://soundcloud.com/pwa-icon-192.png",
|
|
||||||
type: "playlist",
|
|
||||||
source: "soundcloud",
|
|
||||||
author: {
|
|
||||||
name: data.user.name ?? "Unknown Owner",
|
|
||||||
url: data.user.url
|
|
||||||
},
|
|
||||||
tracks: [],
|
|
||||||
id: `${data.id}`, // stringified
|
|
||||||
url: data.url,
|
|
||||||
rawPlaylist: data
|
|
||||||
});
|
|
||||||
|
|
||||||
const songs = await data.all_tracks();
|
|
||||||
for (const song of songs) {
|
|
||||||
const track = new Track(this, {
|
|
||||||
title: song.name,
|
|
||||||
description: "",
|
|
||||||
author: song.publisher.name ?? "Unknown Publisher",
|
|
||||||
url: song.url,
|
|
||||||
thumbnail: song.thumbnail,
|
|
||||||
duration: Util.buildTimeCode(Util.parseMS(song.durationInMs)),
|
|
||||||
views: 0,
|
|
||||||
requestedBy: options.requestedBy,
|
|
||||||
playlist: res,
|
|
||||||
source: "soundcloud",
|
|
||||||
engine: song
|
|
||||||
});
|
|
||||||
res.tracks.push(track);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { playlist: res, tracks: res.tracks };
|
|
||||||
}
|
|
||||||
case QueryType.YOUTUBE_PLAYLIST: {
|
|
||||||
const ytpl = await play.playlist_info(query, { incomplete: true }).catch(Util.noop) as unknown as YouTubePlayList;
|
|
||||||
if (!ytpl) return { playlist: null, tracks: [] };
|
|
||||||
|
|
||||||
const playlist: Playlist = new Playlist(this, {
|
|
||||||
title: ytpl.title,
|
|
||||||
thumbnail: ytpl.thumbnail as unknown as string,
|
|
||||||
description: "",
|
|
||||||
type: "playlist",
|
|
||||||
source: "youtube",
|
|
||||||
author: {
|
|
||||||
name: ytpl.channel.name,
|
|
||||||
url: ytpl.channel.url
|
|
||||||
},
|
|
||||||
tracks: [],
|
|
||||||
id: ytpl.id,
|
|
||||||
url: ytpl.url,
|
|
||||||
rawPlaylist: ytpl
|
|
||||||
});
|
|
||||||
|
|
||||||
const videos = await ytpl.all_videos();
|
|
||||||
playlist.tracks = videos.map(video =>
|
|
||||||
new Track(this, {
|
|
||||||
title: video.title,
|
|
||||||
description: video.description,
|
|
||||||
author: video.channel?.name,
|
|
||||||
url: video.url,
|
|
||||||
requestedBy: options.requestedBy as User,
|
|
||||||
thumbnail: Util.last(video.thumbnails).url,
|
|
||||||
views: video.views,
|
|
||||||
duration: video.durationRaw,
|
|
||||||
raw: video,
|
|
||||||
playlist: playlist,
|
|
||||||
source: "youtube"
|
|
||||||
}));
|
|
||||||
|
|
||||||
return { playlist: playlist, tracks: playlist.tracks };
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return { playlist: null, tracks: [] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers extractor
|
|
||||||
* @param {string} extractorName The extractor name
|
|
||||||
* @param {ExtractorModel|any} extractor The extractor object
|
|
||||||
* @param {boolean} [force=false] Overwrite existing extractor with this name (if available)
|
|
||||||
* @returns {ExtractorModel}
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
use(extractorName: string, extractor: ExtractorModel | any, force = false): ExtractorModel {
|
|
||||||
if (!extractorName) throw new PlayerError("Cannot use unknown extractor!", ErrorStatusCode.UNKNOWN_EXTRACTOR);
|
|
||||||
if (this.extractors.has(extractorName) && !force) return this.extractors.get(extractorName);
|
|
||||||
if (extractor instanceof ExtractorModel) {
|
|
||||||
this.extractors.set(extractorName, extractor);
|
|
||||||
return extractor;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const method of ["validate", "getInfo"]) {
|
|
||||||
if (typeof extractor[method] !== "function") throw new PlayerError("Invalid extractor data!", ErrorStatusCode.INVALID_EXTRACTOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
const model = new ExtractorModel(extractorName, extractor);
|
|
||||||
this.extractors.set(model.name, model);
|
|
||||||
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes registered extractor
|
|
||||||
* @param {string} extractorName The extractor name
|
|
||||||
* @returns {ExtractorModel}
|
|
||||||
*/
|
|
||||||
unuse(extractorName: string) {
|
|
||||||
if (!this.extractors.has(extractorName)) throw new PlayerError(`Cannot find extractor "${extractorName}"`, ErrorStatusCode.UNKNOWN_EXTRACTOR);
|
|
||||||
const prev = this.extractors.get(extractorName);
|
|
||||||
this.extractors.delete(extractorName);
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a report of the dependencies used by the `@discordjs/voice` module. Useful for debugging.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
scanDeps() {
|
|
||||||
const line = "-".repeat(50);
|
|
||||||
const depsReport = generateDependencyReport();
|
|
||||||
const extractorReport = this.extractors
|
|
||||||
.map((m) => {
|
|
||||||
return `${m.name} :: ${m.version || "0.1.0"}`;
|
|
||||||
})
|
|
||||||
.join("\n");
|
|
||||||
return `${depsReport}\n${line}\nLoaded Extractors:\n${extractorReport || "None"}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit<U extends keyof PlayerEvents>(eventName: U, ...args: Parameters<PlayerEvents[U]>): boolean {
|
|
||||||
if (this.requiredEvents.includes(eventName) && !super.eventNames().includes(eventName)) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error(...args);
|
|
||||||
process.emitWarning(`[DiscordPlayerWarning] Unhandled "${eventName}" event! Events ${this.requiredEvents.map((m) => `"${m}"`).join(", ")} must have event listeners!`);
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return super.emit(eventName, ...args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves queue
|
|
||||||
* @param {GuildResolvable|Queue} queueLike Queue like object
|
|
||||||
* @returns {Queue}
|
|
||||||
*/
|
|
||||||
resolveQueue<T>(queueLike: GuildResolvable | Queue): Queue<T> {
|
|
||||||
return this.getQueue(queueLike instanceof Queue ? queueLike.guild : queueLike);
|
|
||||||
}
|
|
||||||
|
|
||||||
*[Symbol.iterator]() {
|
|
||||||
yield* Array.from(this.queues.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates `Playlist` instance
|
|
||||||
* @param data The data to initialize a playlist
|
|
||||||
*/
|
|
||||||
createPlaylist(data: PlaylistInitData) {
|
|
||||||
return new Playlist(this, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Player };
|
|
|
@ -1,73 +0,0 @@
|
||||||
import { ExtractorModelData } from "../types/types";
|
|
||||||
|
|
||||||
class ExtractorModel {
|
|
||||||
name: string;
|
|
||||||
private _raw: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Model for raw Discord Player extractors
|
|
||||||
* @param {string} extractorName Name of the extractor
|
|
||||||
* @param {object} data Extractor object
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
constructor(extractorName: string, data: any) {
|
|
||||||
/**
|
|
||||||
* The extractor name
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.name = extractorName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The raw model
|
|
||||||
* @name ExtractorModel#_raw
|
|
||||||
* @type {any}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
Object.defineProperty(this, "_raw", { value: data, configurable: false, writable: false, enumerable: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method to handle requests from `Player.play()`
|
|
||||||
* @param {string} query Query to handle
|
|
||||||
* @returns {Promise<ExtractorModelData>}
|
|
||||||
*/
|
|
||||||
async handle(query: string): Promise<ExtractorModelData> {
|
|
||||||
const data = await this._raw.getInfo(query);
|
|
||||||
if (!data) return null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
playlist: data.playlist ?? null,
|
|
||||||
data:
|
|
||||||
(data.info as Omit<ExtractorModelData, "playlist">["data"])?.map((m) => ({
|
|
||||||
title: m.title as string,
|
|
||||||
duration: m.duration as number,
|
|
||||||
thumbnail: m.thumbnail as string,
|
|
||||||
engine: m.engine,
|
|
||||||
views: m.views as number,
|
|
||||||
author: m.author as string,
|
|
||||||
description: m.description as string,
|
|
||||||
url: m.url as string,
|
|
||||||
source: m.source || "arbitrary"
|
|
||||||
})) ?? []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method used by Discord Player to validate query with this extractor
|
|
||||||
* @param {string} query The query to validate
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
validate(query: string): boolean {
|
|
||||||
return Boolean(this._raw.validate(query));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The extractor version
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
get version(): string {
|
|
||||||
return this._raw.version ?? "0.0.0";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { ExtractorModel };
|
|
|
@ -1,53 +0,0 @@
|
||||||
export enum ErrorStatusCode {
|
|
||||||
STREAM_ERROR = "StreamError",
|
|
||||||
AUDIO_PLAYER_ERROR = "AudioPlayerError",
|
|
||||||
PLAYER_ERROR = "PlayerError",
|
|
||||||
NO_AUDIO_RESOURCE = "NoAudioResource",
|
|
||||||
UNKNOWN_GUILD = "UnknownGuild",
|
|
||||||
INVALID_ARG_TYPE = "InvalidArgType",
|
|
||||||
UNKNOWN_EXTRACTOR = "UnknownExtractor",
|
|
||||||
INVALID_EXTRACTOR = "InvalidExtractor",
|
|
||||||
INVALID_CHANNEL_TYPE = "InvalidChannelType",
|
|
||||||
INVALID_TRACK = "InvalidTrack",
|
|
||||||
UNKNOWN_REPEAT_MODE = "UnknownRepeatMode",
|
|
||||||
TRACK_NOT_FOUND = "TrackNotFound",
|
|
||||||
NO_CONNECTION = "NoConnection",
|
|
||||||
DESTROYED_QUEUE = "DestroyedQueue"
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PlayerError extends Error {
|
|
||||||
message: string;
|
|
||||||
statusCode: ErrorStatusCode;
|
|
||||||
createdAt = new Date();
|
|
||||||
|
|
||||||
constructor(message: string, code: ErrorStatusCode = ErrorStatusCode.PLAYER_ERROR) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.message = `[${code}] ${message}`;
|
|
||||||
this.statusCode = code;
|
|
||||||
this.name = code;
|
|
||||||
|
|
||||||
Error.captureStackTrace(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
get createdTimestamp() {
|
|
||||||
return this.createdAt.getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
valueOf() {
|
|
||||||
return this.statusCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
stack: this.stack,
|
|
||||||
code: this.statusCode,
|
|
||||||
message: this.message,
|
|
||||||
created: this.createdTimestamp
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
toString() {
|
|
||||||
return this.stack;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,138 +0,0 @@
|
||||||
import { Player } from "../Player";
|
|
||||||
import { Track } from "./Track";
|
|
||||||
import { PlaylistInitData, PlaylistJSON, TrackJSON, TrackSource } from "../types/types";
|
|
||||||
|
|
||||||
class Playlist {
|
|
||||||
public readonly player: Player;
|
|
||||||
public tracks: Track[];
|
|
||||||
public title: string;
|
|
||||||
public description: string;
|
|
||||||
public thumbnail: string;
|
|
||||||
public type: "album" | "playlist";
|
|
||||||
public source: TrackSource;
|
|
||||||
public author: {
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
};
|
|
||||||
public id: string;
|
|
||||||
public url: string;
|
|
||||||
public readonly rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Playlist constructor
|
|
||||||
* @param {Player} player The player
|
|
||||||
* @param {PlaylistInitData} data The data
|
|
||||||
*/
|
|
||||||
constructor(player: Player, data: PlaylistInitData) {
|
|
||||||
/**
|
|
||||||
* The player
|
|
||||||
* @name Playlist#player
|
|
||||||
* @type {Player}
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
this.player = player;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The tracks in this playlist
|
|
||||||
* @name Playlist#tracks
|
|
||||||
* @type {Track[]}
|
|
||||||
*/
|
|
||||||
this.tracks = data.tracks ?? [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The author of this playlist
|
|
||||||
* @name Playlist#author
|
|
||||||
* @type {object}
|
|
||||||
*/
|
|
||||||
this.author = data.author;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The description
|
|
||||||
* @name Playlist#description
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.description = data.description;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The thumbnail of this playlist
|
|
||||||
* @name Playlist#thumbnail
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.thumbnail = data.thumbnail;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The playlist type:
|
|
||||||
* - `album`
|
|
||||||
* - `playlist`
|
|
||||||
* @name Playlist#type
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.type = data.type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The source of this playlist:
|
|
||||||
* - `youtube`
|
|
||||||
* - `soundcloud`
|
|
||||||
* - `spotify`
|
|
||||||
* - `arbitrary`
|
|
||||||
* @name Playlist#source
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.source = data.source;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The playlist id
|
|
||||||
* @name Playlist#id
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.id = data.id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The playlist url
|
|
||||||
* @name Playlist#url
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.url = data.url;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The playlist title
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.title = data.title;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @name Playlist#rawPlaylist
|
|
||||||
* @type {any}
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
*[Symbol.iterator]() {
|
|
||||||
yield* this.tracks;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON representation of this playlist
|
|
||||||
* @param {boolean} [withTracks=true] If it should build json with tracks
|
|
||||||
* @returns {PlaylistJSON}
|
|
||||||
*/
|
|
||||||
toJSON(withTracks = true) {
|
|
||||||
const payload = {
|
|
||||||
id: this.id,
|
|
||||||
url: this.url,
|
|
||||||
title: this.title,
|
|
||||||
description: this.description,
|
|
||||||
thumbnail: this.thumbnail,
|
|
||||||
type: this.type,
|
|
||||||
source: this.source,
|
|
||||||
author: this.author,
|
|
||||||
tracks: [] as TrackJSON[]
|
|
||||||
};
|
|
||||||
|
|
||||||
if (withTracks) payload.tracks = this.tracks.map((m) => m.toJSON(true));
|
|
||||||
|
|
||||||
return payload as PlaylistJSON;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Playlist };
|
|
|
@ -1,776 +0,0 @@
|
||||||
import { Collection, Guild, StageChannel, VoiceChannel, SnowflakeUtil, GuildChannelResolvable, ChannelType } from "discord.js";
|
|
||||||
import { Player } from "../Player";
|
|
||||||
import { StreamDispatcher } from "../VoiceInterface/StreamDispatcher";
|
|
||||||
import Track from "./Track";
|
|
||||||
import { PlayerOptions, PlayerProgressbarOptions, PlayOptions, QueueFilters, QueueRepeatMode, TrackSource } from "../types/types";
|
|
||||||
import { AudioResource, StreamType } from "@discordjs/voice";
|
|
||||||
import play from "play-dl";
|
|
||||||
import { Util } from "../utils/Util";
|
|
||||||
import AudioFilters from "../utils/AudioFilters";
|
|
||||||
import { PlayerError, ErrorStatusCode } from "./PlayerError";
|
|
||||||
import type { Readable } from "stream";
|
|
||||||
import { VolumeTransformer } from "../VoiceInterface/VolumeTransformer";
|
|
||||||
import { createFFmpegStream } from "../utils/FFmpegStream";
|
|
||||||
|
|
||||||
class Queue<T = unknown> {
|
|
||||||
public readonly guild: Guild;
|
|
||||||
public readonly player: Player;
|
|
||||||
public connection: StreamDispatcher;
|
|
||||||
public tracks: Track[] = [];
|
|
||||||
public previousTracks: Track[] = [];
|
|
||||||
public options: PlayerOptions;
|
|
||||||
public playing = false;
|
|
||||||
public metadata?: T = null;
|
|
||||||
public repeatMode: QueueRepeatMode = 0;
|
|
||||||
public readonly id = SnowflakeUtil.generate().toString();
|
|
||||||
private _streamTime = 0;
|
|
||||||
public _cooldownsTimeout = new Collection<string, NodeJS.Timeout>();
|
|
||||||
private _activeFilters: any[] = []; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
private _filtersUpdate = false;
|
|
||||||
#lastVolume = 0;
|
|
||||||
#destroyed = false;
|
|
||||||
public onBeforeCreateStream: (track: Track, source: TrackSource, queue: Queue) => Promise<Readable | undefined> = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Queue constructor
|
|
||||||
* @param {Player} player The player that instantiated this queue
|
|
||||||
* @param {Guild} guild The guild that instantiated this queue
|
|
||||||
* @param {PlayerOptions} [options] Player options for the queue
|
|
||||||
*/
|
|
||||||
constructor(player: Player, guild: Guild, options: PlayerOptions = {}) {
|
|
||||||
/**
|
|
||||||
* The player that instantiated this queue
|
|
||||||
* @type {Player}
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
this.player = player;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The guild that instantiated this queue
|
|
||||||
* @type {Guild}
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
this.guild = guild;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The player options for this queue
|
|
||||||
* @type {PlayerOptions}
|
|
||||||
*/
|
|
||||||
this.options = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Queue repeat mode
|
|
||||||
* @type {QueueRepeatMode}
|
|
||||||
* @name Queue#repeatMode
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Queue metadata
|
|
||||||
* @type {any}
|
|
||||||
* @name Queue#metadata
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Previous tracks
|
|
||||||
* @type {Track[]}
|
|
||||||
* @name Queue#previousTracks
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Regular tracks
|
|
||||||
* @type {Track[]}
|
|
||||||
* @name Queue#tracks
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The connection
|
|
||||||
* @type {StreamDispatcher}
|
|
||||||
* @name Queue#connection
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The ID of this queue
|
|
||||||
* @type {Snowflake}
|
|
||||||
* @name Queue#id
|
|
||||||
*/
|
|
||||||
|
|
||||||
Object.assign(
|
|
||||||
this.options,
|
|
||||||
{
|
|
||||||
leaveOnEnd: true,
|
|
||||||
leaveOnStop: true,
|
|
||||||
leaveOnEmpty: true,
|
|
||||||
leaveOnEmptyCooldown: 1000,
|
|
||||||
autoSelfDeaf: true,
|
|
||||||
ytdlOptions: {
|
|
||||||
highWaterMark: 1 << 25
|
|
||||||
},
|
|
||||||
initialVolume: 100,
|
|
||||||
bufferingTimeout: 3000,
|
|
||||||
spotifyBridge: true,
|
|
||||||
disableVolume: false
|
|
||||||
} as PlayerOptions,
|
|
||||||
options
|
|
||||||
);
|
|
||||||
|
|
||||||
if ("onBeforeCreateStream" in this.options) this.onBeforeCreateStream = this.options.onBeforeCreateStream;
|
|
||||||
|
|
||||||
this.player.emit("debug", this, `Queue initialized:\n\n${this.player.scanDeps()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns current track
|
|
||||||
* @type {Track}
|
|
||||||
*/
|
|
||||||
get current() {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
return this.connection.audioResource?.metadata ?? this.tracks[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If this queue is destroyed
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
get destroyed() {
|
|
||||||
return this.#destroyed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns current track
|
|
||||||
* @returns {Track}
|
|
||||||
*/
|
|
||||||
nowPlaying() {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
return this.current;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connects to a voice channel
|
|
||||||
* @param {GuildChannelResolvable} channel The voice/stage channel
|
|
||||||
* @returns {Promise<Queue>}
|
|
||||||
*/
|
|
||||||
async connect(channel: GuildChannelResolvable) {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
const _channel = this.guild.channels.resolve(channel) as StageChannel | VoiceChannel;
|
|
||||||
if (![ChannelType.GuildStageVoice, ChannelType.GuildVoice].includes(_channel?.type))
|
|
||||||
throw new PlayerError(`Channel type must be GuildVoice or GuildStageVoice, got ${_channel?.type}!`, ErrorStatusCode.INVALID_ARG_TYPE);
|
|
||||||
const connection = await this.player.voiceUtils.connect(_channel, {
|
|
||||||
deaf: this.options.autoSelfDeaf
|
|
||||||
});
|
|
||||||
this.connection = connection;
|
|
||||||
|
|
||||||
if (_channel.type === ChannelType.GuildStageVoice) {
|
|
||||||
await _channel.guild.members.me.voice.setSuppressed(false).catch(async () => {
|
|
||||||
return await _channel.guild.members.me.voice.setRequestToSpeak(true).catch(Util.noop);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.connection.on("error", (err) => {
|
|
||||||
if (this.#watchDestroyed(false)) return;
|
|
||||||
this.player.emit("connectionError", this, err);
|
|
||||||
});
|
|
||||||
this.connection.on("debug", (msg) => {
|
|
||||||
if (this.#watchDestroyed(false)) return;
|
|
||||||
this.player.emit("debug", this, msg);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.player.emit("connectionCreate", this, this.connection);
|
|
||||||
|
|
||||||
this.connection.on("start", (resource) => {
|
|
||||||
if (this.#watchDestroyed(false)) return;
|
|
||||||
this.playing = true;
|
|
||||||
if (!this._filtersUpdate) this.player.emit("trackStart", this, resource?.metadata ?? this.current);
|
|
||||||
this._filtersUpdate = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.connection.on("finish", async (resource) => {
|
|
||||||
if (this.#watchDestroyed(false)) return;
|
|
||||||
this.playing = false;
|
|
||||||
if (this._filtersUpdate) return;
|
|
||||||
this._streamTime = 0;
|
|
||||||
if (resource?.metadata) this.previousTracks.push(resource.metadata);
|
|
||||||
|
|
||||||
this.player.emit("trackEnd", this, resource.metadata);
|
|
||||||
|
|
||||||
if (!this.tracks.length && this.repeatMode === QueueRepeatMode.OFF) {
|
|
||||||
if (this.options.leaveOnEnd) this.destroy();
|
|
||||||
this.player.emit("queueEnd", this);
|
|
||||||
} else if (!this.tracks.length && this.repeatMode === QueueRepeatMode.AUTOPLAY) {
|
|
||||||
this._handleAutoplay(Util.last(this.previousTracks));
|
|
||||||
} else {
|
|
||||||
if (this.repeatMode === QueueRepeatMode.TRACK) return void this.play(Util.last(this.previousTracks), { immediate: true });
|
|
||||||
if (this.repeatMode === QueueRepeatMode.QUEUE) this.tracks.push(Util.last(this.previousTracks));
|
|
||||||
const nextTrack = this.tracks.shift();
|
|
||||||
this.play(nextTrack, { immediate: true });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroys this queue
|
|
||||||
* @param {boolean} [disconnect=this.options.leaveOnStop] If it should leave on destroy
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
destroy(disconnect = this.options.leaveOnStop) {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
if (this.connection) this.connection.end();
|
|
||||||
if (disconnect) this.connection?.disconnect();
|
|
||||||
this.player.queues.delete(this.guild.id);
|
|
||||||
this.player.voiceUtils.cache.delete(this.guild.id);
|
|
||||||
this.#destroyed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Skips current track
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
skip() {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
if (!this.connection) return false;
|
|
||||||
this._filtersUpdate = false;
|
|
||||||
this.connection.end();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds single track to the queue
|
|
||||||
* @param {Track} track The track to add
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
addTrack(track: Track) {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
if (!(track instanceof Track)) throw new PlayerError("invalid track", ErrorStatusCode.INVALID_TRACK);
|
|
||||||
this.tracks.push(track);
|
|
||||||
this.player.emit("trackAdd", this, track);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds multiple tracks to the queue
|
|
||||||
* @param {Track[]} tracks Array of tracks to add
|
|
||||||
*/
|
|
||||||
addTracks(tracks: Track[]) {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
if (!tracks.every((y) => y instanceof Track)) throw new PlayerError("invalid track", ErrorStatusCode.INVALID_TRACK);
|
|
||||||
this.tracks.push(...tracks);
|
|
||||||
this.player.emit("tracksAdd", this, tracks);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets paused state
|
|
||||||
* @param {boolean} paused The paused state
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
setPaused(paused?: boolean) {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
if (!this.connection) return false;
|
|
||||||
return paused ? this.connection.pause(true) : this.connection.resume();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets bitrate
|
|
||||||
* @param {number|auto} bitrate bitrate to set
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
setBitrate(bitrate: number | "auto") {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
if (!this.connection?.audioResource?.encoder) return;
|
|
||||||
if (bitrate === "auto") bitrate = this.connection.channel?.bitrate ?? 64000;
|
|
||||||
this.connection.audioResource.encoder.setBitrate(bitrate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets volume
|
|
||||||
* @param {number} amount The volume amount
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
setVolume(amount: number) {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
if (!this.connection) return false;
|
|
||||||
this.#lastVolume = amount;
|
|
||||||
this.options.initialVolume = amount;
|
|
||||||
return this.connection.setVolume(amount);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Sets repeat mode
|
|
||||||
* @param {QueueRepeatMode} mode The repeat mode
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
setRepeatMode(mode: QueueRepeatMode) {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
if (![QueueRepeatMode.OFF, QueueRepeatMode.QUEUE, QueueRepeatMode.TRACK, QueueRepeatMode.AUTOPLAY].includes(mode))
|
|
||||||
throw new PlayerError(`Unknown repeat mode "${mode}"!`, ErrorStatusCode.UNKNOWN_REPEAT_MODE);
|
|
||||||
if (mode === this.repeatMode) return false;
|
|
||||||
this.repeatMode = mode;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current volume amount
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
get volume() {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
if (!this.connection) return 100;
|
|
||||||
return this.connection.volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
set volume(amount: number) {
|
|
||||||
this.setVolume(amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The stream time of this queue
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
get streamTime() {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
if (!this.connection) return 0;
|
|
||||||
const playbackTime = this._streamTime + this.connection.streamTime;
|
|
||||||
const NC = this._activeFilters.includes("nightcore") ? 1.25 : null;
|
|
||||||
const VW = this._activeFilters.includes("vaporwave") ? 0.8 : null;
|
|
||||||
|
|
||||||
if (NC && VW) return playbackTime * (NC + VW);
|
|
||||||
return NC ? playbackTime * NC : VW ? playbackTime * VW : playbackTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
set streamTime(time: number) {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
this.seek(time);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns enabled filters
|
|
||||||
* @returns {AudioFilters}
|
|
||||||
*/
|
|
||||||
getFiltersEnabled() {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
return AudioFilters.names.filter((x) => this._activeFilters.includes(x));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns disabled filters
|
|
||||||
* @returns {AudioFilters}
|
|
||||||
*/
|
|
||||||
getFiltersDisabled() {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
return AudioFilters.names.filter((x) => !this._activeFilters.includes(x));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets filters
|
|
||||||
* @param {QueueFilters} filters Queue filters
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async setFilters(filters?: QueueFilters) {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
if (!filters || !Object.keys(filters).length) {
|
|
||||||
// reset filters
|
|
||||||
const streamTime = this.streamTime;
|
|
||||||
this._activeFilters = [];
|
|
||||||
return await this.play(this.current, {
|
|
||||||
immediate: true,
|
|
||||||
filtersUpdate: true,
|
|
||||||
seek: streamTime,
|
|
||||||
encoderArgs: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const _filters: any[] = []; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
|
|
||||||
for (const filter in filters) {
|
|
||||||
if (filters[filter as keyof QueueFilters] === true) _filters.push(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._activeFilters.join("") === _filters.join("")) return;
|
|
||||||
|
|
||||||
const newFilters = AudioFilters.create(_filters).trim();
|
|
||||||
const streamTime = this.streamTime;
|
|
||||||
this._activeFilters = _filters;
|
|
||||||
|
|
||||||
return await this.play(this.current, {
|
|
||||||
immediate: true,
|
|
||||||
filtersUpdate: true,
|
|
||||||
seek: streamTime,
|
|
||||||
encoderArgs: !_filters.length ? undefined : ["-af", newFilters]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Seeks to the given time
|
|
||||||
* @param {number} position The position
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
async seek(position: number) {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
if (!this.playing || !this.current) return false;
|
|
||||||
if (position < 1) position = 0;
|
|
||||||
if (position >= this.current.durationMS) return this.skip();
|
|
||||||
|
|
||||||
await this.play(this.current, {
|
|
||||||
immediate: true,
|
|
||||||
filtersUpdate: true, // to stop events
|
|
||||||
seek: position
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plays previous track
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async back() {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
const prev = this.previousTracks[this.previousTracks.length - 2]; // because last item is the current track
|
|
||||||
if (!prev) throw new PlayerError("Could not find previous track", ErrorStatusCode.TRACK_NOT_FOUND);
|
|
||||||
|
|
||||||
return await this.play(prev, { immediate: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear this queue
|
|
||||||
*/
|
|
||||||
clear() {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
this.tracks = [];
|
|
||||||
this.previousTracks = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the player
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
stop() {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
return this.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shuffles this queue
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
shuffle() {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
if (!this.tracks.length || this.tracks.length < 2) return false;
|
|
||||||
|
|
||||||
for (let i = this.tracks.length - 1; i > 0; i--) {
|
|
||||||
const j = Math.floor(Math.random() * (i + 1));
|
|
||||||
[this.tracks[i], this.tracks[j]] = [this.tracks[j], this.tracks[i]];
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a track from the queue
|
|
||||||
* @param {Track|string|number} track The track to remove
|
|
||||||
* @returns {Track}
|
|
||||||
*/
|
|
||||||
remove(track: Track | string | number) {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
let trackFound: Track = null;
|
|
||||||
if (typeof track === "number") {
|
|
||||||
trackFound = this.tracks[track];
|
|
||||||
if (trackFound) {
|
|
||||||
this.tracks = this.tracks.filter((t) => t.id !== trackFound.id);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
trackFound = this.tracks.find((s) => s.id === (track instanceof Track ? track.id : track));
|
|
||||||
if (trackFound) {
|
|
||||||
this.tracks = this.tracks.filter((s) => s.id !== trackFound.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return trackFound;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the index of the specified track. If found, returns the track index else returns -1.
|
|
||||||
* @param {number|Track|string} track The track
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
getTrackPosition(track: number | Track | string) {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
if (typeof track === "number") return this.tracks[track] != null ? track : -1;
|
|
||||||
return this.tracks.findIndex((pred) => pred.id === (track instanceof Track ? track.id : track));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Jumps to particular track
|
|
||||||
* @param {Track|number} track The track
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
jump(track: Track | number): void {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
const foundTrack = this.remove(track);
|
|
||||||
if (!foundTrack) throw new PlayerError("Track not found", ErrorStatusCode.TRACK_NOT_FOUND);
|
|
||||||
|
|
||||||
this.tracks.splice(0, 0, foundTrack);
|
|
||||||
|
|
||||||
return void this.skip();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Jumps to particular track, removing other tracks on the way
|
|
||||||
* @param {Track|number} track The track
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
skipTo(track: Track | number): void {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
const trackIndex = this.getTrackPosition(track);
|
|
||||||
const removedTrack = this.remove(track);
|
|
||||||
if (!removedTrack) throw new PlayerError("Track not found", ErrorStatusCode.TRACK_NOT_FOUND);
|
|
||||||
|
|
||||||
this.tracks.splice(0, trackIndex, removedTrack);
|
|
||||||
|
|
||||||
return void this.skip();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts the given track to specified index
|
|
||||||
* @param {Track} track The track to insert
|
|
||||||
* @param {number} [index=0] The index where this track should be
|
|
||||||
*/
|
|
||||||
insert(track: Track, index = 0) {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
if (!track || !(track instanceof Track)) throw new PlayerError("track must be the instance of Track", ErrorStatusCode.INVALID_TRACK);
|
|
||||||
if (typeof index !== "number" || index < 0 || !Number.isFinite(index)) throw new PlayerError(`Invalid index "${index}"`, ErrorStatusCode.INVALID_ARG_TYPE);
|
|
||||||
|
|
||||||
this.tracks.splice(index, 0, track);
|
|
||||||
|
|
||||||
this.player.emit("trackAdd", this, track);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} PlayerTimestamp
|
|
||||||
* @property {string} current The current progress
|
|
||||||
* @property {string} end The total time
|
|
||||||
* @property {number} progress Progress in %
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns player stream timestamp
|
|
||||||
* @returns {PlayerTimestamp}
|
|
||||||
*/
|
|
||||||
getPlayerTimestamp() {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
const currentStreamTime = this.streamTime;
|
|
||||||
const totalTime = this.current.durationMS;
|
|
||||||
|
|
||||||
const currentTimecode = Util.buildTimeCode(Util.parseMS(currentStreamTime));
|
|
||||||
const endTimecode = Util.buildTimeCode(Util.parseMS(totalTime));
|
|
||||||
|
|
||||||
return {
|
|
||||||
current: currentTimecode,
|
|
||||||
end: endTimecode,
|
|
||||||
progress: Math.round((currentStreamTime / totalTime) * 100)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates progress bar string
|
|
||||||
* @param {PlayerProgressbarOptions} options The progress bar options
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
createProgressBar(options: PlayerProgressbarOptions = { timecodes: true }) {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
const length = typeof options.length === "number" ? (options.length <= 0 || options.length === Infinity ? 15 : options.length) : 15;
|
|
||||||
|
|
||||||
const index = Math.round((this.streamTime / this.current.durationMS) * length);
|
|
||||||
const indicator = typeof options.indicator === "string" && options.indicator.length > 0 ? options.indicator : "🔘";
|
|
||||||
const line = typeof options.line === "string" && options.line.length > 0 ? options.line : "▬";
|
|
||||||
|
|
||||||
if (index >= 1 && index <= length) {
|
|
||||||
const bar = line.repeat(length - 1).split("");
|
|
||||||
bar.splice(index, 0, indicator);
|
|
||||||
if (options.timecodes) {
|
|
||||||
const timestamp = this.getPlayerTimestamp();
|
|
||||||
return `${timestamp.current} ┃ ${bar.join("")} ┃ ${timestamp.end}`;
|
|
||||||
} else {
|
|
||||||
return `${bar.join("")}`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (options.timecodes) {
|
|
||||||
const timestamp = this.getPlayerTimestamp();
|
|
||||||
return `${timestamp.current} ┃ ${indicator}${line.repeat(length - 1)} ┃ ${timestamp.end}`;
|
|
||||||
} else {
|
|
||||||
return `${indicator}${line.repeat(length - 1)}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Total duration
|
|
||||||
* @type {Number}
|
|
||||||
*/
|
|
||||||
get totalTime(): number {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
return this.tracks.length > 0 ? this.tracks.map((t) => t.durationMS).reduce((p, c) => p + c) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Play stream in a voice/stage channel
|
|
||||||
* @param {Track} [src] The track to play (if empty, uses first track from the queue)
|
|
||||||
* @param {PlayOptions} [options] The options
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async play(src?: Track, options: PlayOptions = {}): Promise<void> {
|
|
||||||
if (this.#watchDestroyed(false)) return;
|
|
||||||
if (!this.connection || !this.connection.voiceConnection) throw new PlayerError("Voice connection is not available, use <Queue>.connect()!", ErrorStatusCode.NO_CONNECTION);
|
|
||||||
if (src && (this.playing || this.tracks.length) && !options.immediate) return this.addTrack(src);
|
|
||||||
const track = options.filtersUpdate && !options.immediate ? src || this.current : src ?? this.tracks.shift();
|
|
||||||
if (!track) return;
|
|
||||||
|
|
||||||
this.player.emit("debug", this, "Received play request");
|
|
||||||
|
|
||||||
if (!options.filtersUpdate) {
|
|
||||||
this.previousTracks = this.previousTracks.filter((x) => x.id !== track.id);
|
|
||||||
this.previousTracks.push(track);
|
|
||||||
}
|
|
||||||
|
|
||||||
let stream = null;
|
|
||||||
const hasCustomDownloader = typeof this.onBeforeCreateStream === "function";
|
|
||||||
|
|
||||||
if (["youtube", "spotify"].includes(track.raw.source)) {
|
|
||||||
let spotifyResolved = false;
|
|
||||||
if (this.options.spotifyBridge && track.raw.source === "spotify" && !track.raw.engine) {
|
|
||||||
track.raw.engine = await play.search(`${track.author} ${track.title}`, { source: { youtube: "video" } })
|
|
||||||
.then(res => res[0].url)
|
|
||||||
.catch(() => null);
|
|
||||||
spotifyResolved = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = track.raw.source === "spotify" ? track.raw.engine : track.url;
|
|
||||||
if (!url) return void this.play(this.tracks.shift(), { immediate: true });
|
|
||||||
|
|
||||||
if (hasCustomDownloader) {
|
|
||||||
stream = (await this.onBeforeCreateStream(track, spotifyResolved ? "youtube" : track.raw.source, this)) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!stream) {
|
|
||||||
stream = (await play.stream(url, { discordPlayerCompatibility: true })).stream;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const arbitraryStream = (hasCustomDownloader && (await this.onBeforeCreateStream(track, track.raw.source || track.raw.engine, this))) || null;
|
|
||||||
stream =
|
|
||||||
arbitraryStream || (track.raw.source === "soundcloud" && typeof track.raw.engine?.downloadProgressive === "function")
|
|
||||||
? await track.raw.engine.downloadProgressive()
|
|
||||||
: typeof track.raw.engine === "function"
|
|
||||||
? await track.raw.engine()
|
|
||||||
: track.raw.engine;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ffmpegStream = createFFmpegStream(stream, {
|
|
||||||
encoderArgs: options.encoderArgs || [],
|
|
||||||
seek: options.seek ? options.seek / 1000 : 0,
|
|
||||||
fmt: "s16le"
|
|
||||||
}).on("error", (err) => {
|
|
||||||
if (!`${err}`.toLowerCase().includes("premature close")) this.player.emit("error", this, err);
|
|
||||||
});
|
|
||||||
|
|
||||||
const resource: AudioResource<Track> = this.connection.createStream(ffmpegStream, {
|
|
||||||
type: StreamType.Raw,
|
|
||||||
data: track,
|
|
||||||
disableVolume: Boolean(this.options.disableVolume)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (options.seek) this._streamTime = options.seek;
|
|
||||||
this._filtersUpdate = options.filtersUpdate;
|
|
||||||
|
|
||||||
const volumeTransformer = resource.volume as VolumeTransformer;
|
|
||||||
if (volumeTransformer && typeof this.options.initialVolume === "number") Reflect.set(volumeTransformer, "volume", Math.pow(this.options.initialVolume / 100, 1.660964));
|
|
||||||
if (volumeTransformer?.hasSmoothness && typeof this.options.volumeSmoothness === "number") {
|
|
||||||
if (typeof volumeTransformer.setSmoothness === "function") volumeTransformer.setSmoothness(this.options.volumeSmoothness || 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.connection.playStream(resource);
|
|
||||||
}, this.#getBufferingTimeout()).unref();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Private method to handle autoplay
|
|
||||||
* @param {Track} track The source track to find its similar track for autoplay
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private async _handleAutoplay(track: Track): Promise<void> {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
if (!track || ![track.source, track.raw?.source].includes("youtube")) {
|
|
||||||
if (this.options.leaveOnEnd) this.destroy();
|
|
||||||
return void this.player.emit("queueEnd", this);
|
|
||||||
}
|
|
||||||
const info = await play.video_info(track.url)
|
|
||||||
.catch(Util.noop);
|
|
||||||
if (!info) {
|
|
||||||
if (this.options.leaveOnEnd) this.destroy();
|
|
||||||
return void this.player.emit("queueEnd", this);
|
|
||||||
}
|
|
||||||
|
|
||||||
const randomRelated = await play.video_info(info.related_videos[0]);
|
|
||||||
const nextTrack = new Track(this.player, {
|
|
||||||
title: randomRelated.video_details.title,
|
|
||||||
url: randomRelated.video_details.url,
|
|
||||||
duration: randomRelated.video_details.durationRaw ? Util.buildTimeCode(Util.parseMS(randomRelated.video_details.durationInSec * 1000)) : "0:00",
|
|
||||||
description: "",
|
|
||||||
thumbnail: Util.last(randomRelated.video_details.thumbnails).url,
|
|
||||||
views: randomRelated.video_details.views,
|
|
||||||
author: randomRelated.video_details.channel.name,
|
|
||||||
requestedBy: track.requestedBy,
|
|
||||||
source: "youtube"
|
|
||||||
});
|
|
||||||
|
|
||||||
this.play(nextTrack, { immediate: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
*[Symbol.iterator]() {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
yield* this.tracks;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JSON representation of this queue
|
|
||||||
* @returns {object}
|
|
||||||
*/
|
|
||||||
toJSON() {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
return {
|
|
||||||
id: this.id,
|
|
||||||
guild: this.guild.id,
|
|
||||||
voiceChannel: this.connection?.channel?.id,
|
|
||||||
options: this.options,
|
|
||||||
tracks: this.tracks.map((m) => m.toJSON())
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* String representation of this queue
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
toString() {
|
|
||||||
if (this.#watchDestroyed()) return;
|
|
||||||
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")}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
#watchDestroyed(emit = true) {
|
|
||||||
if (this.#destroyed) {
|
|
||||||
if (emit) this.player.emit("error", this, new PlayerError("Cannot use destroyed queue", ErrorStatusCode.DESTROYED_QUEUE));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#getBufferingTimeout() {
|
|
||||||
const timeout = this.options.bufferingTimeout;
|
|
||||||
|
|
||||||
if (isNaN(timeout) || timeout < 0 || !Number.isFinite(timeout)) return 1000;
|
|
||||||
return timeout;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Queue };
|
|
|
@ -1,191 +0,0 @@
|
||||||
import { User, escapeMarkdown, SnowflakeUtil } from "discord.js";
|
|
||||||
import { Player } from "../Player";
|
|
||||||
import { RawTrackData, TrackJSON } from "../types/types";
|
|
||||||
import { Playlist } from "./Playlist";
|
|
||||||
import { Queue } from "./Queue";
|
|
||||||
|
|
||||||
class Track {
|
|
||||||
public player!: Player;
|
|
||||||
public title!: string;
|
|
||||||
public description!: string;
|
|
||||||
public author!: string;
|
|
||||||
public url!: string;
|
|
||||||
public thumbnail!: string;
|
|
||||||
public duration!: string;
|
|
||||||
public views!: number;
|
|
||||||
public requestedBy!: User;
|
|
||||||
public playlist?: Playlist;
|
|
||||||
public readonly raw: RawTrackData = {} as RawTrackData;
|
|
||||||
public readonly id = SnowflakeUtil.generate().toString();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Track constructor
|
|
||||||
* @param {Player} player The player that instantiated this Track
|
|
||||||
* @param {RawTrackData} data Track data
|
|
||||||
*/
|
|
||||||
constructor(player: Player, data: RawTrackData) {
|
|
||||||
/**
|
|
||||||
* The player that instantiated this Track
|
|
||||||
* @name Track#player
|
|
||||||
* @type {Player}
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
Object.defineProperty(this, "player", { value: player, enumerable: false });
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Title of this track
|
|
||||||
* @name Track#title
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Description of this track
|
|
||||||
* @name Track#description
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Author of this track
|
|
||||||
* @name Track#author
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* URL of this track
|
|
||||||
* @name Track#url
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thumbnail of this track
|
|
||||||
* @name Track#thumbnail
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Duration of this track
|
|
||||||
* @name Track#duration
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Views count of this track
|
|
||||||
* @name Track#views
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Person who requested this track
|
|
||||||
* @name Track#requestedBy
|
|
||||||
* @type {User}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If this track belongs to playlist
|
|
||||||
* @name Track#fromPlaylist
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Raw track data
|
|
||||||
* @name Track#raw
|
|
||||||
* @type {RawTrackData}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The track id
|
|
||||||
* @name Track#id
|
|
||||||
* @type {Snowflake}
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The playlist which track belongs
|
|
||||||
* @name Track#playlist
|
|
||||||
* @type {Playlist}
|
|
||||||
*/
|
|
||||||
|
|
||||||
void this._patch(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _patch(data: RawTrackData) {
|
|
||||||
this.title = escapeMarkdown(data.title ?? "");
|
|
||||||
this.description = data.description ?? "";
|
|
||||||
this.author = data.author ?? "";
|
|
||||||
this.url = data.url ?? "";
|
|
||||||
this.thumbnail = data.thumbnail ?? "";
|
|
||||||
this.duration = data.duration ?? "";
|
|
||||||
this.views = data.views ?? 0;
|
|
||||||
this.requestedBy = data.requestedBy;
|
|
||||||
this.playlist = data.playlist;
|
|
||||||
|
|
||||||
// raw
|
|
||||||
Object.defineProperty(this, "raw", { value: Object.assign({}, { source: data.raw?.source ?? data.source }, data.raw ?? data), enumerable: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The queue in which this track is located
|
|
||||||
* @type {Queue}
|
|
||||||
*/
|
|
||||||
get queue(): Queue {
|
|
||||||
return this.player.queues.find((q) => q.tracks.some((ab) => ab.id === this.id));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The track duration in millisecond
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
get durationMS(): number {
|
|
||||||
const times = (n: number, t: number) => {
|
|
||||||
let tn = 1;
|
|
||||||
for (let i = 0; i < t; i++) tn *= n;
|
|
||||||
return t <= 0 ? 1000 : tn * 1000;
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.duration
|
|
||||||
.split(":")
|
|
||||||
.reverse()
|
|
||||||
.map((m, i) => parseInt(m) * times(60, i))
|
|
||||||
.reduce((a, c) => a + c, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns source of this track
|
|
||||||
* @type {TrackSource}
|
|
||||||
*/
|
|
||||||
get source() {
|
|
||||||
return this.raw.source ?? "arbitrary";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* String representation of this track
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
toString(): string {
|
|
||||||
return `${this.title} by ${this.author}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Raw JSON representation of this track
|
|
||||||
* @returns {TrackJSON}
|
|
||||||
*/
|
|
||||||
toJSON(hidePlaylist?: boolean) {
|
|
||||||
return {
|
|
||||||
id: this.id,
|
|
||||||
title: this.title,
|
|
||||||
description: this.description,
|
|
||||||
author: this.author,
|
|
||||||
url: this.url,
|
|
||||||
thumbnail: this.thumbnail,
|
|
||||||
duration: this.duration,
|
|
||||||
durationMS: this.durationMS,
|
|
||||||
views: this.views,
|
|
||||||
requestedBy: this.requestedBy?.id,
|
|
||||||
playlist: hidePlaylist ? null : this.playlist?.toJSON() ?? null
|
|
||||||
} as TrackJSON;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Track;
|
|
||||||
|
|
||||||
export { Track };
|
|
|
@ -1,253 +0,0 @@
|
||||||
import {
|
|
||||||
AudioPlayer,
|
|
||||||
AudioPlayerError,
|
|
||||||
AudioPlayerStatus,
|
|
||||||
AudioResource,
|
|
||||||
createAudioPlayer,
|
|
||||||
createAudioResource,
|
|
||||||
entersState,
|
|
||||||
StreamType,
|
|
||||||
VoiceConnection,
|
|
||||||
VoiceConnectionStatus,
|
|
||||||
VoiceConnectionDisconnectReason
|
|
||||||
} from "@discordjs/voice";
|
|
||||||
import { StageChannel, VoiceChannel } from "discord.js";
|
|
||||||
import { Duplex, Readable } from "stream";
|
|
||||||
import { TypedEmitter as EventEmitter } from "tiny-typed-emitter";
|
|
||||||
import Track from "../Structures/Track";
|
|
||||||
import { Util } from "../utils/Util";
|
|
||||||
import { PlayerError, ErrorStatusCode } from "../Structures/PlayerError";
|
|
||||||
|
|
||||||
export interface VoiceEvents {
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
error: (error: AudioPlayerError) => any;
|
|
||||||
debug: (message: string) => any;
|
|
||||||
start: (resource: AudioResource<Track>) => any;
|
|
||||||
finish: (resource: AudioResource<Track>) => any;
|
|
||||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
||||||
}
|
|
||||||
|
|
||||||
class StreamDispatcher extends EventEmitter<VoiceEvents> {
|
|
||||||
public readonly voiceConnection: VoiceConnection;
|
|
||||||
public readonly audioPlayer: AudioPlayer;
|
|
||||||
public channel: VoiceChannel | StageChannel;
|
|
||||||
public audioResource?: AudioResource<Track>;
|
|
||||||
private readyLock = false;
|
|
||||||
public paused: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates new connection object
|
|
||||||
* @param {VoiceConnection} connection The connection
|
|
||||||
* @param {VoiceChannel|StageChannel} channel The connected channel
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
constructor(connection: VoiceConnection, channel: VoiceChannel | StageChannel, public readonly connectionTimeout: number = 20000) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The voice connection
|
|
||||||
* @type {VoiceConnection}
|
|
||||||
*/
|
|
||||||
this.voiceConnection = connection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The audio player
|
|
||||||
* @type {AudioPlayer}
|
|
||||||
*/
|
|
||||||
this.audioPlayer = createAudioPlayer();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The voice channel
|
|
||||||
* @type {VoiceChannel|StageChannel}
|
|
||||||
*/
|
|
||||||
this.channel = channel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The paused state
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
this.paused = false;
|
|
||||||
|
|
||||||
this.voiceConnection.on("stateChange", async (_, newState) => {
|
|
||||||
if (newState.status === VoiceConnectionStatus.Disconnected) {
|
|
||||||
if (newState.reason === VoiceConnectionDisconnectReason.WebSocketClose && newState.closeCode === 4014) {
|
|
||||||
try {
|
|
||||||
await entersState(this.voiceConnection, VoiceConnectionStatus.Connecting, this.connectionTimeout);
|
|
||||||
} catch {
|
|
||||||
try {
|
|
||||||
this.voiceConnection.destroy();
|
|
||||||
} catch (err) {
|
|
||||||
this.emit("error", err as AudioPlayerError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (this.voiceConnection.rejoinAttempts < 5) {
|
|
||||||
await Util.wait((this.voiceConnection.rejoinAttempts + 1) * 5000);
|
|
||||||
this.voiceConnection.rejoin();
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
this.voiceConnection.destroy();
|
|
||||||
} catch (err) {
|
|
||||||
this.emit("error", err as AudioPlayerError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (newState.status === VoiceConnectionStatus.Destroyed) {
|
|
||||||
this.end();
|
|
||||||
} else if (!this.readyLock && (newState.status === VoiceConnectionStatus.Connecting || newState.status === VoiceConnectionStatus.Signalling)) {
|
|
||||||
this.readyLock = true;
|
|
||||||
try {
|
|
||||||
await entersState(this.voiceConnection, VoiceConnectionStatus.Ready, this.connectionTimeout);
|
|
||||||
} catch {
|
|
||||||
if (this.voiceConnection.state.status !== VoiceConnectionStatus.Destroyed) {
|
|
||||||
try {
|
|
||||||
this.voiceConnection.destroy();
|
|
||||||
} catch (err) {
|
|
||||||
this.emit("error", err as AudioPlayerError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
this.readyLock = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.audioPlayer.on("stateChange", (oldState, newState) => {
|
|
||||||
if (newState.status === AudioPlayerStatus.Playing) {
|
|
||||||
if (!this.paused) return void this.emit("start", this.audioResource);
|
|
||||||
} else if (newState.status === AudioPlayerStatus.Idle && oldState.status !== AudioPlayerStatus.Idle) {
|
|
||||||
if (!this.paused) {
|
|
||||||
void this.emit("finish", this.audioResource);
|
|
||||||
this.audioResource = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.audioPlayer.on("debug", (m) => void this.emit("debug", m));
|
|
||||||
this.audioPlayer.on("error", (error) => void this.emit("error", error));
|
|
||||||
this.voiceConnection.subscribe(this.audioPlayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates stream
|
|
||||||
* @param {Readable|Duplex|string} src The stream source
|
|
||||||
* @param {object} [ops] Options
|
|
||||||
* @returns {AudioResource}
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
createStream(src: Readable | Duplex | string, ops?: { type?: StreamType; data?: any; disableVolume?: boolean }) {
|
|
||||||
this.audioResource = createAudioResource(src, {
|
|
||||||
inputType: ops?.type ?? StreamType.Arbitrary,
|
|
||||||
metadata: ops?.data,
|
|
||||||
// eslint-disable-next-line no-extra-boolean-cast
|
|
||||||
inlineVolume: !Boolean(ops?.disableVolume)
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.audioResource;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The player status
|
|
||||||
* @type {AudioPlayerStatus}
|
|
||||||
*/
|
|
||||||
get status() {
|
|
||||||
return this.audioPlayer.state.status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnects from voice
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
disconnect() {
|
|
||||||
try {
|
|
||||||
this.audioPlayer.stop(true);
|
|
||||||
this.voiceConnection.destroy();
|
|
||||||
} catch {} // eslint-disable-line no-empty
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the player
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
end() {
|
|
||||||
this.audioPlayer.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pauses the stream playback
|
|
||||||
* @param {boolean} [interpolateSilence=false] If true, the player will play 5 packets of silence after pausing to prevent audio glitches.
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
pause(interpolateSilence?: boolean) {
|
|
||||||
const success = this.audioPlayer.pause(interpolateSilence);
|
|
||||||
this.paused = success;
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resumes the stream playback
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
resume() {
|
|
||||||
const success = this.audioPlayer.unpause();
|
|
||||||
this.paused = !success;
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Play stream
|
|
||||||
* @param {AudioResource<Track>} [resource=this.audioResource] The audio resource to play
|
|
||||||
* @returns {Promise<StreamDispatcher>}
|
|
||||||
*/
|
|
||||||
async playStream(resource: AudioResource<Track> = this.audioResource) {
|
|
||||||
if (!resource) throw new PlayerError("Audio resource is not available!", ErrorStatusCode.NO_AUDIO_RESOURCE);
|
|
||||||
if (resource.ended) return void this.emit("error", new PlayerError("Cannot play a resource that has already ended.") as unknown as AudioPlayerError);
|
|
||||||
if (!this.audioResource) this.audioResource = resource;
|
|
||||||
if (this.voiceConnection.state.status !== VoiceConnectionStatus.Ready) {
|
|
||||||
try {
|
|
||||||
await entersState(this.voiceConnection, VoiceConnectionStatus.Ready, this.connectionTimeout);
|
|
||||||
} catch (err) {
|
|
||||||
return void this.emit("error", err as AudioPlayerError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.audioPlayer.play(resource);
|
|
||||||
} catch (e) {
|
|
||||||
this.emit("error", e as AudioPlayerError);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets playback volume
|
|
||||||
* @param {number} value The volume amount
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
setVolume(value: number) {
|
|
||||||
if (!this.audioResource?.volume || isNaN(value) || value < 0 || value > Infinity) return false;
|
|
||||||
|
|
||||||
this.audioResource.volume.setVolumeLogarithmic(value / 100);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current volume
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
get volume() {
|
|
||||||
if (!this.audioResource?.volume) return 100;
|
|
||||||
const currentVol = this.audioResource.volume.volume;
|
|
||||||
return Math.round(Math.pow(currentVol, 1 / 1.660964) * 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The playback time
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
get streamTime() {
|
|
||||||
if (!this.audioResource) return 0;
|
|
||||||
return this.audioResource.playbackDuration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { StreamDispatcher as StreamDispatcher };
|
|
|
@ -1,82 +0,0 @@
|
||||||
import { VoiceChannel, StageChannel, Collection, Snowflake } from "discord.js";
|
|
||||||
import { DiscordGatewayAdapterCreator, joinVoiceChannel, VoiceConnection } from "@discordjs/voice";
|
|
||||||
import { StreamDispatcher } from "./StreamDispatcher";
|
|
||||||
|
|
||||||
class VoiceUtils {
|
|
||||||
public cache: Collection<Snowflake, StreamDispatcher>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The voice utils
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
/**
|
|
||||||
* The cache where voice utils stores stream managers
|
|
||||||
* @type {Collection<Snowflake, StreamDispatcher>}
|
|
||||||
*/
|
|
||||||
this.cache = new Collection<Snowflake, StreamDispatcher>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Joins a voice channel, creating basic stream dispatch manager
|
|
||||||
* @param {StageChannel|VoiceChannel} channel The voice channel
|
|
||||||
* @param {object} [options] Join options
|
|
||||||
* @returns {Promise<StreamDispatcher>}
|
|
||||||
*/
|
|
||||||
public async connect(
|
|
||||||
channel: VoiceChannel | StageChannel,
|
|
||||||
options?: {
|
|
||||||
deaf?: boolean;
|
|
||||||
maxTime?: number;
|
|
||||||
}
|
|
||||||
): Promise<StreamDispatcher> {
|
|
||||||
const conn = await this.join(channel, options);
|
|
||||||
const sub = new StreamDispatcher(conn, channel, options.maxTime);
|
|
||||||
this.cache.set(channel.guild.id, sub);
|
|
||||||
return sub;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Joins a voice channel
|
|
||||||
* @param {StageChannel|VoiceChannel} [channel] The voice/stage channel to join
|
|
||||||
* @param {object} [options] Join options
|
|
||||||
* @returns {VoiceConnection}
|
|
||||||
*/
|
|
||||||
public async join(
|
|
||||||
channel: VoiceChannel | StageChannel,
|
|
||||||
options?: {
|
|
||||||
deaf?: boolean;
|
|
||||||
maxTime?: number;
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const conn = joinVoiceChannel({
|
|
||||||
guildId: channel.guild.id,
|
|
||||||
channelId: channel.id,
|
|
||||||
adapterCreator: channel.guild.voiceAdapterCreator as unknown as DiscordGatewayAdapterCreator,
|
|
||||||
selfDeaf: Boolean(options.deaf)
|
|
||||||
});
|
|
||||||
|
|
||||||
return conn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnects voice connection
|
|
||||||
* @param {VoiceConnection} connection The voice connection
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
public disconnect(connection: VoiceConnection | StreamDispatcher) {
|
|
||||||
if (connection instanceof StreamDispatcher) return connection.voiceConnection.destroy();
|
|
||||||
return connection.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns Discord Player voice connection
|
|
||||||
* @param {Snowflake} guild The guild id
|
|
||||||
* @returns {StreamDispatcher}
|
|
||||||
*/
|
|
||||||
public getConnection(guild: Snowflake) {
|
|
||||||
return this.cache.get(guild);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { VoiceUtils };
|
|
|
@ -1,144 +0,0 @@
|
||||||
// prism's volume transformer with smooth volume support
|
|
||||||
|
|
||||||
import { Transform, TransformOptions } from "stream";
|
|
||||||
|
|
||||||
export interface VolumeTransformerOptions extends TransformOptions {
|
|
||||||
type?: "s16le" | "s16be" | "s32le" | "s32be";
|
|
||||||
smoothness?: number;
|
|
||||||
volume?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class VolumeTransformer extends Transform {
|
|
||||||
private _bits: number;
|
|
||||||
private _smoothing: number;
|
|
||||||
private _bytes: number;
|
|
||||||
private _extremum: number;
|
|
||||||
private _chunk: Buffer;
|
|
||||||
public volume: number;
|
|
||||||
private _targetVolume: number;
|
|
||||||
public type: "s16le" | "s32le" | "s16be" | "s32be";
|
|
||||||
constructor(options: VolumeTransformerOptions = {}) {
|
|
||||||
super(options);
|
|
||||||
switch (options.type) {
|
|
||||||
case "s16le":
|
|
||||||
this._readInt = (buffer, index) => buffer.readInt16LE(index);
|
|
||||||
this._writeInt = (buffer, int, index) => buffer.writeInt16LE(int, index);
|
|
||||||
this._bits = 16;
|
|
||||||
break;
|
|
||||||
case "s16be":
|
|
||||||
this._readInt = (buffer, index) => buffer.readInt16BE(index);
|
|
||||||
this._writeInt = (buffer, int, index) => buffer.writeInt16BE(int, index);
|
|
||||||
this._bits = 16;
|
|
||||||
break;
|
|
||||||
case "s32le":
|
|
||||||
this._readInt = (buffer, index) => buffer.readInt32LE(index);
|
|
||||||
this._writeInt = (buffer, int, index) => buffer.writeInt32LE(int, index);
|
|
||||||
this._bits = 32;
|
|
||||||
break;
|
|
||||||
case "s32be":
|
|
||||||
this._readInt = (buffer, index) => buffer.readInt32BE(index);
|
|
||||||
this._writeInt = (buffer, int, index) => buffer.writeInt32BE(int, index);
|
|
||||||
this._bits = 32;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error("VolumeTransformer type should be one of s16le, s16be, s32le, s32be");
|
|
||||||
}
|
|
||||||
this.type = options.type;
|
|
||||||
this._bytes = this._bits / 8;
|
|
||||||
this._extremum = Math.pow(2, this._bits - 1);
|
|
||||||
this.volume = Number.isNaN(options.volume) ? 1 : Number(options.volume);
|
|
||||||
if (!Number.isFinite(this.volume)) this.volume = 1;
|
|
||||||
this._targetVolume = this.volume;
|
|
||||||
this._chunk = Buffer.alloc(0);
|
|
||||||
this._smoothing = options.smoothness || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
_readInt(buffer: Buffer, index: number) {
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
_writeInt(buffer: Buffer, int: number, index: number) {
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
_applySmoothness() {
|
|
||||||
if (this.volume < this._targetVolume) {
|
|
||||||
this.volume = this.volume + this._smoothing >= this._targetVolume ? this._targetVolume : this.volume + this._smoothing;
|
|
||||||
} else if (this.volume > this._targetVolume) {
|
|
||||||
this.volume = this.volume - this._smoothing <= this._targetVolume ? this._targetVolume : this.volume - this._smoothing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_transform(chunk: Buffer, encoding: BufferEncoding, done: () => unknown) {
|
|
||||||
if (this.smoothingEnabled() && this.volume !== this._targetVolume) this._applySmoothness();
|
|
||||||
|
|
||||||
if (this.volume === 1) {
|
|
||||||
this.push(chunk);
|
|
||||||
return done();
|
|
||||||
}
|
|
||||||
|
|
||||||
const { _bytes, _extremum } = this;
|
|
||||||
|
|
||||||
chunk = this._chunk = Buffer.concat([this._chunk, chunk]);
|
|
||||||
if (chunk.length < _bytes) return done();
|
|
||||||
|
|
||||||
const complete = Math.floor(chunk.length / _bytes) * _bytes;
|
|
||||||
|
|
||||||
for (let i = 0; i < complete; i += _bytes) {
|
|
||||||
const int = Math.min(_extremum - 1, Math.max(-_extremum, Math.floor(this.volume * this._readInt(chunk, i))));
|
|
||||||
this._writeInt(chunk, int, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._chunk = chunk.slice(complete);
|
|
||||||
this.push(chunk.slice(0, complete));
|
|
||||||
return done();
|
|
||||||
}
|
|
||||||
|
|
||||||
_destroy(err: Error, cb: (error: Error) => void) {
|
|
||||||
super._destroy(err, cb);
|
|
||||||
this._chunk = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
setVolume(volume: number) {
|
|
||||||
if (Number.isNaN(volume)) volume = 1;
|
|
||||||
if (typeof volume !== "number") volume = Number(volume);
|
|
||||||
if (!Number.isFinite(volume)) volume = volume < 0 ? 0 : 1;
|
|
||||||
this._targetVolume = volume;
|
|
||||||
if (this._smoothing <= 0) this.volume = volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
setVolumeDecibels(db: number) {
|
|
||||||
this.setVolume(Math.pow(10, db / 20));
|
|
||||||
}
|
|
||||||
|
|
||||||
setVolumeLogarithmic(value: number) {
|
|
||||||
this.setVolume(Math.pow(value, 1.660964));
|
|
||||||
}
|
|
||||||
|
|
||||||
get volumeDecibels() {
|
|
||||||
return Math.log10(this.volume) * 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
get volumeLogarithmic() {
|
|
||||||
return Math.pow(this.volume, 1 / 1.660964);
|
|
||||||
}
|
|
||||||
|
|
||||||
get smoothness() {
|
|
||||||
return this._smoothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSmoothness(smoothness: number) {
|
|
||||||
this._smoothing = smoothness;
|
|
||||||
}
|
|
||||||
|
|
||||||
smoothingEnabled() {
|
|
||||||
return Number.isFinite(this._smoothing) && this._smoothing > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get hasSmoothness() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get hasSmoothing() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
// try applying smooth volume patch on load
|
|
||||||
import "./smoothVolume";
|
|
||||||
|
|
||||||
export { AudioFilters } from "./utils/AudioFilters";
|
|
||||||
export { ExtractorModel } from "./Structures/ExtractorModel";
|
|
||||||
export { Playlist } from "./Structures/Playlist";
|
|
||||||
export { Player } from "./Player";
|
|
||||||
export { PlayerError, ErrorStatusCode } from "./Structures/PlayerError";
|
|
||||||
export { QueryResolver } from "./utils/QueryResolver";
|
|
||||||
export { Queue } from "./Structures/Queue";
|
|
||||||
export { Track } from "./Structures/Track";
|
|
||||||
export { VoiceUtils } from "./VoiceInterface/VoiceUtils";
|
|
||||||
export { VoiceEvents, StreamDispatcher } from "./VoiceInterface/StreamDispatcher";
|
|
||||||
export { Util } from "./utils/Util";
|
|
||||||
export * from "./types/types";
|
|
||||||
export * from "./utils/FFmpegStream";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
export const version: string = require(`${__dirname}/../package.json`).version;
|
|
|
@ -1,12 +0,0 @@
|
||||||
import { VolumeTransformer as VolumeTransformerMock } from "./VoiceInterface/VolumeTransformer";
|
|
||||||
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
const mod = require("prism-media") as typeof import("prism-media") & { VolumeTransformer: typeof VolumeTransformerMock };
|
|
||||||
|
|
||||||
if (typeof mod.VolumeTransformer.hasSmoothing !== "boolean") {
|
|
||||||
Reflect.set(mod, "VolumeTransformer", VolumeTransformerMock);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
/* do nothing */
|
|
||||||
}
|
|
|
@ -1,485 +0,0 @@
|
||||||
import { Snowflake, User, UserResolvable } from "discord.js";
|
|
||||||
import { Readable, Duplex } from "stream";
|
|
||||||
import { Queue } from "../Structures/Queue";
|
|
||||||
import Track from "../Structures/Track";
|
|
||||||
import { Playlist } from "../Structures/Playlist";
|
|
||||||
import { StreamDispatcher } from "../VoiceInterface/StreamDispatcher";
|
|
||||||
|
|
||||||
export type FiltersName = keyof QueueFilters;
|
|
||||||
|
|
||||||
export interface PlayerSearchResult {
|
|
||||||
playlist: Playlist | null;
|
|
||||||
tracks: Track[];
|
|
||||||
searched?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {AudioFilters} QueueFilters
|
|
||||||
*/
|
|
||||||
export interface QueueFilters {
|
|
||||||
bassboost_low?: boolean;
|
|
||||||
bassboost?: boolean;
|
|
||||||
bassboost_high?: boolean;
|
|
||||||
"8D"?: boolean;
|
|
||||||
vaporwave?: boolean;
|
|
||||||
nightcore?: boolean;
|
|
||||||
phaser?: boolean;
|
|
||||||
tremolo?: boolean;
|
|
||||||
vibrato?: boolean;
|
|
||||||
reverse?: boolean;
|
|
||||||
treble?: boolean;
|
|
||||||
normalizer?: boolean;
|
|
||||||
normalizer2?: boolean;
|
|
||||||
surrounding?: boolean;
|
|
||||||
pulsator?: boolean;
|
|
||||||
subboost?: boolean;
|
|
||||||
karaoke?: boolean;
|
|
||||||
flanger?: boolean;
|
|
||||||
gate?: boolean;
|
|
||||||
haas?: boolean;
|
|
||||||
mcompand?: boolean;
|
|
||||||
mono?: boolean;
|
|
||||||
mstlr?: boolean;
|
|
||||||
mstrr?: boolean;
|
|
||||||
compressor?: boolean;
|
|
||||||
expander?: boolean;
|
|
||||||
softlimiter?: boolean;
|
|
||||||
chorus?: boolean;
|
|
||||||
chorus2d?: boolean;
|
|
||||||
chorus3d?: boolean;
|
|
||||||
fadein?: boolean;
|
|
||||||
dim?: boolean;
|
|
||||||
earrape?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The track source:
|
|
||||||
* - soundcloud
|
|
||||||
* - youtube
|
|
||||||
* - spotify
|
|
||||||
* - arbitrary
|
|
||||||
* @typedef {string} TrackSource
|
|
||||||
*/
|
|
||||||
export type TrackSource = "soundcloud" | "youtube" | "spotify" | "arbitrary";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} RawTrackData
|
|
||||||
* @property {string} title The title
|
|
||||||
* @property {string} description The description
|
|
||||||
* @property {string} author The author
|
|
||||||
* @property {string} url The url
|
|
||||||
* @property {string} thumbnail The thumbnail
|
|
||||||
* @property {string} duration The duration
|
|
||||||
* @property {number | boolean} views The views
|
|
||||||
* @property {User} requestedBy The user who requested this track
|
|
||||||
* @property {Playlist} [playlist] The playlist
|
|
||||||
* @property {TrackSource} [source="arbitrary"] The source
|
|
||||||
* @property {any} [engine] The engine
|
|
||||||
* @property {boolean} [live] If this track is live
|
|
||||||
* @property {any} [raw] The raw data
|
|
||||||
*/
|
|
||||||
export interface RawTrackData {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
author: string;
|
|
||||||
url: string;
|
|
||||||
thumbnail: string;
|
|
||||||
duration: string;
|
|
||||||
views: number;
|
|
||||||
requestedBy: User;
|
|
||||||
playlist?: Playlist;
|
|
||||||
source?: TrackSource;
|
|
||||||
engine?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
live?: boolean;
|
|
||||||
raw?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} TimeData
|
|
||||||
* @property {number} days Time in days
|
|
||||||
* @property {number} hours Time in hours
|
|
||||||
* @property {number} minutes Time in minutes
|
|
||||||
* @property {number} seconds Time in seconds
|
|
||||||
*/
|
|
||||||
export interface TimeData {
|
|
||||||
days: number;
|
|
||||||
hours: number;
|
|
||||||
minutes: number;
|
|
||||||
seconds: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} PlayerProgressbarOptions
|
|
||||||
* @property {boolean} [timecodes] If it should render time codes
|
|
||||||
* @property {boolean} [queue] If it should create progress bar for the whole queue
|
|
||||||
* @property {number} [length] The bar length
|
|
||||||
* @property {string} [line] The bar track
|
|
||||||
* @property {string} [indicator] The indicator
|
|
||||||
*/
|
|
||||||
export interface PlayerProgressbarOptions {
|
|
||||||
timecodes?: boolean;
|
|
||||||
length?: number;
|
|
||||||
line?: string;
|
|
||||||
indicator?: string;
|
|
||||||
queue?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} PlayerOptions
|
|
||||||
* @property {boolean} [leaveOnEnd=true] If it should leave on end
|
|
||||||
* @property {boolean} [leaveOnStop=true] If it should leave on stop
|
|
||||||
* @property {boolean} [leaveOnEmpty=true] If it should leave on empty
|
|
||||||
* @property {number} [leaveOnEmptyCooldown=1000] The cooldown in ms
|
|
||||||
* @property {boolean} [autoSelfDeaf=true] If it should set the bot in deaf mode
|
|
||||||
* @property {YTDLDownloadOptions} [ytdlOptions] The youtube download options
|
|
||||||
* @property {number} [initialVolume=100] The initial player volume
|
|
||||||
* @property {number} [bufferingTimeout=3000] Buffering timeout for the stream
|
|
||||||
* @property {boolean} [spotifyBridge=true] If player should bridge spotify source to youtube
|
|
||||||
* @property {boolean} [disableVolume=false] If player should disable inline volume
|
|
||||||
* @property {number} [volumeSmoothness=0] The volume transition smoothness between volume changes (lower the value to get better result)
|
|
||||||
* Setting this or leaving this empty will disable this effect. Example: `volumeSmoothness: 0.1`
|
|
||||||
* @property {Function} [onBeforeCreateStream] Runs before creating stream
|
|
||||||
*/
|
|
||||||
export interface PlayerOptions {
|
|
||||||
leaveOnEnd?: boolean;
|
|
||||||
leaveOnStop?: boolean;
|
|
||||||
leaveOnEmpty?: boolean;
|
|
||||||
leaveOnEmptyCooldown?: number;
|
|
||||||
autoSelfDeaf?: boolean;
|
|
||||||
initialVolume?: number;
|
|
||||||
bufferingTimeout?: number;
|
|
||||||
spotifyBridge?: boolean;
|
|
||||||
disableVolume?: boolean;
|
|
||||||
volumeSmoothness?: number;
|
|
||||||
onBeforeCreateStream?: (track: Track, source: TrackSource, queue: Queue) => Promise<Readable>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} ExtractorModelData
|
|
||||||
* @property {object} [playlist] The playlist info (if any)
|
|
||||||
* @property {string} [playlist.title] The playlist title
|
|
||||||
* @property {string} [playlist.description] The playlist description
|
|
||||||
* @property {string} [playlist.thumbnail] The playlist thumbnail
|
|
||||||
* @property {album|playlist} [playlist.type] The playlist type: `album` | `playlist`
|
|
||||||
* @property {TrackSource} [playlist.source] The playlist source
|
|
||||||
* @property {object} [playlist.author] The playlist author
|
|
||||||
* @property {string} [playlist.author.name] The author name
|
|
||||||
* @property {string} [playlist.author.url] The author url
|
|
||||||
* @property {string} [playlist.id] The playlist id
|
|
||||||
* @property {string} [playlist.url] The playlist url
|
|
||||||
* @property {any} [playlist.rawPlaylist] The raw data
|
|
||||||
* @property {ExtractorData[]} data The data
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} ExtractorData
|
|
||||||
* @property {string} title The title
|
|
||||||
* @property {number} duration The duration
|
|
||||||
* @property {string} thumbnail The thumbnail
|
|
||||||
* @property {string|Readable|Duplex} engine The stream engine
|
|
||||||
* @property {number} views The views count
|
|
||||||
* @property {string} author The author
|
|
||||||
* @property {string} description The description
|
|
||||||
* @property {string} url The url
|
|
||||||
* @property {string} [version] The extractor version
|
|
||||||
* @property {TrackSource} [source="arbitrary"] The source
|
|
||||||
*/
|
|
||||||
export interface ExtractorModelData {
|
|
||||||
playlist?: {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
thumbnail: string;
|
|
||||||
type: "album" | "playlist";
|
|
||||||
source: TrackSource;
|
|
||||||
author: {
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
};
|
|
||||||
id: string;
|
|
||||||
url: string;
|
|
||||||
rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
};
|
|
||||||
data: {
|
|
||||||
title: string;
|
|
||||||
duration: number;
|
|
||||||
thumbnail: string;
|
|
||||||
engine: string | Readable | Duplex;
|
|
||||||
views: number;
|
|
||||||
author: string;
|
|
||||||
description: string;
|
|
||||||
url: string;
|
|
||||||
version?: string;
|
|
||||||
source?: TrackSource;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The search query type
|
|
||||||
* This can be one of:
|
|
||||||
* - AUTO
|
|
||||||
* - YOUTUBE
|
|
||||||
* - YOUTUBE_PLAYLIST
|
|
||||||
* - SOUNDCLOUD_TRACK
|
|
||||||
* - SOUNDCLOUD_PLAYLIST
|
|
||||||
* - SOUNDCLOUD
|
|
||||||
* - SPOTIFY_SONG
|
|
||||||
* - SPOTIFY_ALBUM
|
|
||||||
* - SPOTIFY_PLAYLIST
|
|
||||||
* - FACEBOOK
|
|
||||||
* - VIMEO
|
|
||||||
* - ARBITRARY
|
|
||||||
* - REVERBNATION
|
|
||||||
* - YOUTUBE_SEARCH
|
|
||||||
* - YOUTUBE_VIDEO
|
|
||||||
* - SOUNDCLOUD_SEARCH
|
|
||||||
* @typedef {number} QueryType
|
|
||||||
*/
|
|
||||||
export enum QueryType {
|
|
||||||
AUTO,
|
|
||||||
YOUTUBE,
|
|
||||||
YOUTUBE_PLAYLIST,
|
|
||||||
SOUNDCLOUD_TRACK,
|
|
||||||
SOUNDCLOUD_PLAYLIST,
|
|
||||||
SOUNDCLOUD,
|
|
||||||
SPOTIFY_SONG,
|
|
||||||
SPOTIFY_ALBUM,
|
|
||||||
SPOTIFY_PLAYLIST,
|
|
||||||
FACEBOOK,
|
|
||||||
VIMEO,
|
|
||||||
ARBITRARY,
|
|
||||||
REVERBNATION,
|
|
||||||
YOUTUBE_SEARCH,
|
|
||||||
YOUTUBE_VIDEO,
|
|
||||||
SOUNDCLOUD_SEARCH
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emitted when bot gets disconnected from a voice channel
|
|
||||||
* @event Player#botDisconnect
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emitted when the voice channel is empty
|
|
||||||
* @event Player#channelEmpty
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emitted when bot connects to a voice channel
|
|
||||||
* @event Player#connectionCreate
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
* @param {StreamDispatcher} connection The discord player connection object
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Debug information
|
|
||||||
* @event Player#debug
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
* @param {string} message The message
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emitted on error
|
|
||||||
* <warn>This event should handled properly otherwise it may crash your process!</warn>
|
|
||||||
* @event Player#error
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
* @param {Error} error The error
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emitted on connection error. Sometimes stream errors are emitted here as well.
|
|
||||||
* @event Player#connectionError
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
* @param {Error} error The error
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emitted when queue ends
|
|
||||||
* @event Player#queueEnd
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emitted when a single track is added
|
|
||||||
* @event Player#trackAdd
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
* @param {Track} track The track
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emitted when multiple tracks are added
|
|
||||||
* @event Player#tracksAdd
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
* @param {Track[]} tracks The tracks
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emitted when a track starts playing
|
|
||||||
* @event Player#trackStart
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
* @param {Track} track The track
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emitted when a track ends
|
|
||||||
* @event Player#trackEnd
|
|
||||||
* @param {Queue} queue The queue
|
|
||||||
* @param {Track} track The track
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
export interface PlayerEvents {
|
|
||||||
botDisconnect: (queue: Queue) => any;
|
|
||||||
channelEmpty: (queue: Queue) => any;
|
|
||||||
connectionCreate: (queue: Queue, connection: StreamDispatcher) => any;
|
|
||||||
debug: (queue: Queue, message: string) => any;
|
|
||||||
error: (queue: Queue, error: Error) => any;
|
|
||||||
connectionError: (queue: Queue, error: Error) => any;
|
|
||||||
queueEnd: (queue: Queue) => any;
|
|
||||||
trackAdd: (queue: Queue, track: Track) => any;
|
|
||||||
tracksAdd: (queue: Queue, track: Track[]) => any;
|
|
||||||
trackStart: (queue: Queue, track: Track) => any;
|
|
||||||
trackEnd: (queue: Queue, track: Track) => any;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} PlayOptions
|
|
||||||
* @property {boolean} [filtersUpdate=false] If this play was triggered for filters update
|
|
||||||
* @property {string[]} [encoderArgs=[]] FFmpeg args passed to encoder
|
|
||||||
* @property {number} [seek] Time to seek to before playing
|
|
||||||
* @property {boolean} [immediate=false] If it should start playing the provided track immediately
|
|
||||||
*/
|
|
||||||
export interface PlayOptions {
|
|
||||||
filtersUpdate?: boolean;
|
|
||||||
encoderArgs?: string[];
|
|
||||||
seek?: number;
|
|
||||||
immediate?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} SearchOptions
|
|
||||||
* @property {UserResolvable} requestedBy The user who requested this search
|
|
||||||
* @property {QueryType|string} [searchEngine=QueryType.AUTO] The query search engine, can be extractor name to target specific one (custom)
|
|
||||||
* @property {boolean} [blockExtractor=false] If it should block custom extractors
|
|
||||||
*/
|
|
||||||
export interface SearchOptions {
|
|
||||||
requestedBy: UserResolvable;
|
|
||||||
searchEngine?: QueryType | string;
|
|
||||||
blockExtractor?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The queue repeat mode. This can be one of:
|
|
||||||
* - OFF
|
|
||||||
* - TRACK
|
|
||||||
* - QUEUE
|
|
||||||
* - AUTOPLAY
|
|
||||||
* @typedef {number} QueueRepeatMode
|
|
||||||
*/
|
|
||||||
export enum QueueRepeatMode {
|
|
||||||
OFF = 0,
|
|
||||||
TRACK = 1,
|
|
||||||
QUEUE = 2,
|
|
||||||
AUTOPLAY = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} PlaylistInitData
|
|
||||||
* @property {Track[]} tracks The tracks of this playlist
|
|
||||||
* @property {string} title The playlist title
|
|
||||||
* @property {string} description The description
|
|
||||||
* @property {string} thumbnail The thumbnail
|
|
||||||
* @property {album|playlist} type The playlist type: `album` | `playlist`
|
|
||||||
* @property {TrackSource} source The playlist source
|
|
||||||
* @property {object} author The playlist author
|
|
||||||
* @property {string} [author.name] The author name
|
|
||||||
* @property {string} [author.url] The author url
|
|
||||||
* @property {string} id The playlist id
|
|
||||||
* @property {string} url The playlist url
|
|
||||||
* @property {any} [rawPlaylist] The raw playlist data
|
|
||||||
*/
|
|
||||||
export interface PlaylistInitData {
|
|
||||||
tracks: Track[];
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
thumbnail: string;
|
|
||||||
type: "album" | "playlist";
|
|
||||||
source: TrackSource;
|
|
||||||
author: {
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
};
|
|
||||||
id: string;
|
|
||||||
url: string;
|
|
||||||
rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} TrackJSON
|
|
||||||
* @property {string} title The track title
|
|
||||||
* @property {string} description The track description
|
|
||||||
* @property {string} author The author
|
|
||||||
* @property {string} url The url
|
|
||||||
* @property {string} thumbnail The thumbnail
|
|
||||||
* @property {string} duration The duration
|
|
||||||
* @property {number} durationMS The duration in ms
|
|
||||||
* @property {number} views The views count
|
|
||||||
* @property {Snowflake} requestedBy The id of the user who requested this track
|
|
||||||
* @property {PlaylistJSON} [playlist] The playlist info (if any)
|
|
||||||
*/
|
|
||||||
export interface TrackJSON {
|
|
||||||
id: Snowflake;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
author: string;
|
|
||||||
url: string;
|
|
||||||
thumbnail: string;
|
|
||||||
duration: string;
|
|
||||||
durationMS: number;
|
|
||||||
views: number;
|
|
||||||
requestedBy: Snowflake;
|
|
||||||
playlist?: PlaylistJSON;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} PlaylistJSON
|
|
||||||
* @property {string} id The playlist id
|
|
||||||
* @property {string} url The playlist url
|
|
||||||
* @property {string} title The playlist title
|
|
||||||
* @property {string} description The playlist description
|
|
||||||
* @property {string} thumbnail The thumbnail
|
|
||||||
* @property {album|playlist} type The playlist type: `album` | `playlist`
|
|
||||||
* @property {TrackSource} source The track source
|
|
||||||
* @property {object} author The playlist author
|
|
||||||
* @property {string} [author.name] The author name
|
|
||||||
* @property {string} [author.url] The author url
|
|
||||||
* @property {TrackJSON[]} tracks The tracks data (if any)
|
|
||||||
*/
|
|
||||||
export interface PlaylistJSON {
|
|
||||||
id: string;
|
|
||||||
url: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
thumbnail: string;
|
|
||||||
type: "album" | "playlist";
|
|
||||||
source: TrackSource;
|
|
||||||
author: {
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
};
|
|
||||||
tracks: TrackJSON[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} PlayerInitOptions
|
|
||||||
* @property {boolean} [autoRegisterExtractor=true] If it should automatically register `@discord-player/extractor`
|
|
||||||
* @property {YTDLDownloadOptions} [ytdlOptions] The options passed to `ytdl-core`
|
|
||||||
* @property {number} [connectionTimeout=20000] The voice connection timeout
|
|
||||||
*/
|
|
||||||
export interface PlayerInitOptions {
|
|
||||||
autoRegisterExtractor?: boolean;
|
|
||||||
connectionTimeout?: number;
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
import { FiltersName } from "../types/types";
|
|
||||||
|
|
||||||
const bass = (g: number) => `bass=g=${g}:f=110:w=0.3`;
|
|
||||||
|
|
||||||
class AudioFilters {
|
|
||||||
public constructor() {
|
|
||||||
return AudioFilters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get filters(): Record<FiltersName, string> {
|
|
||||||
return {
|
|
||||||
bassboost_low: bass(15),
|
|
||||||
bassboost: bass(20),
|
|
||||||
bassboost_high: bass(30),
|
|
||||||
"8D": "apulsator=hz=0.09",
|
|
||||||
vaporwave: "aresample=48000,asetrate=48000*0.8",
|
|
||||||
nightcore: "aresample=48000,asetrate=48000*1.25",
|
|
||||||
phaser: "aphaser=in_gain=0.4",
|
|
||||||
tremolo: "tremolo",
|
|
||||||
vibrato: "vibrato=f=6.5",
|
|
||||||
reverse: "areverse",
|
|
||||||
treble: "treble=g=5",
|
|
||||||
normalizer2: "dynaudnorm=g=101",
|
|
||||||
normalizer: "acompressor",
|
|
||||||
surrounding: "surround",
|
|
||||||
pulsator: "apulsator=hz=1",
|
|
||||||
subboost: "asubboost",
|
|
||||||
karaoke: "stereotools=mlev=0.03",
|
|
||||||
flanger: "flanger",
|
|
||||||
gate: "agate",
|
|
||||||
haas: "haas",
|
|
||||||
mcompand: "mcompand",
|
|
||||||
mono: "pan=mono|c0=.5*c0+.5*c1",
|
|
||||||
mstlr: "stereotools=mode=ms>lr",
|
|
||||||
mstrr: "stereotools=mode=ms>rr",
|
|
||||||
compressor: "compand=points=-80/-105|-62/-80|-15.4/-15.4|0/-12|20/-7.6",
|
|
||||||
expander: "compand=attacks=0:points=-80/-169|-54/-80|-49.5/-64.6|-41.1/-41.1|-25.8/-15|-10.8/-4.5|0/0|20/8.3",
|
|
||||||
softlimiter: "compand=attacks=0:points=-80/-80|-12.4/-12.4|-6/-8|0/-6.8|20/-2.8",
|
|
||||||
chorus: "chorus=0.7:0.9:55:0.4:0.25:2",
|
|
||||||
chorus2d: "chorus=0.6:0.9:50|60:0.4|0.32:0.25|0.4:2|1.3",
|
|
||||||
chorus3d: "chorus=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3",
|
|
||||||
fadein: "afade=t=in:ss=0:d=10",
|
|
||||||
dim: `afftfilt="'real=re * (1-clip((b/nb)*b,0,1))':imag='im * (1-clip((b/nb)*b,0,1))'"`,
|
|
||||||
earrape: "channelsplit,sidechaingate=level_in=64"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get<K extends FiltersName>(name: K) {
|
|
||||||
return this.filters[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static has<K extends FiltersName>(name: K) {
|
|
||||||
return name in this.filters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static *[Symbol.iterator](): IterableIterator<{ name: FiltersName; value: string }> {
|
|
||||||
for (const [k, v] of Object.entries(this.filters)) {
|
|
||||||
yield { name: k as FiltersName, value: v as string };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get names() {
|
|
||||||
return Object.keys(this.filters) as FiltersName[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-expect-error AudioFilters.length
|
|
||||||
public static get length() {
|
|
||||||
return this.names.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static toString() {
|
|
||||||
return this.names.map((m) => (this as any)[m]).join(","); // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create ffmpeg args from the specified filters name
|
|
||||||
* @param filter The filter name
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public static create<K extends FiltersName>(filters?: K[]) {
|
|
||||||
if (!filters || !Array.isArray(filters)) return this.toString();
|
|
||||||
return filters
|
|
||||||
.filter((predicate) => typeof predicate === "string")
|
|
||||||
.map((m) => this.get(m))
|
|
||||||
.join(",");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines audio filter
|
|
||||||
* @param filterName The name of the filter
|
|
||||||
* @param value The ffmpeg args
|
|
||||||
*/
|
|
||||||
public static define(filterName: string, value: string) {
|
|
||||||
this.filters[filterName as FiltersName] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines multiple audio filters
|
|
||||||
* @param filtersArray Array of filters containing the filter name and ffmpeg args
|
|
||||||
*/
|
|
||||||
public static defineBulk(filtersArray: { name: string; value: string }[]) {
|
|
||||||
filtersArray.forEach((arr) => this.define(arr.name, arr.value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AudioFilters;
|
|
||||||
export { AudioFilters };
|
|
|
@ -1,59 +0,0 @@
|
||||||
import { FFmpeg } from "prism-media";
|
|
||||||
import type { Duplex, Readable } from "stream";
|
|
||||||
|
|
||||||
export interface FFmpegStreamOptions {
|
|
||||||
fmt?: string;
|
|
||||||
encoderArgs?: string[];
|
|
||||||
seek?: number;
|
|
||||||
skip?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function FFMPEG_ARGS_STRING(stream: string, fmt?: string) {
|
|
||||||
// prettier-ignore
|
|
||||||
return [
|
|
||||||
"-reconnect", "1",
|
|
||||||
"-reconnect_streamed", "1",
|
|
||||||
"-reconnect_delay_max", "5",
|
|
||||||
"-i", stream,
|
|
||||||
"-analyzeduration", "0",
|
|
||||||
"-loglevel", "0",
|
|
||||||
"-f", `${typeof fmt === "string" ? fmt : "s16le"}`,
|
|
||||||
"-ar", "48000",
|
|
||||||
"-ac", "2"
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function FFMPEG_ARGS_PIPED(fmt?: string) {
|
|
||||||
// prettier-ignore
|
|
||||||
return [
|
|
||||||
"-analyzeduration", "0",
|
|
||||||
"-loglevel", "0",
|
|
||||||
"-f", `${typeof fmt === "string" ? fmt : "s16le"}`,
|
|
||||||
"-ar", "48000",
|
|
||||||
"-ac", "2"
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates FFmpeg stream
|
|
||||||
* @param stream The source stream
|
|
||||||
* @param options FFmpeg stream options
|
|
||||||
*/
|
|
||||||
export function createFFmpegStream(stream: Readable | Duplex | string, options?: FFmpegStreamOptions) {
|
|
||||||
if (options.skip && typeof stream !== "string") return stream;
|
|
||||||
options ??= {};
|
|
||||||
const args = typeof stream === "string" ? FFMPEG_ARGS_STRING(stream, options.fmt) : FFMPEG_ARGS_PIPED(options.fmt);
|
|
||||||
|
|
||||||
if (!Number.isNaN(options.seek)) args.unshift("-ss", String(options.seek));
|
|
||||||
if (Array.isArray(options.encoderArgs)) args.push(...options.encoderArgs);
|
|
||||||
|
|
||||||
const transcoder = new FFmpeg({ shell: false, args });
|
|
||||||
transcoder.on("close", () => transcoder.destroy());
|
|
||||||
|
|
||||||
if (typeof stream !== "string") {
|
|
||||||
stream.on("error", () => transcoder.destroy());
|
|
||||||
stream.pipe(transcoder);
|
|
||||||
}
|
|
||||||
|
|
||||||
return transcoder;
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
import { QueryType } from "../types/types";
|
|
||||||
import play from "play-dl";
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
|
|
||||||
// scary things below *sigh*
|
|
||||||
const spotifySongRegex = /https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:track\/|\?uri=spotify:track:)((\w|-){22})/;
|
|
||||||
const spotifyPlaylistRegex = /https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:playlist\/|\?uri=spotify:playlist:)((\w|-){22})/;
|
|
||||||
const spotifyAlbumRegex = /https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:album\/|\?uri=spotify:album:)((\w|-){22})/;
|
|
||||||
const vimeoRegex = /(http|https)?:\/\/(www\.|player\.)?vimeo\.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|video\/|)(\d+)(?:|\/\?)/;
|
|
||||||
const facebookRegex = /(https?:\/\/)(www\.|m\.)?(facebook|fb).com\/.*\/videos\/.*/;
|
|
||||||
const reverbnationRegex = /https:\/\/(www.)?reverbnation.com\/(.+)\/song\/(.+)/;
|
|
||||||
const attachmentRegex =
|
|
||||||
/^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/;
|
|
||||||
// scary things above *sigh*
|
|
||||||
|
|
||||||
class QueryResolver {
|
|
||||||
/**
|
|
||||||
* Query resolver
|
|
||||||
*/
|
|
||||||
private constructor() {} // eslint-disable-line @typescript-eslint/no-empty-function
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves the given search query
|
|
||||||
* @param {string} query The query
|
|
||||||
* @returns {QueryType}
|
|
||||||
*/
|
|
||||||
static async resolve(query: string): Promise<QueryType> {
|
|
||||||
if (await play.so_validate(query) === "track") return QueryType.SOUNDCLOUD_TRACK;
|
|
||||||
if (await play.so_validate(query) === "playlist" || query.includes("/sets/")) return QueryType.SOUNDCLOUD_PLAYLIST;
|
|
||||||
if (play.yt_validate(query) === "playlist") return QueryType.YOUTUBE_PLAYLIST;
|
|
||||||
if (play.yt_validate(query) === "video") return QueryType.YOUTUBE_VIDEO;
|
|
||||||
if (spotifySongRegex.test(query)) return QueryType.SPOTIFY_SONG;
|
|
||||||
if (spotifyPlaylistRegex.test(query)) return QueryType.SPOTIFY_PLAYLIST;
|
|
||||||
if (spotifyAlbumRegex.test(query)) return QueryType.SPOTIFY_ALBUM;
|
|
||||||
if (vimeoRegex.test(query)) return QueryType.VIMEO;
|
|
||||||
if (facebookRegex.test(query)) return QueryType.FACEBOOK;
|
|
||||||
if (reverbnationRegex.test(query)) return QueryType.REVERBNATION;
|
|
||||||
if (attachmentRegex.test(query)) return QueryType.ARBITRARY;
|
|
||||||
|
|
||||||
return QueryType.YOUTUBE_SEARCH;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses vimeo id from url
|
|
||||||
* @param {string} query The query
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
static async getVimeoID(query: string): Promise<string> {
|
|
||||||
return await QueryResolver.resolve(query) === QueryType.VIMEO
|
|
||||||
? query
|
|
||||||
.split("/")
|
|
||||||
.filter((x) => !!x)
|
|
||||||
.pop()
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { QueryResolver };
|
|
|
@ -1,117 +0,0 @@
|
||||||
import { StageChannel, VoiceChannel } from "discord.js";
|
|
||||||
import { TimeData } from "../types/types";
|
|
||||||
|
|
||||||
class Util {
|
|
||||||
/**
|
|
||||||
* Utils
|
|
||||||
*/
|
|
||||||
private constructor() {} // eslint-disable-line @typescript-eslint/no-empty-function
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates duration string
|
|
||||||
* @param {object} durObj The duration object
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
static durationString(durObj: Record<string, number>) {
|
|
||||||
return Object.values(durObj)
|
|
||||||
.map((m) => (isNaN(m) ? 0 : m))
|
|
||||||
.join(":");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses milliseconds to consumable time object
|
|
||||||
* @param {number} milliseconds The time in ms
|
|
||||||
* @returns {TimeData}
|
|
||||||
*/
|
|
||||||
static parseMS(milliseconds: number) {
|
|
||||||
const round = milliseconds > 0 ? Math.floor : Math.ceil;
|
|
||||||
|
|
||||||
return {
|
|
||||||
days: round(milliseconds / 86400000),
|
|
||||||
hours: round(milliseconds / 3600000) % 24,
|
|
||||||
minutes: round(milliseconds / 60000) % 60,
|
|
||||||
seconds: round(milliseconds / 1000) % 60
|
|
||||||
} as TimeData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds time code
|
|
||||||
* @param {TimeData} duration The duration object
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
static buildTimeCode(duration: TimeData) {
|
|
||||||
const items = Object.keys(duration);
|
|
||||||
const required = ["days", "hours", "minutes", "seconds"];
|
|
||||||
|
|
||||||
const parsed = items.filter((x) => required.includes(x)).map((m) => duration[m as keyof TimeData]);
|
|
||||||
const final = parsed
|
|
||||||
.slice(parsed.findIndex((x) => x !== 0))
|
|
||||||
.map((x) => x.toString().padStart(2, "0"))
|
|
||||||
.join(":");
|
|
||||||
|
|
||||||
return final.length <= 3 ? `0:${final.padStart(2, "0") || 0}` : final;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Picks last item of the given array
|
|
||||||
* @param {any[]} arr The array
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
static last<T = any>(arr: T[]): T {
|
|
||||||
if (!Array.isArray(arr)) return;
|
|
||||||
return arr[arr.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the voice channel is empty
|
|
||||||
* @param {VoiceChannel|StageChannel} channel The voice channel
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
static isVoiceEmpty(channel: VoiceChannel | StageChannel) {
|
|
||||||
return channel.members.filter((member) => !member.user.bot).size === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Safer require
|
|
||||||
* @param {string} id Node require id
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
static require(id: string) {
|
|
||||||
try {
|
|
||||||
return require(id);
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asynchronous timeout
|
|
||||||
* @param {number} time The time in ms to wait
|
|
||||||
* @returns {Promise<unknown>}
|
|
||||||
*/
|
|
||||||
static wait(time: number) {
|
|
||||||
return new Promise((r) => setTimeout(r, time).unref());
|
|
||||||
}
|
|
||||||
|
|
||||||
static noop() {} // eslint-disable-line @typescript-eslint/no-empty-function
|
|
||||||
|
|
||||||
static async getFetch() {
|
|
||||||
if ("fetch" in globalThis) return globalThis.fetch;
|
|
||||||
for (const lib of ["undici", "node-fetch"]) {
|
|
||||||
try {
|
|
||||||
return await import(lib).then((res) => res.fetch || res.default?.fetch || res.default);
|
|
||||||
} catch {
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
const res = require(lib);
|
|
||||||
if (res) return res.fetch || res.default?.fetch || res.default;
|
|
||||||
} catch {
|
|
||||||
// no?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Util };
|
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2020",
|
|
||||||
"module": "commonjs",
|
|
||||||
"declaration": true,
|
|
||||||
"outDir": "./dist",
|
|
||||||
"strict": true,
|
|
||||||
"strictNullChecks": false,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"pretty": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"importHelpers": true
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"src/**/*"
|
|
||||||
]
|
|
||||||
}
|
|
97
package-lock.json
generated
97
package-lock.json
generated
|
@ -20,6 +20,7 @@
|
||||||
"cron": "^2.1.0",
|
"cron": "^2.1.0",
|
||||||
"discord-api-types": "^0.37.5",
|
"discord-api-types": "^0.37.5",
|
||||||
"discord-giveaways": "^6.0.1",
|
"discord-giveaways": "^6.0.1",
|
||||||
|
"discord-player-play-dl": "^5.3.2",
|
||||||
"discord.js": "^14.3.0",
|
"discord.js": "^14.3.0",
|
||||||
"ejs": "^3.1.3",
|
"ejs": "^3.1.3",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
@ -1478,6 +1479,19 @@
|
||||||
"discord.js": ">=14.0.0"
|
"discord.js": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/discord-player-play-dl": {
|
||||||
|
"version": "5.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/discord-player-play-dl/-/discord-player-play-dl-5.3.2.tgz",
|
||||||
|
"integrity": "sha512-KBoQddJst0Xj8qFxikHCLlbHAVup5tDp+aE5rQIY5rG4t/D9As9LHKoiXIKnrjAfa9y4QvQeTMGJWDRYYBabsQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@discordjs/voice": "^0.11.0",
|
||||||
|
"libsodium-wrappers": "^0.7.10",
|
||||||
|
"play-dl": "^1.9.5",
|
||||||
|
"spotify-url-info": "^3.1.2",
|
||||||
|
"tiny-typed-emitter": "^2.1.0",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/discord.js": {
|
"node_modules/discord.js": {
|
||||||
"version": "14.3.0",
|
"version": "14.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.3.0.tgz",
|
||||||
|
@ -2485,6 +2499,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||||
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
|
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/himalaya": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/himalaya/-/himalaya-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-LLase1dHCRMel68/HZTFft0N0wti0epHr3nNY7ynpLbyZpmrKMQ8YIpiOV77TM97cNpC8Wb2n6f66IRggwdWPw=="
|
||||||
|
},
|
||||||
"node_modules/html-encoding-sniffer": {
|
"node_modules/html-encoding-sniffer": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz",
|
||||||
|
@ -2886,6 +2905,19 @@
|
||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/libsodium": {
|
||||||
|
"version": "0.7.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.10.tgz",
|
||||||
|
"integrity": "sha512-eY+z7hDrDKxkAK+QKZVNv92A5KYkxfvIshtBJkmg5TSiCnYqZP3i9OO9whE79Pwgm4jGaoHgkM4ao/b9Cyu4zQ=="
|
||||||
|
},
|
||||||
|
"node_modules/libsodium-wrappers": {
|
||||||
|
"version": "0.7.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz",
|
||||||
|
"integrity": "sha512-pO3F1Q9NPLB/MWIhehim42b/Fwb30JNScCNh8TcQ/kIc+qGLQch8ag8wb0keK3EP5kbGakk1H8Wwo7v+36rNQg==",
|
||||||
|
"dependencies": {
|
||||||
|
"libsodium": "^0.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/locate-path": {
|
"node_modules/locate-path": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||||
|
@ -4230,6 +4262,26 @@
|
||||||
"memory-pager": "^1.0.2"
|
"memory-pager": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/spotify-uri": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/spotify-uri/-/spotify-uri-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-mMstJ4dAMki6GbUjg94kp/h9ZH+7T7+ro/KUC00WVh+WKoLgMRrTKLkWMIwCZNO53Xa8DRHQw/6jwYtRZrVI3g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/spotify-url-info": {
|
||||||
|
"version": "3.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/spotify-url-info/-/spotify-url-info-3.1.8.tgz",
|
||||||
|
"integrity": "sha512-XxDRxDtTd9p1X22+dWweqB2bf41UVRZgyvq8VmLyY4iuClR/4/LGWOqcqG/zh9BwLUtony4818QrJ2bp2tbUkg==",
|
||||||
|
"dependencies": {
|
||||||
|
"himalaya": "~1.1.0",
|
||||||
|
"spotify-uri": "~3.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
|
@ -5784,6 +5836,19 @@
|
||||||
"serialize-javascript": "^6.0.0"
|
"serialize-javascript": "^6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"discord-player-play-dl": {
|
||||||
|
"version": "5.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/discord-player-play-dl/-/discord-player-play-dl-5.3.2.tgz",
|
||||||
|
"integrity": "sha512-KBoQddJst0Xj8qFxikHCLlbHAVup5tDp+aE5rQIY5rG4t/D9As9LHKoiXIKnrjAfa9y4QvQeTMGJWDRYYBabsQ==",
|
||||||
|
"requires": {
|
||||||
|
"@discordjs/voice": "^0.11.0",
|
||||||
|
"libsodium-wrappers": "^0.7.10",
|
||||||
|
"play-dl": "^1.9.5",
|
||||||
|
"spotify-url-info": "^3.1.2",
|
||||||
|
"tiny-typed-emitter": "^2.1.0",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"discord.js": {
|
"discord.js": {
|
||||||
"version": "14.3.0",
|
"version": "14.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.3.0.tgz",
|
||||||
|
@ -6561,6 +6626,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||||
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
|
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
|
||||||
},
|
},
|
||||||
|
"himalaya": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/himalaya/-/himalaya-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-LLase1dHCRMel68/HZTFft0N0wti0epHr3nNY7ynpLbyZpmrKMQ8YIpiOV77TM97cNpC8Wb2n6f66IRggwdWPw=="
|
||||||
|
},
|
||||||
"html-encoding-sniffer": {
|
"html-encoding-sniffer": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz",
|
||||||
|
@ -6847,6 +6917,19 @@
|
||||||
"type-check": "~0.4.0"
|
"type-check": "~0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"libsodium": {
|
||||||
|
"version": "0.7.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.10.tgz",
|
||||||
|
"integrity": "sha512-eY+z7hDrDKxkAK+QKZVNv92A5KYkxfvIshtBJkmg5TSiCnYqZP3i9OO9whE79Pwgm4jGaoHgkM4ao/b9Cyu4zQ=="
|
||||||
|
},
|
||||||
|
"libsodium-wrappers": {
|
||||||
|
"version": "0.7.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz",
|
||||||
|
"integrity": "sha512-pO3F1Q9NPLB/MWIhehim42b/Fwb30JNScCNh8TcQ/kIc+qGLQch8ag8wb0keK3EP5kbGakk1H8Wwo7v+36rNQg==",
|
||||||
|
"requires": {
|
||||||
|
"libsodium": "^0.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"locate-path": {
|
"locate-path": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||||
|
@ -7802,6 +7885,20 @@
|
||||||
"memory-pager": "^1.0.2"
|
"memory-pager": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"spotify-uri": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/spotify-uri/-/spotify-uri-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-mMstJ4dAMki6GbUjg94kp/h9ZH+7T7+ro/KUC00WVh+WKoLgMRrTKLkWMIwCZNO53Xa8DRHQw/6jwYtRZrVI3g=="
|
||||||
|
},
|
||||||
|
"spotify-url-info": {
|
||||||
|
"version": "3.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/spotify-url-info/-/spotify-url-info-3.1.8.tgz",
|
||||||
|
"integrity": "sha512-XxDRxDtTd9p1X22+dWweqB2bf41UVRZgyvq8VmLyY4iuClR/4/LGWOqcqG/zh9BwLUtony4818QrJ2bp2tbUkg==",
|
||||||
|
"requires": {
|
||||||
|
"himalaya": "~1.1.0",
|
||||||
|
"spotify-uri": "~3.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"statuses": {
|
"statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
"cron": "^2.1.0",
|
"cron": "^2.1.0",
|
||||||
"discord-api-types": "^0.37.5",
|
"discord-api-types": "^0.37.5",
|
||||||
"discord-giveaways": "^6.0.1",
|
"discord-giveaways": "^6.0.1",
|
||||||
|
"discord-player-play-dl": "^5.3.2",
|
||||||
"discord.js": "^14.3.0",
|
"discord.js": "^14.3.0",
|
||||||
"ejs": "^3.1.3",
|
"ejs": "^3.1.3",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
|
Loading…
Reference in a new issue