Use play-dl instead node-ytdl-core

This commit is contained in:
JonnyBro 2022-09-13 11:40:21 +05:00
parent 8d7b841d9a
commit 7e4bfc1499
11 changed files with 1237 additions and 1634 deletions

View file

@ -1,18 +1,15 @@
# Discord Player
# Discord Player Play-dl
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)
[![discordBadge](https://img.shields.io/discord/558328638911545423?style=for-the-badge&color=7289da)](https://androz2091.fr/discord)
[![wakatime](https://wakatime.com/badge/github/Androz2091/discord-player.svg)](https://wakatime.com/badge/github/Androz2091/discord-player)
[![CodeFactor](https://www.codefactor.io/repository/github/androz2091/discord-player/badge/v5)](https://www.codefactor.io/repository/github/androz2091/discord-player/overview/v5)
[![wakatime](https://wakatime.com/badge/github/JonnyBro/discord-player.svg)](https://wakatime.com/badge/github/Androz2091/discord-player)
[![CodeFactor](https://www.codefactor.io/repository/github/jonnybro/discord-player/badge/v5)](https://www.codefactor.io/repository/github/androz2091/discord-player/overview/v5)
## Installation
### Install **[discord-player](https://npmjs.com/package/discord-player)**
### Install **[discord-player-play-dl](https://github.com/JonnyBro/discord-player-play-dl)**
```sh
$ npm install --save discord-player
$ npm install git+https://github.com/JonnyBro/discord-player-play-dl
```
### Install **[@discordjs/opus](https://npmjs.com/package/@discordjs/opus)**
@ -145,7 +142,7 @@ By default, discord-player supports **YouTube**, **Spotify** and **SoundCloud**
### 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 Play-dl 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/DevAndromeda/discord-player-extractors) (optional)
@ -156,23 +153,17 @@ You just need to install it using `npm i --save @discord-player/extractor` (disc
`@discord-player/downloader` is an optional package that brings support for +700 websites. The documentation is available [here](https://github.com/DevAndromeda/discord-player-downloader).
## Examples of bots made with Discord Player
## Examples of bots made with Discord Player Play-dl
These bots are made by the community, they can help you build your own!
* **[Discord Music Bot](https://github.com/Androz2091/discord-music-bot)** by [Androz2091](https://github.com/Androz2091)
* [Dodong](https://github.com/nizeic/Dodong) by [nizeic](https://github.com/nizeic)
* [Musico](https://github.com/Whirl21/Musico) by [Whirl21](https://github.com/Whirl21)
* [Eyesense-Music-Bot](https://github.com/naseif/Eyesense-Music-Bot) by [naseif](https://github.com/naseif)
* [Music-bot](https://github.com/ZerioDev/Music-bot) by [ZerioDev](https://github.com/ZerioDev)
* [AtlantaBot](https://github.com/Androz2091/AtlantaBot) by [Androz2091](https://github.com/Androz2091) (**outdated**)
* [Discord-Music](https://github.com/inhydrox/discord-music) by [inhydrox](https://github.com/inhydrox) (**outdated**)
* **[JaBa Bot](https://canary.discord.com/api/oauth2/authorize?client_id=708637495054565426&permissions=8&scope=bot%20applications.commands)** by [Jonny_Bro](https://github.com/JonnyBro)
## Advanced
### Smooth Volume
Discord Player will by default try to implement this. If smooth volume does not work, you need to add this line at the top of your main file:
Discord Player Play-dl will by default try to implement this. If smooth volume does not work, you need to add this line at the top of your main file:
```js
// CJS
@ -184,61 +175,10 @@ import "discord-player/smoothVolume"
> ⚠️ Make sure that line is situated at the **TOP** of your **main** file.
### Use cookies
```js
const player = new Player(client, {
ytdlOptions: {
requestOptions: {
headers: {
cookie: "YOUR_YOUTUBE_COOKIE"
}
}
}
});
```
### 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, {
ytdlOptions: {
requestOptions: { agent }
}
});
```
> You may also create a simple proxy server and forward requests through it.
> See **[https://github.com/http-party/node-http-proxy](https://github.com/http-party/node-http-proxy)** for more info.
### Custom stream Engine
Discord Player by default uses **[node-ytdl-core](https://github.com/fent/node-ytdl-core)** for youtube and some other extractors for other sources.
If you need to modify this behavior without touching extractors, you need to use `createStream` functionality of discord player.
Here's an example on how you can use **[play-dl](https://npmjs.com/package/play-dl)** to download youtube streams instead of using ytdl-core.
```js
const playdl = require("play-dl");
// other code
const queue = player.createQueue(..., {
...,
async onBeforeCreateStream(track, source, _queue) {
// only trap youtube source
if (source === "youtube") {
// track here would be youtube track
return (await playdl.stream(track.url, { discordPlayerCompatibility : true })).stream;
// we must return readable stream or void (returning void means telling discord-player to look for default extractor)
}
}
});
```
Discord Player Play-dl by default uses **[play-dl](https://github.com/play-dl/play-dl)** for youtube and some other extractors for other sources.
If you need to modify this behavior without touching extractors, you need to use `onBeforeCreateStream` functionality of Discord player Play-dl.
`<Queue>.onBeforeCreateStream` is called before actually downloading the stream. It is a different concept from extractors, where you are **just** downloading
streams. `source` here will be a video source. Streams from `onBeforeCreateStream` are then piped to `FFmpeg` and finally sent to Discord voice servers.

View file

@ -1,51 +0,0 @@
# Create Stream
This is a checkpoint where discord-player calls `createStream` before downloading stream.
### Custom stream Engine
Discord Player by default uses **[node-ytdl-core](https://github.com/fent/node-ytdl-core)** for youtube and some other extractors for other sources.
If you need to modify this behavior without touching extractors, you need to use `createStream` functionality of discord player.
Here's an example on how you can use **[play-dl](https://npmjs.com/package/play-dl)** to download youtube streams instead of using ytdl-core.
```js
const playdl = require("play-dl");
// other code
const queue = player.createQueue(..., {
...,
async onBeforeCreateStream(track, source, _queue) {
// only trap youtube source
if (source === "youtube") {
// track here would be youtube track
return (await playdl.stream(track.url, { discordPlayerCompatibility : true })).stream;
// we must return readable stream or void (returning void means telling discord-player to look for default extractor)
}
}
});
```
`<Queue>.onBeforeCreateStream` is called before actually downloading the stream. It is a different concept from extractors, where you are **just** downloading
streams. `source` here will be a video source. Streams from `onBeforeCreateStream` are then piped to `FFmpeg` and finally sent to Discord voice servers.
# FAQ
## How can I remove this?
> If you already made this change and want to switch to default mode in runtime,
> you can set `queue.onBeforeCreateStream` to `null` which will make `discord-player` use default config.
## Which stream format should I return?
> It's not necessary to return opus format or whatever, since every streams have to be converted to s16le, due to inline volume.
## Can I use ytdl-core-discord?
> Yes, you can.
## Can I use this for other sources, like soundcloud?
> Absolutely.
## This is not working properly
> `onBeforeCreateStream` may not work properly if you have `spotifyBridge` enabled (enabled by default).

View file

@ -1,18 +0,0 @@
# Using Cookies to avoid 429
```js
const { Player } = require("discord-player");
const player = new Player(client, {
ytdlOptions: {
requestOptions: {
headers: {
cookie: "YOUR_YOUTUBE_COOKIE"
}
}
}
});
```
> Keep in mind that using `cookies` after getting `429` **does not fix the problem**.
> You should use `cookies` before getting `429` which helps to **_reduce_** `Error: Status Code 429`

View file

@ -1,19 +0,0 @@
# Using Proxy to avoid 429
```js
const { Player } = require("discord-player");
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, {
ytdlOptions: {
requestOptions: { agent }
}
});
```
> You may also create a simple proxy server and forward requests through it.
> See **[https://github.com/http-party/node-http-proxy](https://github.com/http-party/node-http-proxy)** for more info.

View file

@ -30,13 +30,13 @@
"prepare": "husky install",
"lint:fix": "eslint src --ext .ts --fix"
},
"funding": "https://github.com/Androz2091/discord-player?sponsor=1",
"contributors": [
"DevAndromeda <devandromeda@snowflakedev.org>"
"DevAndromeda <devandromeda@snowflakedev.org>",
"Jonny_Bro#4226"
],
"repository": {
"type": "git",
"url": "git+https://github.com/Androz2091/discord-player.git"
"url": "git+https://github.com/JonnyBro/discord-player.git"
},
"keywords": [
"music",
@ -52,27 +52,21 @@
"discord-music-player",
"discord-music",
"music-player",
"youtube-dl",
"ytdl-core",
"ytdl",
"lavalink",
"api"
],
"author": "Androz2091",
"author": "Androz2091 & Jonny_Bro",
"license": "MIT",
"bugs": {
"url": "https://github.com/Androz2091/discord-player/issues"
"url": "https://github.com/JonnyBro/discord-player/issues"
},
"homepage": "https://discord-player.js.org",
"dependencies": {
"@discordjs/voice": "^0.11.0",
"libsodium-wrappers": "^0.7.10",
"soundcloud-scraper": "^5.0.3",
"play-dl": "^1.9.5",
"spotify-url-info": "^3.1.2",
"tiny-typed-emitter": "^2.1.0",
"tslib": "^2.4.0",
"youtube-sr": "^4.3.0",
"ytdl-core": "^4.11.0"
"tslib": "^2.4.0"
},
"devDependencies": {
"@discordjs/ts-docgen": "^0.4.1",

View file

@ -4,26 +4,19 @@ 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 { QueryResolver } from "./utils/QueryResolver";
import YouTube from "youtube-sr";
import { Util } from "./utils/Util";
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 { getInfo as ytdlGetInfo } from "ytdl-core";
import { Client as SoundCloud, SearchResult as SoundCloudSearchResult } from "soundcloud-scraper";
import { Playlist } from "./Structures/Playlist";
import { ExtractorModel } from "./Structures/ExtractorModel";
import { generateDependencyReport } from "@discordjs/voice";
const soundcloud = new SoundCloud();
class Player extends EventEmitter<PlayerEvents> {
public readonly client: Client;
public readonly options: PlayerInitOptions = {
autoRegisterExtractor: true,
ytdlOptions: {
highWaterMark: 1 << 25
},
connectionTimeout: 20000
};
public readonly queues = new Collection<Snowflake, Queue>();
@ -165,7 +158,6 @@ class Player extends EventEmitter<PlayerEvents> {
const _meta = queueInitOptions.metadata;
delete queueInitOptions["metadata"];
queueInitOptions.volumeSmoothness ??= 0.08;
queueInitOptions.ytdlOptions ??= this.options.ytdlOptions;
const queue = new Queue(this, guild, queueInitOptions);
queue.metadata = _meta;
this.queues.set(guild.id, queue);
@ -275,21 +267,21 @@ class Player extends EventEmitter<PlayerEvents> {
}
}
const qt = options.searchEngine === QueryType.AUTO ? QueryResolver.resolve(query) : options.searchEngine;
const qt = options.searchEngine === QueryType.AUTO ? await QueryResolver.resolve(query) : options.searchEngine;
switch (qt) {
case QueryType.YOUTUBE_VIDEO: {
const info = await ytdlGetInfo(query, this.options.ytdlOptions).catch(Util.noop);
const info = await play.video_info(query).catch(Util.noop);
if (!info) return { playlist: null, tracks: [] };
const track = new Track(this, {
title: info.videoDetails.title,
description: info.videoDetails.description,
author: info.videoDetails.author?.name,
url: info.videoDetails.video_url,
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.videoDetails.thumbnails)?.url,
views: parseInt(info.videoDetails.viewCount.replace(/[^0-9]/g, "")) || 0,
duration: Util.buildTimeCode(Util.parseMS(parseInt(info.videoDetails.lengthSeconds) * 1000)),
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
});
@ -297,12 +289,13 @@ class Player extends EventEmitter<PlayerEvents> {
return { playlist: null, tracks: [track] };
}
case QueryType.YOUTUBE_SEARCH: {
const videos = await YouTube.search(query, {
type: "video"
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) => {
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,
@ -310,34 +303,37 @@ class Player extends EventEmitter<PlayerEvents> {
author: m.channel?.name,
url: m.url,
requestedBy: options.requestedBy as User,
thumbnail: m.thumbnail?.displayThumbnailURL("maxresdefault"),
thumbnail: Util.last(m.thumbnails).url,
views: m.views,
duration: m.durationFormatted,
duration: m.durationRaw,
source: "youtube",
raw: m
});
});
return { playlist: null, tracks };
return { playlist: null, tracks, searched: true };
}
case QueryType.SOUNDCLOUD_TRACK:
case QueryType.SOUNDCLOUD_SEARCH: {
const result: SoundCloudSearchResult[] = QueryResolver.resolve(query) === QueryType.SOUNDCLOUD_TRACK ? [{ url: query }] : await soundcloud.search(query, "track").catch(() => []);
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 soundcloud.getSongInfo(r.url).catch(Util.noop);
const trackInfo = await play.soundcloud(r.url).catch(Util.noop);
if (!trackInfo) continue;
const track = new Track(this, {
title: trackInfo.title,
title: trackInfo.name,
url: trackInfo.url,
duration: Util.buildTimeCode(Util.parseMS(trackInfo.duration)),
description: trackInfo.description,
thumbnail: trackInfo.thumbnail,
views: trackInfo.playCount,
author: trackInfo.author.name,
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
@ -440,18 +436,18 @@ class Player extends EventEmitter<PlayerEvents> {
return { playlist: playlist, tracks: playlist.tracks };
}
case QueryType.SOUNDCLOUD_PLAYLIST: {
const data = await soundcloud.getPlaylist(query).catch(Util.noop);
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.title,
description: data.description ?? "",
thumbnail: data.thumbnail ?? "https://soundcloud.com/pwa-icon-192.png",
title: data.name,
description: "",
thumbnail: "https://soundcloud.com/pwa-icon-192.png",
type: "playlist",
source: "soundcloud",
author: {
name: data.author?.name ?? data.author?.username ?? "Unknown Artist",
url: data.author?.profile
name: data.user.name ?? "Unknown Owner",
url: data.user.url
},
tracks: [],
id: `${data.id}`, // stringified
@ -459,15 +455,16 @@ class Player extends EventEmitter<PlayerEvents> {
rawPlaylist: data
});
for (const song of data.tracks) {
const songs = await data.all_tracks();
for (const song of songs) {
const track = new Track(this, {
title: song.title,
description: song.description ?? "",
author: song.author?.username ?? song.author?.name ?? "Unknown Artist",
title: song.name,
description: "",
author: song.publisher.name ?? "Unknown Publisher",
url: song.url,
thumbnail: song.thumbnail,
duration: Util.buildTimeCode(Util.parseMS(song.duration)),
views: song.playCount ?? 0,
duration: Util.buildTimeCode(Util.parseMS(song.durationInMs)),
views: 0,
requestedBy: options.requestedBy,
playlist: res,
source: "soundcloud",
@ -479,11 +476,9 @@ class Player extends EventEmitter<PlayerEvents> {
return { playlist: res, tracks: res.tracks };
}
case QueryType.YOUTUBE_PLAYLIST: {
const ytpl = await YouTube.getPlaylist(query).catch(Util.noop);
const ytpl = await play.playlist_info(query, { incomplete: true }).catch(Util.noop) as unknown as YouTubePlayList;
if (!ytpl) return { playlist: null, tracks: [] };
await ytpl.fetch().catch(Util.noop);
const playlist: Playlist = new Playlist(this, {
title: ytpl.title,
thumbnail: ytpl.thumbnail as unknown as string,
@ -500,22 +495,21 @@ class Player extends EventEmitter<PlayerEvents> {
rawPlaylist: ytpl
});
playlist.tracks = ytpl.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: video.thumbnail.url,
views: video.views,
duration: video.durationFormatted,
raw: video,
playlist: playlist,
source: "youtube"
})
);
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 };
}

View file

@ -3,10 +3,9 @@ import { Player } from "../Player";
import { StreamDispatcher } from "../VoiceInterface/StreamDispatcher";
import Track from "./Track";
import { PlayerOptions, PlayerProgressbarOptions, PlayOptions, QueueFilters, QueueRepeatMode, TrackSource } from "../types/types";
import ytdl from "ytdl-core";
import { AudioResource, StreamType } from "@discordjs/voice";
import play from "play-dl";
import { Util } from "../utils/Util";
import YouTube from "youtube-sr";
import AudioFilters from "../utils/AudioFilters";
import { PlayerError, ErrorStatusCode } from "./PlayerError";
import type { Readable } from "stream";
@ -103,12 +102,8 @@ class Queue<T = unknown> {
leaveOnEmpty: true,
leaveOnEmptyCooldown: 1000,
autoSelfDeaf: true,
ytdlOptions: {
highWaterMark: 1 << 25
},
initialVolume: 100,
bufferingTimeout: 3000,
spotifyBridge: true,
disableVolume: false
} as PlayerOptions,
options
@ -636,23 +631,15 @@ class Queue<T = unknown> {
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 YouTube.search(`${track.author} ${track.title}`, { type: "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;
stream = (await this.onBeforeCreateStream(track, "youtube", this)) || null;
}
if (!stream) {
stream = ytdl(url, this.options.ytdlOptions);
stream = (await play.stream(url, { discordPlayerCompatibility: true })).stream;
}
} else {
const arbitraryStream = (hasCustomDownloader && (await this.onBeforeCreateStream(track, track.raw.source || track.raw.engine, this))) || null;
@ -665,7 +652,7 @@ class Queue<T = unknown> {
}
const ffmpegStream = createFFmpegStream(stream, {
encoderArgs: options.encoderArgs || this._activeFilters.length ? ["-af", AudioFilters.create(this._activeFilters)] : [],
encoderArgs: options.encoderArgs || [],
seek: options.seek ? options.seek / 1000 : 0,
fmt: "s16le"
}).on("error", (err) => {
@ -704,27 +691,22 @@ class Queue<T = unknown> {
if (this.options.leaveOnEnd) this.destroy();
return void this.player.emit("queueEnd", this);
}
let info = await YouTube.getVideo(track.url)
.then((x) => x.videos[0])
const info = await play.video_info(track.url)
.catch(Util.noop);
// fallback
if (!info)
info = await YouTube.search(track.author)
.then((x) => x[0])
.catch(Util.noop);
if (!info) {
if (this.options.leaveOnEnd) this.destroy();
return void this.player.emit("queueEnd", this);
}
const related = await play.video_info(info.related_videos[1]);
const nextTrack = new Track(this.player, {
title: info.title,
url: `https://www.youtube.com/watch?v=${info.id}`,
duration: info.durationFormatted ? Util.buildTimeCode(Util.parseMS(info.duration * 1000)) : "0:00",
title: related.video_details.title,
url: related.video_details.url,
duration: related.video_details.durationRaw ? Util.buildTimeCode(Util.parseMS(related.video_details.durationInSec * 1000)) : "0:00",
description: "",
thumbnail: typeof info.thumbnail === "string" ? info.thumbnail : info.thumbnail.url,
views: info.views,
author: info.channel.name,
thumbnail: Util.last(related.video_details.thumbnails).url,
views: related.video_details.views,
author: related.video_details.channel.name,
requestedBy: track.requestedBy,
source: "youtube"
});

View file

@ -110,6 +110,7 @@ class Track {
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 ?? "";

View file

@ -4,13 +4,13 @@ import { Queue } from "../Structures/Queue";
import Track from "../Structures/Track";
import { Playlist } from "../Structures/Playlist";
import { StreamDispatcher } from "../VoiceInterface/StreamDispatcher";
import { downloadOptions } from "ytdl-core";
export type FiltersName = keyof QueueFilters;
export interface PlayerSearchResult {
playlist: Playlist | null;
tracks: Track[];
searched?: boolean;
}
/**
@ -70,7 +70,7 @@ export type TrackSource = "soundcloud" | "youtube" | "spotify" | "arbitrary";
* @property {string} url The url
* @property {string} thumbnail The thumbnail
* @property {string} duration The duration
* @property {number} views The views
* @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
@ -131,10 +131,8 @@ export interface PlayerProgressbarOptions {
* @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`
@ -146,10 +144,8 @@ export interface PlayerOptions {
leaveOnEmpty?: boolean;
leaveOnEmptyCooldown?: number;
autoSelfDeaf?: boolean;
ytdlOptions?: downloadOptions;
initialVolume?: number;
bufferingTimeout?: number;
spotifyBridge?: boolean;
disableVolume?: boolean;
volumeSmoothness?: number;
onBeforeCreateStream?: (track: Track, source: TrackSource, queue: Queue) => Promise<Readable>;
@ -477,11 +473,9 @@ export interface PlaylistJSON {
/**
* @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;
ytdlOptions?: downloadOptions;
connectionTimeout?: number;
}

View file

@ -1,9 +1,6 @@
import { validateID, validateURL } from "ytdl-core";
import { YouTube } from "youtube-sr";
import { QueryType } from "../types/types";
import play from "play-dl";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import { validateURL as SoundcloudValidateURL } from "soundcloud-scraper";
// scary things below *sigh*
const spotifySongRegex = /https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:track\/|\?uri=spotify:track:)((\w|-){22})/;
@ -27,11 +24,11 @@ class QueryResolver {
* @param {string} query The query
* @returns {QueryType}
*/
static resolve(query: string): QueryType {
if (SoundcloudValidateURL(query, "track")) return QueryType.SOUNDCLOUD_TRACK;
if (SoundcloudValidateURL(query, "playlist") || query.includes("/sets/")) return QueryType.SOUNDCLOUD_PLAYLIST;
if (YouTube.isPlaylist(query)) return QueryType.YOUTUBE_PLAYLIST;
if (validateID(query) || validateURL(query)) return QueryType.YOUTUBE_VIDEO;
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;
@ -48,8 +45,8 @@ class QueryResolver {
* @param {string} query The query
* @returns {string}
*/
static getVimeoID(query: string): string {
return QueryResolver.resolve(query) === QueryType.VIMEO
static async getVimeoID(query: string): Promise<string> {
return await QueryResolver.resolve(query) === QueryType.VIMEO
? query
.split("/")
.filter((x) => !!x)

2481
yarn.lock

File diff suppressed because it is too large Load diff