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
yarn-error.log
# demo
demo/

View file

@ -3,3 +3,4 @@ tslint.json
tsconfig.json
.prettierrc
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",
"build": "tsc",
"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",
"contributors": [
@ -45,11 +47,17 @@
"ytdl-core": "^4.5.0"
},
"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",
"@discordjs/opus": "^0.5.0",
"@types/node": "^14.14.41",
"@types/ws": "^7.4.1",
"discord.js": "^12.5.3",
"discord.js-docgen": "discordjs/docgen#ts-patch",
"jsdoc-babel": "^0.5.0",
"prettier": "^2.2.1",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0",

View file

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

View file

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

View file

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

View file

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

View file

@ -47,11 +47,7 @@ const FilterList = {
return `${Object.values(this).join(',')}`;
},
/**
* Creates single string of audio filters
* @param filter Array of AudioFilters name
*/
create(filter?: FiltersName[]) {
create(filter?: FiltersName[]): string {
if (!filter || !Array.isArray(filter)) return this.toString();
return filter
.filter((predicate) => typeof predicate === 'string')
@ -59,27 +55,13 @@ const FilterList = {
.join(',');
},
/**
* Defines custom filter
* @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;
define(filterName: string, value: string): void {
if (typeof this[filterName as FiltersName] && typeof this[filterName as FiltersName] === 'function') return;
/* @ts-ignore */
this[filterName] = value;
this[filterName as FiltersName] = value;
},
/**
* Defines filters in bulk
* @param filterArray Array of filters containing object with `name` and `value` prop
*/
defineBulk(filterArray: { name: string; value: string }[]) {
defineBulk(filterArray: { name: string; value: string }[]): void {
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*)?$/;
export class Util {
/**
* Static Player Util class
*/
constructor() {
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 {
try {
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 {
const version = Util.getFFmpegVersion(force);
return version === null ? false : true;
}
/**
* Alerts if FFmpeg is not available
*/
static alertFFmpeg(): void {
const hasFFmpeg = Util.checkFFmpeg();
@ -43,6 +57,10 @@ export class Util {
);
}
/**
* Resolves query type
* @param {string} query The query
*/
static getQueryType(query: string): QueryType {
if (SoundcloudValidateURL(query) && !query.includes('/sets/')) return 'soundcloud_track';
if (SoundcloudValidateURL(query) && query.includes('/sets/')) return 'soundcloud_playlist';
@ -59,10 +77,18 @@ export class Util {
return 'youtube_search';
}
/**
* Checks if the given string is url
* @param {string} str URL to check
*/
static isURL(str: string): boolean {
return str.length < 2083 && attachmentRegex.test(str);
}
/**
* Returns Vimeo ID
* @param {string} query Vimeo link
*/
static getVimeoID(query: string): string {
return Util.getQueryType(query) === 'vimeo'
? query
@ -72,6 +98,10 @@ export class Util {
: null;
}
/**
* Parses ms time
* @param {number} milliseconds Time to parse
*/
static parseMS(milliseconds: number): TimeData {
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 {
return Object.values(durObj)
.map((m) => (isNaN(m) ? 0 : m))
.join(':');
}
/**
* Makes youtube searches
* @param {string} query The query
* @param {any} options Options
* @returns {Promise<Track[]>}
*/
static ytSearch(query: string, options?: any): Promise<Track[]> {
return new Promise(async (resolve) => {
await YouTube.search(query, {
@ -119,6 +159,9 @@ export class Util {
});
}
/**
* Checks if this system is running in replit.com
*/
static isRepl(): boolean {
if ('DP_REPL_NOCHECK' in process.env) return false;
@ -137,10 +180,18 @@ export class Util {
return false;
}
/**
* Checks if the given voice channel is empty
* @param {Discord.VoiceChannel} channel The voice channel
*/
static isVoiceEmpty(channel: VoiceChannel): boolean {
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 {
const items = Object.keys(data);
const required = ['days', 'hours', 'minutes', 'seconds'];
@ -155,7 +206,7 @@ export class Util {
/**
* Manage CJS require
* @param id id to require
* @param {string} id id to require
*/
static require(id: string): any {
try {

3109
yarn.lock

File diff suppressed because it is too large Load diff