This commit is contained in:
Snowflake107 2021-04-21 10:53:33 +05:45
parent e1cc416c84
commit 9d5f065582
14 changed files with 3427 additions and 106 deletions

3
.gitignore vendored
View file

@ -9,3 +9,6 @@ lib/
# error logs # error logs
yarn-error.log yarn-error.log
# demo
demo/

View file

@ -3,3 +3,4 @@ tslint.json
tsconfig.json tsconfig.json
.prettierrc .prettierrc
test/ test/
demo/

View file

@ -0,0 +1,2 @@
# Discord Player Extractor API
The extractor API

139
docs/general/Welcome.md Normal file
View file

@ -0,0 +1,139 @@
# Discord Player
Complete framework to facilitate music commands using **[discord.js](https://discord.js.org)**.
[![downloadsBadge](https://img.shields.io/npm/dt/discord-player?style=for-the-badge)](https://npmjs.com/discord-player)
[![versionBadge](https://img.shields.io/npm/v/discord-player?style=for-the-badge)](https://npmjs.com/discord-player)
## Installation
### Install **[discord-player](https://npmjs.com/package/discord-player)**
```sh
$ npm install --save discord-player
```
### Install **[@discordjs/opus](https://npmjs.com/package/@discordjs/opus)**
```sh
$ npm install --save @discordjs/opus
```
### Install FFmpeg or Avconv
- Official FFMPEG Website: **[https://www.ffmpeg.org/download.html](https://www.ffmpeg.org/download.html)**
- Node Module (FFMPEG): **[https://npmjs.com/package/ffmpeg-static](https://npmjs.com/package/ffmpeg-static)**
- Avconv: **[https://libav.org/download](https://libav.org/download)**
# Features
- Simple & easy to use 🤘
- Beginner friendly 😱
- Audio filters 🎸
- Lightweight 🛬
- Custom extractors support 🌌
- Lyrics 📃
- Multiple sources support ✌
- Play in multiple servers at the same time 🚗
## [Documentation](https://discord-player.js.org)
## Getting Started
Here is the code you will need to get started with discord-player. Then, you will be able to use `client.player` everywhere in your code!
```js
const Discord = require("discord.js"),
client = new Discord.Client,
settings = {
prefix: "!",
token: "Your Discord Token"
};
const { Player } = require("discord-player");
// Create a new Player (you don't need any API Key)
const player = new Player(client);
// To easily access the player
client.player = player;
// add the trackStart event so when a song will be played this message will be sent
client.player.on("trackStart", (message, track) => message.channel.send(`Now playing ${track.title}...`))
client.once("ready", () => {
console.log("I'm ready !");
});
client.on("message", async (message) => {
const args = message.content.slice(settings.prefix.length).trim().split(/ +/g);
const command = args.shift().toLowerCase();
// !play Despacito
// will play "Despacito" in the voice channel
if(command === "play"){
client.player.play(message, args[0]);
// as we registered the event above, no need to send a success message here
}
});
client.login(settings.token);
```
## Supported websites
By default, discord-player supports **YouTube**, **Spotify** and **SoundCloud** streams only.
### Optional dependencies
Discord Player provides an **Extractor API** that enables you to use your custom stream extractor with it. Some packages have been made by the community to add new features using this API.
#### [@discord-player/extractor](https://github.com/Snowflake107/discord-player-extractors) (optional)
Optional package that adds support for `vimeo`, `reverbnation`, `facebook`, `attachment links` and `lyrics`.
You just need to install it using `npm i --save @discord-player/extractor` (discord-player will automatically detect and use it).
#### [@discord-player/downloader](https://github.com/DevSnowflake/discord-player-downloader) (optional)
`@discord-player/downloader` is an optional package that brings support for +700 websites. The documentation is available [here](https://github.com/DevSnowflake/discord-player-downloader).
## Examples of bots made with Discord Player
These bots are made by the community, they can help you build your own!
* [AtlantaBot](https://github.com/Androz2091/AtlantaBot) by [Androz2091](https://github.com/Androz2091)
* [Discord-Music](https://github.com/inhydrox/discord-music) by [inhydrox](https://github.com/inhydrox)
* [Music-bot](https://github.com/ZerioDev/Music-bot) by [ZerioDev](https://github.com/ZerioDev)
## FAQ
### How to use cookies?
```js
const player = new Player(client, {
ytdlDownloadOptions: {
requestOptions: {
headers: {
cookie: "YOUR_YOUTUBE_COOKIE"
}
}
}
});
```
### How to use custom proxies?
```js
const HttpsProxyAgent = require("https-proxy-agent");
// Remove "user:pass@" if you don't need to authenticate to your proxy.
const proxy = "http://user:pass@111.111.111.111:8080";
const agent = HttpsProxyAgent(proxy);
const player = new Player(client, {
ytdlDownloadOptions: {
requestOptions: { agent }
}
});
```

8
docs/index.yml Normal file
View file

@ -0,0 +1,8 @@
- name: General
files:
- name: Welcome
path: welcome.md
- name: Extractors
files:
- name: Extractors
path: extractor.md

17
jsdoc.json Normal file
View file

@ -0,0 +1,17 @@
{
"source": {
"includePattern": ".+\\.ts(doc|x)?$"
},
"plugins": [
"plugins/markdown",
"node_modules/jsdoc-babel"
],
"babel": {
"extensions": ["ts"],
"babelrc": false,
"presets": [
["@babel/preset-env", { "targets": { "node": true } }],
"@babel/preset-typescript"
]
}
}

View file

@ -11,7 +11,9 @@
"test": "yarn build && cd test && node index.js", "test": "yarn build && cd test && node index.js",
"build": "tsc", "build": "tsc",
"format": "prettier --write \"src/**/*.ts\"", "format": "prettier --write \"src/**/*.ts\"",
"lint": "tslint -p tsconfig.json" "lint": "tslint -p tsconfig.json",
"docs": "docgen --jsdoc jsdoc.json --source src/*.ts src/**/*.ts --custom docs/index.yml --output demo/docs.json",
"docs:test": "docgen --jsdoc jsdoc.json --source src/*.ts src/**/*.ts --custom docs/index.yml"
}, },
"funding": "https://github.com/Androz2091/discord-player?sponsor=1", "funding": "https://github.com/Androz2091/discord-player?sponsor=1",
"contributors": [ "contributors": [
@ -45,11 +47,17 @@
"ytdl-core": "^4.5.0" "ytdl-core": "^4.5.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.13.16",
"@babel/core": "^7.13.16",
"@babel/preset-env": "^7.13.15",
"@babel/preset-typescript": "^7.13.0",
"@discord-player/extractor": "^2.0.0", "@discord-player/extractor": "^2.0.0",
"@discordjs/opus": "^0.5.0", "@discordjs/opus": "^0.5.0",
"@types/node": "^14.14.41", "@types/node": "^14.14.41",
"@types/ws": "^7.4.1", "@types/ws": "^7.4.1",
"discord.js": "^12.5.3", "discord.js": "^12.5.3",
"discord.js-docgen": "discordjs/docgen#ts-patch",
"jsdoc-babel": "^0.5.0",
"prettier": "^2.2.1", "prettier": "^2.2.1",
"tslint": "^6.1.3", "tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0", "tslint-config-prettier": "^1.18.0",

View file

@ -28,13 +28,23 @@ const SoundCloud = new SoundCloudClient();
export class Player extends EventEmitter { export class Player extends EventEmitter {
/** /**
* The discord client that instantiated this player * The discord client that instantiated this player
* @type {Discord.Client}
*/ */
public client!: Client; public client!: Client;
/**
* The player options
*/
public options: PlayerOptionsType; public options: PlayerOptionsType;
/**
* The audio filters
*/
public filters: typeof AudioFilters; public filters: typeof AudioFilters;
/** /**
* The collection of queues in this player * The collection of queues in this player
* @type {Discord.Collection}
*/ */
public queues = new Collection<Snowflake, Queue>(); public queues = new Collection<Snowflake, Queue>();
private _resultsCollectors = new Collection<string, Collector<Snowflake, Message>>(); private _resultsCollectors = new Collection<string, Collector<Snowflake, Message>>();
@ -42,13 +52,14 @@ export class Player extends EventEmitter {
/** /**
* The extractor model collection * The extractor model collection
* @type {Discord.Collection}
*/ */
public Extractors = new Collection<string, ExtractorModel>(); public Extractors = new Collection<string, ExtractorModel>();
/** /**
* Creates new Player instance * Creates new Player instance
* @param client The discord.js client * @param {Discord.Client} client The discord.js client
* @param options Player options * @param {PlayerOptionsType} options Player options
*/ */
constructor(client: Client, options?: PlayerOptionsType) { constructor(client: Client, options?: PlayerOptionsType) {
super(); super();
@ -58,17 +69,11 @@ export class Player extends EventEmitter {
enumerable: false enumerable: false
}); });
/**
* The player options
*/
this.options = Object.assign({}, PlayerOptions, options ?? {}); this.options = Object.assign({}, PlayerOptions, options ?? {});
// check FFmpeg // check FFmpeg
void Util.alertFFmpeg(); void Util.alertFFmpeg();
/**
* The audio filters
*/
this.filters = AudioFilters; this.filters = AudioFilters;
this.client.on('voiceStateUpdate', (o, n) => void this._handleVoiceStateUpdate(o, n)); this.client.on('voiceStateUpdate', (o, n) => void this._handleVoiceStateUpdate(o, n));
@ -86,8 +91,8 @@ export class Player extends EventEmitter {
/** /**
* Define custom extractor in this player * Define custom extractor in this player
* @param extractorName The extractor name * @param {string} extractorName The extractor name
* @param extractor The extractor itself * @param {any} extractor The extractor itself
*/ */
use(extractorName: string, extractor: any): Player { use(extractorName: string, extractor: any): Player {
if (!extractorName) throw new PlayerError('Missing extractor name!', 'PlayerExtractorError'); if (!extractorName) throw new PlayerError('Missing extractor name!', 'PlayerExtractorError');
@ -106,7 +111,7 @@ export class Player extends EventEmitter {
/** /**
* Remove existing extractor from this player * Remove existing extractor from this player
* @param extractorName The extractor name * @param {string} extractorName The extractor name
*/ */
unuse(extractorName: string): boolean { unuse(extractorName: string): boolean {
if (!extractorName) throw new PlayerError('Missing extractor name!', 'PlayerExtractorError'); if (!extractorName) throw new PlayerError('Missing extractor name!', 'PlayerExtractorError');
@ -359,9 +364,9 @@ export class Player extends EventEmitter {
/** /**
* Play a song * Play a song
* @param message The discord.js message object * @param {Discord.Message} message The discord.js message object
* @param query Search query, can be `Player.Track` instance * @param {string|Track} query Search query, can be `Player.Track` instance
* @param firstResult If it should play the first result * @param {boolean} [firstResult] If it should play the first result
* @example await player.play(message, "never gonna give you up", true) * @example await player.play(message, "never gonna give you up", true)
*/ */
async play(message: Message, query: string | Track, firstResult?: boolean): Promise<void> { async play(message: Message, query: string | Track, firstResult?: boolean): Promise<void> {
@ -444,7 +449,7 @@ export class Player extends EventEmitter {
/** /**
* Checks if this player is playing in a server * Checks if this player is playing in a server
* @param message The message object * @param {Discord.Message} message The message object
*/ */
isPlaying(message: Message): boolean { isPlaying(message: Message): boolean {
return this.queues.some((g) => g.guildID === message.guild.id); return this.queues.some((g) => g.guildID === message.guild.id);
@ -452,7 +457,7 @@ export class Player extends EventEmitter {
/** /**
* Returns guild queue object * Returns guild queue object
* @param message The message object * @param {Discord.Message} message The message object
*/ */
getQueue(message: Message): Queue { getQueue(message: Message): Queue {
return this.queues.find((g) => g.guildID === message.guild.id); return this.queues.find((g) => g.guildID === message.guild.id);
@ -460,8 +465,8 @@ export class Player extends EventEmitter {
/** /**
* Sets audio filters in this player * Sets audio filters in this player
* @param message The message object * @param {Discord.Message} message The message object
* @param newFilters Audio filters object * @param {QueueFilters} newFilters Audio filters object
*/ */
setFilters(message: Message, newFilters: QueueFilters): Promise<void> { setFilters(message: Message, newFilters: QueueFilters): Promise<void> {
return new Promise((resolve) => { return new Promise((resolve) => {
@ -495,9 +500,9 @@ export class Player extends EventEmitter {
/** /**
* Sets track position * Sets track position
* @param message The message object * @param {Discord.Message} message The message object
* @param time Time in ms to set * @param {number} time Time in ms to set
* @alias seek * @alias Player.seek
*/ */
setPosition(message: Message, time: number): Promise<void> { setPosition(message: Message, time: number): Promise<void> {
return new Promise((resolve) => { return new Promise((resolve) => {
@ -519,9 +524,9 @@ export class Player extends EventEmitter {
/** /**
* Sets track position * Sets track position
* @param message The message object * @param {Discord.Message} message The message object
* @param time Time in ms to set * @param {number} time Time in ms to set
* @alias setPosition * @alias Player.setPosition
*/ */
seek(message: Message, time: number): Promise<void> { seek(message: Message, time: number): Promise<void> {
return this.setPosition(message, time); return this.setPosition(message, time);
@ -529,7 +534,7 @@ export class Player extends EventEmitter {
/** /**
* Skips current track * Skips current track
* @param message The message object * @param {Discord.Message} message The message object
*/ */
skip(message: Message): boolean { skip(message: Message): boolean {
const queue = this.getQueue(message); const queue = this.getQueue(message);
@ -550,8 +555,8 @@ export class Player extends EventEmitter {
/** /**
* Moves to a new voice channel * Moves to a new voice channel
* @param message The message object * @param {Discord.Message} message The message object
* @param channel New voice channel to move to * @param {Discord.VoiceChannel} channel New voice channel to move to
*/ */
moveTo(message: Message, channel?: VoiceChannel): boolean { moveTo(message: Message, channel?: VoiceChannel): boolean {
if (!channel || channel.type !== 'voice') return; if (!channel || channel.type !== 'voice') return;
@ -577,7 +582,7 @@ export class Player extends EventEmitter {
/** /**
* Pause the playback * Pause the playback
* @param message The message object * @param {Discord.Message} message The message object
*/ */
pause(message: Message): boolean { pause(message: Message): boolean {
const queue = this.getQueue(message); const queue = this.getQueue(message);
@ -597,7 +602,7 @@ export class Player extends EventEmitter {
/** /**
* Resume the playback * Resume the playback
* @param message The message object * @param {Discord.Message} message The message object
*/ */
resume(message: Message): boolean { resume(message: Message): boolean {
const queue = this.getQueue(message); const queue = this.getQueue(message);
@ -617,7 +622,7 @@ export class Player extends EventEmitter {
/** /**
* Stops the player * Stops the player
* @param message The message object * @param {Discord.Message} message The message object
*/ */
stop(message: Message): boolean { stop(message: Message): boolean {
const queue = this.getQueue(message); const queue = this.getQueue(message);
@ -641,8 +646,8 @@ export class Player extends EventEmitter {
/** /**
* Sets music volume * Sets music volume
* @param message The message object * @param {Discord.Message} message The message object
* @param percent The volume percentage/amount to set * @param {number} percent The volume percentage/amount to set
*/ */
setVolume(message: Message, percent: number): boolean { setVolume(message: Message, percent: number): boolean {
const queue = this.getQueue(message); const queue = this.getQueue(message);
@ -663,7 +668,7 @@ export class Player extends EventEmitter {
/** /**
* Clears the queue * Clears the queue
* @param message The message object * @param {Discord.Message} message The message object
*/ */
clearQueue(message: Message): boolean { clearQueue(message: Message): boolean {
const queue = this.getQueue(message); const queue = this.getQueue(message);
@ -679,7 +684,7 @@ export class Player extends EventEmitter {
/** /**
* Plays previous track * Plays previous track
* @param message The message object * @param {Discord.Message} message The message object
*/ */
back(message: Message): boolean { back(message: Message): boolean {
const queue = this.getQueue(message); const queue = this.getQueue(message);
@ -701,8 +706,8 @@ export class Player extends EventEmitter {
/** /**
* Sets repeat mode * Sets repeat mode
* @param message The message object * @param {Discord.Message} message The message object
* @param enabled If it should enable the repeat mode * @param {boolean} enabled If it should enable the repeat mode
*/ */
setRepeatMode(message: Message, enabled: boolean): boolean { setRepeatMode(message: Message, enabled: boolean): boolean {
const queue = this.getQueue(message); const queue = this.getQueue(message);
@ -718,8 +723,8 @@ export class Player extends EventEmitter {
/** /**
* Sets loop mode * Sets loop mode
* @param message The message object * @param {Discord.Message} message The message object
* @param enabled If it should enable the loop mode * @param {boolean} enabled If it should enable the loop mode
*/ */
setLoopMode(message: Message, enabled: boolean): boolean { setLoopMode(message: Message, enabled: boolean): boolean {
const queue = this.getQueue(message); const queue = this.getQueue(message);
@ -735,7 +740,7 @@ export class Player extends EventEmitter {
/** /**
* Returns currently playing track * Returns currently playing track
* @param message The message object * @param {Discord.Message} message The message object
*/ */
nowPlaying(message: Message): Track { nowPlaying(message: Message): Track {
const queue = this.getQueue(message); const queue = this.getQueue(message);
@ -749,7 +754,7 @@ export class Player extends EventEmitter {
/** /**
* Shuffles the queue * Shuffles the queue
* @param message The message object * @param {Discord.Message} message The message object
*/ */
shuffle(message: Message): Queue { shuffle(message: Message): Queue {
const queue = this.getQueue(message); const queue = this.getQueue(message);
@ -772,8 +777,8 @@ export class Player extends EventEmitter {
/** /**
* Removes specified track * Removes specified track
* @param message The message object * @param {Discord.Message} message The message object
* @param track The track object/id to remove * @param {Track|number} track The track object/id to remove
*/ */
remove(message: Message, track: Track | number): Track { remove(message: Message, track: Track | number): Track {
const queue = this.getQueue(message); const queue = this.getQueue(message);
@ -800,8 +805,8 @@ export class Player extends EventEmitter {
/** /**
* Returns time code of currently playing song * Returns time code of currently playing song
* @param message The message object * @param {Discord.Message} message The message object
* @param queueTime If it should make the time code of the whole queue * @param {boolean} [queueTime] If it should make the time code of the whole queue
*/ */
getTimeCode(message: Message, queueTime?: boolean): { current: string; end: string } { getTimeCode(message: Message, queueTime?: boolean): { current: string; end: string } {
const queue = this.getQueue(message); const queue = this.getQueue(message);
@ -824,8 +829,8 @@ export class Player extends EventEmitter {
/** /**
* Creates progressbar * Creates progressbar
* @param message The message object * @param {Discord.Message} message The message object
* @param options Progressbar options * @param {PlayerProgressbarOptions} [options] Progressbar options
*/ */
createProgressBar(message: Message, options?: PlayerProgressbarOptions): string { createProgressBar(message: Message, options?: PlayerProgressbarOptions): string {
const queue = this.getQueue(message); const queue = this.getQueue(message);
@ -872,7 +877,7 @@ export class Player extends EventEmitter {
/** /**
* Gets lyrics of a song * Gets lyrics of a song
* @param query Search query * @param {string} query Search query
* @example const lyrics = await player.lyrics("alan walker faded") * @example const lyrics = await player.lyrics("alan walker faded")
* message.channel.send(lyrics.lyrics); * message.channel.send(lyrics.lyrics);
*/ */
@ -888,8 +893,8 @@ export class Player extends EventEmitter {
/** /**
* Toggle autoplay for youtube streams * Toggle autoplay for youtube streams
* @param message The message object * @param {Discord.Message} message The message object
* @param enable Enable/Disable autoplay * @param {boolean} enable Enable/Disable autoplay
*/ */
setAutoPlay(message: Message, enable: boolean): boolean { setAutoPlay(message: Message, enable: boolean): boolean {
const queue = this.getQueue(message); const queue = this.getQueue(message);

View file

@ -6,12 +6,13 @@ class ExtractorModel {
/** /**
* Model for raw Discord Player extractors * Model for raw Discord Player extractors
* @param extractorName Name of the extractor * @param {string} extractorName Name of the extractor
* @param data Extractor object * @param {Object} data Extractor object
*/ */
constructor(extractorName: string, data: any) { constructor(extractorName: string, data: any) {
/** /**
* The extractor name * The extractor name
* @type {string}
*/ */
this.name = extractorName; this.name = extractorName;
@ -20,7 +21,7 @@ class ExtractorModel {
/** /**
* Method to handle requests from `Player.play()` * Method to handle requests from `Player.play()`
* @param query Query to handle * @param {string} query Query to handle
*/ */
async handle(query: string): Promise<ExtractorModelData> { async handle(query: string): Promise<ExtractorModelData> {
const data = await this._raw.getInfo(query); const data = await this._raw.getInfo(query);
@ -40,7 +41,7 @@ class ExtractorModel {
/** /**
* Method used by Discord Player to validate query with this extractor * Method used by Discord Player to validate query with this extractor
* @param query The query to validate * @param {string} query The query to validate
*/ */
validate(query: string): boolean { validate(query: string): boolean {
return Boolean(this._raw.validate(query)); return Boolean(this._raw.validate(query));
@ -48,6 +49,7 @@ class ExtractorModel {
/** /**
* The extractor version * The extractor version
* @type {string}
*/ */
get version(): string { get version(): string {
return this._raw.version ?? '0.0.0'; return this._raw.version ?? '0.0.0';
@ -55,6 +57,7 @@ class ExtractorModel {
/** /**
* If player should mark this extractor as important * If player should mark this extractor as important
* @type {boolean}
*/ */
get important(): boolean { get important(): boolean {
return Boolean(this._raw.important); return Boolean(this._raw.important);

View file

@ -24,12 +24,16 @@ export class Queue extends EventEmitter {
public filters: QueueFilters; public filters: QueueFilters;
public additionalStreamTime: number; public additionalStreamTime: number;
public firstMessage: Message; public firstMessage: Message;
/**
* @type {boolean}
*/
public autoPlay = false; public autoPlay = false;
/** /**
* Queue constructor * Queue constructor
* @param player The player that instantiated this Queue * @param {Player} player The player that instantiated this Queue
* @param message The message object * @param {Discord.Message} message The message object
*/ */
constructor(player: Player, message: Message) { constructor(player: Player, message: Message) {
super(); super();
@ -38,66 +42,79 @@ export class Queue extends EventEmitter {
/** /**
* ID of the guild assigned to this queue * ID of the guild assigned to this queue
* @type {Discord.Snowflake}
*/ */
this.guildID = message.guild.id; this.guildID = message.guild.id;
/** /**
* The voice connection of this queue * The voice connection of this queue
* @type {Discord.VoiceConnection}
*/ */
this.voiceConnection = null; this.voiceConnection = null;
/** /**
* Tracks of this queue * Tracks of this queue
* @type {Track[]}
*/ */
this.tracks = []; this.tracks = [];
/** /**
* Previous tracks of this queue * Previous tracks of this queue
* @type {Track[]}
*/ */
this.previousTracks = []; this.previousTracks = [];
/** /**
* If the player of this queue is stopped * If the player of this queue is stopped
* @type {boolean}
*/ */
this.stopped = false; this.stopped = false;
/** /**
* If last track was skipped * If last track was skipped
* @type {boolean}
*/ */
this.lastSkipped = false; this.lastSkipped = false;
/** /**
* Queue volume * Queue volume
* @type {number}
*/ */
this.volume = 100; this.volume = 100;
/** /**
* If the player of this queue is paused * If the player of this queue is paused
* @type {boolean}
*/ */
this.paused = Boolean(this.voiceConnection?.dispatcher?.paused); this.paused = Boolean(this.voiceConnection?.dispatcher?.paused);
/** /**
* If repeat mode is enabled in this queue * If repeat mode is enabled in this queue
* @type {boolean}
*/ */
this.repeatMode = false; this.repeatMode = false;
/** /**
* If loop mode is enabled in this queue * If loop mode is enabled in this queue
* @type {boolean}
*/ */
this.loopMode = false; this.loopMode = false;
/** /**
* The additional calculated stream time * The additional calculated stream time
* @type {number}
*/ */
this.additionalStreamTime = 0; this.additionalStreamTime = 0;
/** /**
* The initial message object * The initial message object
* @type {Discord.Message}
*/ */
this.firstMessage = message; this.firstMessage = message;
/** /**
* The audio filters in this queue * The audio filters in this queue
* @type {QueueFilters}
*/ */
this.filters = {}; this.filters = {};
@ -108,6 +125,7 @@ export class Queue extends EventEmitter {
/** /**
* Currently playing track * Currently playing track
* @type {Track}
*/ */
get playing(): Track { get playing(): Track {
return this.tracks[0]; return this.tracks[0];
@ -115,6 +133,7 @@ export class Queue extends EventEmitter {
/** /**
* Calculated volume of this queue * Calculated volume of this queue
* @type {number}
*/ */
get calculatedVolume(): number { get calculatedVolume(): number {
return this.filters.normalizer ? this.volume + 70 : this.volume; return this.filters.normalizer ? this.volume + 70 : this.volume;
@ -122,6 +141,7 @@ export class Queue extends EventEmitter {
/** /**
* Total duration * Total duration
* @type {number}
*/ */
get totalTime(): number { get totalTime(): number {
return this.tracks.length > 0 ? this.tracks.map((t) => t.durationMS).reduce((p, c) => p + c) : 0; return this.tracks.length > 0 ? this.tracks.map((t) => t.durationMS).reduce((p, c) => p + c) : 0;
@ -129,6 +149,7 @@ export class Queue extends EventEmitter {
/** /**
* Current stream time * Current stream time
* @type {number}
*/ */
get currentStreamTime(): number { get currentStreamTime(): number {
return this.voiceConnection?.dispatcher?.streamTime + this.additionalStreamTime || 0; return this.voiceConnection?.dispatcher?.streamTime + this.additionalStreamTime || 0;
@ -136,7 +157,8 @@ export class Queue extends EventEmitter {
/** /**
* Sets audio filters in this player * Sets audio filters in this player
* @param filters Audio filters to set * @param {QueueFilters} filters Audio filters to set
* @type {Promise<void>}
*/ */
setFilters(filters: QueueFilters): Promise<void> { setFilters(filters: QueueFilters): Promise<void> {
return this.player.setFilters(this.firstMessage, filters); return this.player.setFilters(this.firstMessage, filters);
@ -144,6 +166,7 @@ export class Queue extends EventEmitter {
/** /**
* Returns array of all enabled filters * Returns array of all enabled filters
* @type {string[]}
*/ */
getFiltersEnabled(): string[] { getFiltersEnabled(): string[] {
const filters: string[] = []; const filters: string[] = [];
@ -157,6 +180,7 @@ export class Queue extends EventEmitter {
/** /**
* Returns all disabled filters * Returns all disabled filters
* @type {string[]}
*/ */
getFiltersDisabled(): string[] { getFiltersDisabled(): string[] {
const enabled = this.getFiltersEnabled(); const enabled = this.getFiltersEnabled();
@ -166,6 +190,7 @@ export class Queue extends EventEmitter {
/** /**
* String representation of this Queue * String representation of this Queue
* @type {string}
*/ */
toString(): string { toString(): string {
return `<Queue ${this.guildID}>`; return `<Queue ${this.guildID}>`;

View file

@ -6,63 +6,74 @@ import Queue from './Queue';
export class Track { export class Track {
/** /**
* The player that instantiated this Track * The player that instantiated this Track
* @type {Player}
*/ */
public player!: Player; public player!: Player;
/** /**
* Title of this track * Title of this track
* @type {string}
*/ */
public title!: string; public title!: string;
/** /**
* Description of this track * Description of this track
* @type {string}
*/ */
public description!: string; public description!: string;
/** /**
* Author of this track * Author of this track
* @type {string}
*/ */
public author!: string; public author!: string;
/** /**
* Link of this track * Link of this track
* @type {string}
*/ */
public url!: string; public url!: string;
/** /**
* Thumbnail of this track * Thumbnail of this track
* @type {string}
*/ */
public thumbnail!: string; public thumbnail!: string;
/** /**
* Duration of this track * Duration of this track
* @type {string}
*/ */
public duration!: string; public duration!: string;
/** /**
* View count of this track * View count of this track
* @type {number}
*/ */
public views!: number; public views!: number;
/** /**
* Person who requested this track * Person who requested this track
* @type {Discord.User}
*/ */
public requestedBy!: User; public requestedBy!: User;
/** /**
* If this track belongs to a playlist * If this track belongs to a playlist
* @type {boolean}
*/ */
public fromPlaylist!: boolean; public fromPlaylist!: boolean;
/** /**
* Raw data of this track * Raw data of this track
* @type {TrackData}
*/ */
public raw!: TrackData; public raw!: TrackData;
/** /**
* Track constructor * Track constructor
* @param player The player that instantiated this Track * @param {Player} player The player that instantiated this Track
* @param data Track data * @param {TrackData} data Track data
*/ */
constructor(player: Player, data: TrackData) { constructor(player: Player, data: TrackData) {
Object.defineProperty(this, 'player', { value: player, enumerable: false }); Object.defineProperty(this, 'player', { value: player, enumerable: false });
@ -87,6 +98,7 @@ export class Track {
/** /**
* The queue in which this track is located * The queue in which this track is located
* @type {Queue}
*/ */
get queue(): Queue { get queue(): Queue {
return this.player.queues.find((q) => q.tracks.includes(this)); return this.player.queues.find((q) => q.tracks.includes(this));
@ -94,6 +106,7 @@ export class Track {
/** /**
* The track duration in millisecond * The track duration in millisecond
* @type {number}
*/ */
get durationMS(): number { get durationMS(): number {
const times = (n: number, t: number) => { const times = (n: number, t: number) => {
@ -111,6 +124,7 @@ export class Track {
/** /**
* String representation of this track * String representation of this track
* @type {string}
*/ */
toString(): string { toString(): string {
return `${this.title} by ${this.author}`; return `${this.title} by ${this.author}`;

View file

@ -47,11 +47,7 @@ const FilterList = {
return `${Object.values(this).join(',')}`; return `${Object.values(this).join(',')}`;
}, },
/** create(filter?: FiltersName[]): string {
* Creates single string of audio filters
* @param filter Array of AudioFilters name
*/
create(filter?: FiltersName[]) {
if (!filter || !Array.isArray(filter)) return this.toString(); if (!filter || !Array.isArray(filter)) return this.toString();
return filter return filter
.filter((predicate) => typeof predicate === 'string') .filter((predicate) => typeof predicate === 'string')
@ -59,27 +55,13 @@ const FilterList = {
.join(','); .join(',');
}, },
/** define(filterName: string, value: string): void {
* Defines custom filter if (typeof this[filterName as FiltersName] && typeof this[filterName as FiltersName] === 'function') return;
* @param filterName The filter name
* @param value FFmpeg args to use with -af
* @example Player.AudioFilters.define("3D", "apulsator=hz=0.125")
*
* player.setFilters(message, { "3D": true })
*/
define(filterName: string, value: string) {
/* @ts-ignore */
if (typeof this[filterName] && typeof this[filterName] === 'function') return;
/* @ts-ignore */ this[filterName as FiltersName] = value;
this[filterName] = value;
}, },
/** defineBulk(filterArray: { name: string; value: string }[]): void {
* Defines filters in bulk
* @param filterArray Array of filters containing object with `name` and `value` prop
*/
defineBulk(filterArray: { name: string; value: string }[]) {
filterArray.forEach((arr) => this.define(arr.name, arr.value)); filterArray.forEach((arr) => this.define(arr.name, arr.value));
} }
}; };

View file

@ -15,10 +15,17 @@ 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*)?$/; 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*)?$/;
export class Util { export class Util {
/**
* Static Player Util class
*/
constructor() { constructor() {
throw new Error(`The ${this.constructor.name} class is static and cannot be instantiated!`); throw new Error(`The ${this.constructor.name} class is static and cannot be instantiated!`);
} }
/**
* Checks FFmpeg Version
* @param {boolean} [force] If it should forcefully get the version
*/
static getFFmpegVersion(force?: boolean): string { static getFFmpegVersion(force?: boolean): string {
try { try {
const info = FFmpeg.getInfo(Boolean(force)); const info = FFmpeg.getInfo(Boolean(force));
@ -29,11 +36,18 @@ export class Util {
} }
} }
/**
* Checks FFmpeg
* @param {boolean} [force] If it should forcefully get the version
*/
static checkFFmpeg(force?: boolean): boolean { static checkFFmpeg(force?: boolean): boolean {
const version = Util.getFFmpegVersion(force); const version = Util.getFFmpegVersion(force);
return version === null ? false : true; return version === null ? false : true;
} }
/**
* Alerts if FFmpeg is not available
*/
static alertFFmpeg(): void { static alertFFmpeg(): void {
const hasFFmpeg = Util.checkFFmpeg(); const hasFFmpeg = Util.checkFFmpeg();
@ -43,6 +57,10 @@ export class Util {
); );
} }
/**
* Resolves query type
* @param {string} query The query
*/
static getQueryType(query: string): QueryType { static getQueryType(query: string): QueryType {
if (SoundcloudValidateURL(query) && !query.includes('/sets/')) return 'soundcloud_track'; if (SoundcloudValidateURL(query) && !query.includes('/sets/')) return 'soundcloud_track';
if (SoundcloudValidateURL(query) && query.includes('/sets/')) return 'soundcloud_playlist'; if (SoundcloudValidateURL(query) && query.includes('/sets/')) return 'soundcloud_playlist';
@ -59,10 +77,18 @@ export class Util {
return 'youtube_search'; return 'youtube_search';
} }
/**
* Checks if the given string is url
* @param {string} str URL to check
*/
static isURL(str: string): boolean { static isURL(str: string): boolean {
return str.length < 2083 && attachmentRegex.test(str); return str.length < 2083 && attachmentRegex.test(str);
} }
/**
* Returns Vimeo ID
* @param {string} query Vimeo link
*/
static getVimeoID(query: string): string { static getVimeoID(query: string): string {
return Util.getQueryType(query) === 'vimeo' return Util.getQueryType(query) === 'vimeo'
? query ? query
@ -72,6 +98,10 @@ export class Util {
: null; : null;
} }
/**
* Parses ms time
* @param {number} milliseconds Time to parse
*/
static parseMS(milliseconds: number): TimeData { static parseMS(milliseconds: number): TimeData {
const roundTowardsZero = milliseconds > 0 ? Math.floor : Math.ceil; const roundTowardsZero = milliseconds > 0 ? Math.floor : Math.ceil;
@ -83,12 +113,22 @@ export class Util {
}; };
} }
/**
* Creates simple duration string
* @param {object} durObj Duration object
*/
static durationString(durObj: object): string { static durationString(durObj: object): string {
return Object.values(durObj) return Object.values(durObj)
.map((m) => (isNaN(m) ? 0 : m)) .map((m) => (isNaN(m) ? 0 : m))
.join(':'); .join(':');
} }
/**
* Makes youtube searches
* @param {string} query The query
* @param {any} options Options
* @returns {Promise<Track[]>}
*/
static ytSearch(query: string, options?: any): Promise<Track[]> { static ytSearch(query: string, options?: any): Promise<Track[]> {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
await YouTube.search(query, { await YouTube.search(query, {
@ -119,6 +159,9 @@ export class Util {
}); });
} }
/**
* Checks if this system is running in replit.com
*/
static isRepl(): boolean { static isRepl(): boolean {
if ('DP_REPL_NOCHECK' in process.env) return false; if ('DP_REPL_NOCHECK' in process.env) return false;
@ -137,10 +180,18 @@ export class Util {
return false; return false;
} }
/**
* Checks if the given voice channel is empty
* @param {Discord.VoiceChannel} channel The voice channel
*/
static isVoiceEmpty(channel: VoiceChannel): boolean { static isVoiceEmpty(channel: VoiceChannel): boolean {
return channel.members.filter((member) => !member.user.bot).size === 0; return channel.members.filter((member) => !member.user.bot).size === 0;
} }
/**
* Builds time code
* @param {object} data The data to build time code from
*/
static buildTimeCode(data: any): string { static buildTimeCode(data: any): string {
const items = Object.keys(data); const items = Object.keys(data);
const required = ['days', 'hours', 'minutes', 'seconds']; const required = ['days', 'hours', 'minutes', 'seconds'];
@ -155,7 +206,7 @@ export class Util {
/** /**
* Manage CJS require * Manage CJS require
* @param id id to require * @param {string} id id to require
*/ */
static require(id: string): any { static require(id: string): any {
try { try {

3109
yarn.lock

File diff suppressed because it is too large Load diff