update
This commit is contained in:
parent
1be07e4703
commit
b40c070b2e
14 changed files with 681 additions and 95 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -5,4 +5,7 @@ node_modules/
|
|||
test/
|
||||
|
||||
# Compiled
|
||||
lib/
|
||||
lib/
|
||||
|
||||
# error logs
|
||||
yarn-error.log
|
|
@ -38,6 +38,8 @@
|
|||
"homepage": "https://github.com/Androz2091/discord-player#readme",
|
||||
"dependencies": {
|
||||
"discord-ytdl-core": "^5.0.2",
|
||||
"soundcloud-scraper": "^4.0.3",
|
||||
"spotify-url-info": "^2.2.0",
|
||||
"youtube-sr": "^4.0.3",
|
||||
"ytdl-core": "^4.5.0"
|
||||
},
|
||||
|
|
120
src/Player.ts
120
src/Player.ts
|
@ -1,11 +1,27 @@
|
|||
import { EventEmitter } from 'events';
|
||||
import { Client } from 'discord.js';
|
||||
import { PlayerOptions } from './types/Player';
|
||||
import Util from './Util';
|
||||
import YouTube from "youtube-sr";
|
||||
import { EventEmitter } from "events";
|
||||
import { Client, Collection, Snowflake, Collector, Message } from "discord.js";
|
||||
import { PlayerOptions } from "./types/types";
|
||||
import Util from "./utils/Util";
|
||||
import AudioFilters from "./utils/AudioFilters";
|
||||
import Queue from "./Structures/Queue";
|
||||
import Track from "./Structures/Track";
|
||||
import { PlayerEvents } from "./utils/Constants";
|
||||
|
||||
// @ts-ignore
|
||||
import spotify from "spotify-url-info";
|
||||
// @ts-ignore
|
||||
import { Client as SoundCloudClient } from "soundcloud-scraper";
|
||||
|
||||
const SoundCloud = new SoundCloudClient;
|
||||
|
||||
export default class Player extends EventEmitter {
|
||||
public client!: Client;
|
||||
public options: PlayerOptions;
|
||||
public filters: typeof AudioFilters;
|
||||
public queues: Collection<Snowflake, Queue>;
|
||||
private _resultsCollectors: Collection<string, Collector<Snowflake, Message>>;
|
||||
private _cooldownsTimeout: Collection<string, NodeJS.Timeout>;
|
||||
|
||||
constructor(client: Client, options?: PlayerOptions) {
|
||||
super();
|
||||
|
@ -13,7 +29,7 @@ export default class Player extends EventEmitter {
|
|||
/**
|
||||
* The discord client that instantiated this player
|
||||
*/
|
||||
Object.defineProperty(this, 'client', {
|
||||
Object.defineProperty(this, "client", {
|
||||
value: client,
|
||||
enumerable: false
|
||||
});
|
||||
|
@ -25,5 +41,99 @@ export default class Player extends EventEmitter {
|
|||
|
||||
// check FFmpeg
|
||||
void Util.alertFFmpeg();
|
||||
|
||||
/**
|
||||
* The audio filters
|
||||
*/
|
||||
this.filters = AudioFilters;
|
||||
|
||||
/**
|
||||
* Player queues
|
||||
*/
|
||||
this.queues = new Collection();
|
||||
}
|
||||
|
||||
static get AudioFilters() {
|
||||
return AudioFilters;
|
||||
}
|
||||
|
||||
private _searchTracks(message: Message, query: string, firstResult?: boolean, isAttachment?: boolean): Promise<Track> {
|
||||
return new Promise(async (resolve) => {
|
||||
let tracks: Track[] = [];
|
||||
let queryType = Util.getQueryType(query);
|
||||
|
||||
switch(queryType) {
|
||||
case "soundcloud_track": {
|
||||
const data = await SoundCloud.getSongInfo(query).catch(() => { })
|
||||
if (data) {
|
||||
const track = new Track(this, {
|
||||
title: data.title,
|
||||
url: data.url,
|
||||
duration: Util.durationString(Util.parseMS(data.duration / 1000)),
|
||||
description: data.description,
|
||||
thumbnail: data.thumbnail,
|
||||
views: data.playCount,
|
||||
author: data.author,
|
||||
requestedBy: message.author,
|
||||
fromPlaylist: false,
|
||||
source: "soundcloud",
|
||||
engine: data
|
||||
});
|
||||
|
||||
tracks.push(track)
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "spotify_song": {
|
||||
const matchSpotifyURL = query.match(/https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:track\/|\?uri=spotify:track:)((\w|-){22})/)
|
||||
if (matchSpotifyURL) {
|
||||
const spotifyData = await spotify.getPreview(query).catch(() => { })
|
||||
if (spotifyData) {
|
||||
tracks = await Util.ytSearch(`${spotifyData.artist} - ${spotifyData.title}`, { user: message.author, player: this });
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
tracks = await Util.ytSearch(query, { user: message.author, player: this });
|
||||
}
|
||||
|
||||
if (tracks.length < 1) return this.emit(PlayerEvents.NO_RESULTS, message, query);
|
||||
if (firstResult) return resolve(tracks[0]);
|
||||
|
||||
const collectorString = `${message.author.id}-${message.channel.id}`;
|
||||
const currentCollector = this._resultsCollectors.get(collectorString);
|
||||
if (currentCollector) currentCollector.stop()
|
||||
|
||||
const collector = message.channel.createMessageCollector((m) => m.author.id === message.author.id, {
|
||||
time: 60000
|
||||
});
|
||||
|
||||
this._resultsCollectors.set(collectorString, collector);
|
||||
|
||||
this.emit(PlayerEvents.SEARCH_RESULTS, message, query, tracks, collector);
|
||||
|
||||
collector.on("collect", ({ content }) => {
|
||||
if (content === "cancel") {
|
||||
collector.stop();
|
||||
return this.emit(PlayerEvents.SEARCH_CANCEL, message, query, tracks);
|
||||
}
|
||||
|
||||
if (!isNaN(content) && parseInt(content) >= 1 && parseInt(content) <= tracks.length) {
|
||||
const index = parseInt(content, 10);
|
||||
const track = tracks[index - 1];
|
||||
collector.stop();
|
||||
resolve(track);
|
||||
} else {
|
||||
this.emit(PlayerEvents.SEARCH_INVALID_RESPONSE, message, query, tracks, content, collector);
|
||||
}
|
||||
})
|
||||
|
||||
collector.on("end", (collected, reason) => {
|
||||
if (reason === "time") {
|
||||
this.emit(PlayerEvents.SEARCH_CANCEL, message, query, tracks);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,129 @@
|
|||
import Player from '../Player';
|
||||
import { Message, Snowflake, VoiceConnection } from "discord.js";
|
||||
import AudioFilters from "../utils/AudioFilters";
|
||||
import Player from "../Player";
|
||||
import { EventEmitter } from "events";
|
||||
import Track from "./Track";
|
||||
import { QueueFilters } from "../types/types";
|
||||
|
||||
export default class Queue {
|
||||
export default class Queue extends EventEmitter {
|
||||
public player!: Player;
|
||||
public guildID: Snowflake;
|
||||
public voiceConnection?: VoiceConnection;
|
||||
public stream?: any;
|
||||
public tracks: Track[];
|
||||
public previousTracks: Track[];
|
||||
public stopped: boolean;
|
||||
public lastSkipped: boolean;
|
||||
public volume: number;
|
||||
public paused: boolean;
|
||||
public repeatMode: boolean;
|
||||
public loopMode: boolean;
|
||||
public filters: QueueFilters;
|
||||
public additionalStreamTime: number;
|
||||
public firstMessage: Message;
|
||||
|
||||
constructor(player: Player, message: Message, filters: typeof AudioFilters) {
|
||||
super();
|
||||
|
||||
constructor(player: Player) {
|
||||
/**
|
||||
* The player that instantiated this Queue
|
||||
*/
|
||||
Object.defineProperty(this, 'player', { value: player, enumerable: false });
|
||||
Object.defineProperty(this, "player", { value: player, enumerable: false });
|
||||
|
||||
/**
|
||||
* ID of the guild assigned to this queue
|
||||
*/
|
||||
this.guildID = message.guild.id;
|
||||
|
||||
/**
|
||||
* The voice connection of this queue
|
||||
*/
|
||||
this.voiceConnection = null;
|
||||
|
||||
/**
|
||||
* Tracks of this queue
|
||||
*/
|
||||
this.tracks = [];
|
||||
|
||||
/**
|
||||
* Previous tracks of this queue
|
||||
*/
|
||||
this.previousTracks = [];
|
||||
|
||||
/**
|
||||
* If the player of this queue is stopped
|
||||
*/
|
||||
this.stopped = false;
|
||||
|
||||
/**
|
||||
* If last track was skipped
|
||||
*/
|
||||
this.lastSkipped = false;
|
||||
|
||||
/**
|
||||
* Queue volume
|
||||
*/
|
||||
this.volume = 100;
|
||||
|
||||
/**
|
||||
* If the player of this queue is paused
|
||||
*/
|
||||
this.paused = Boolean(this.voiceConnection?.dispatcher?.paused);
|
||||
|
||||
/**
|
||||
* If repeat mode is enabled in this queue
|
||||
*/
|
||||
this.repeatMode = false;
|
||||
|
||||
/**
|
||||
* If loop mode is enabled in this queue
|
||||
*/
|
||||
this.loopMode = false;
|
||||
|
||||
/**
|
||||
* The additional calculated stream time
|
||||
*/
|
||||
this.additionalStreamTime = 0;
|
||||
|
||||
/**
|
||||
* The initial message object
|
||||
*/
|
||||
this.firstMessage = message;
|
||||
|
||||
// @ts-ignore
|
||||
this.filters = {};
|
||||
|
||||
Object.keys(AudioFilters).forEach(fn => {
|
||||
// @ts-ignore
|
||||
this.filters[fn] = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently playing track
|
||||
*/
|
||||
get playing() {
|
||||
return this.tracks[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculated volume of this queue
|
||||
*/
|
||||
get calculatedVolume() {
|
||||
return this.filters.bassboost ? this.volume + 50 : this.volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Total duration
|
||||
*/
|
||||
get totalTime() {
|
||||
return this.tracks.length > 0 ? this.tracks.map((t) => t.durationMS).reduce((p, c) => p + c) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current stream time
|
||||
*/
|
||||
get currentStreamTime() {
|
||||
return this.voiceConnection?.dispatcher?.streamTime + this.additionalStreamTime || 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Player from '../Player';
|
||||
import { User } from 'discord.js';
|
||||
import { TrackData } from "../types/Track";
|
||||
import Player from "../Player";
|
||||
import { User } from "discord.js";
|
||||
import { TrackData } from "../types/types";
|
||||
|
||||
export default class Track {
|
||||
public player!: Player;
|
||||
|
@ -19,18 +19,18 @@ export default class Track {
|
|||
/**
|
||||
* The player that instantiated this Track
|
||||
*/
|
||||
Object.defineProperty(this, 'player', { value: player, enumerable: false });
|
||||
Object.defineProperty(this, "player", { value: player, enumerable: false });
|
||||
|
||||
void this._patch(data);
|
||||
}
|
||||
|
||||
private _patch(data: TrackData) {
|
||||
this.title = data.title ?? '';
|
||||
this.description = data.description ?? '';
|
||||
this.author = data.author ?? '';
|
||||
this.url = data.url ?? '';
|
||||
this.thumbnail = data.thumbnail ?? '';
|
||||
this.duration = data.duration ?? '';
|
||||
this.title = data.title ?? "";
|
||||
this.description = data.description ?? "";
|
||||
this.author = data.author ?? "";
|
||||
this.url = data.url ?? "";
|
||||
this.thumbnail = data.thumbnail ?? "";
|
||||
this.duration = data.duration ?? "";
|
||||
this.views = data.views ?? 0;
|
||||
this.requestedBy = data.requestedBy;
|
||||
this.fromPlaylist = Boolean(data.fromPlaylist);
|
||||
|
@ -38,4 +38,28 @@ export default class Track {
|
|||
// raw
|
||||
Object.defineProperty(this, "raw", { get: () => data, enumerable: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* The queue in which this track is located
|
||||
*/
|
||||
get queue() {
|
||||
return this.player.queues.find(q => q.tracks.includes(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* The track duration in millisecond
|
||||
*/
|
||||
get durationMS() {
|
||||
const times = (n: number, t: number) => {
|
||||
let tn = 1;
|
||||
for (let i = 0; i < t; i++) tn *= n;
|
||||
return t <= 0 ? 1000 : tn * 1000;
|
||||
};
|
||||
|
||||
return this.duration.split(":").reverse().map((m, i) => parseInt(m) * times(60, i)).reduce((a, c) => a + c, 0);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `${this.title} by ${this.author}`;
|
||||
}
|
||||
}
|
||||
|
|
39
src/Util.ts
39
src/Util.ts
|
@ -1,39 +0,0 @@
|
|||
import { PlayerOptions } from './types/Player';
|
||||
import { FFmpeg } from 'prism-media';
|
||||
|
||||
export default class Util {
|
||||
constructor() {
|
||||
throw new Error(`The ${this.constructor.name} class is static and cannot be instantiated!`);
|
||||
}
|
||||
|
||||
static get DefaultPlayerOptions() {
|
||||
return {
|
||||
leaveOnEnd: true,
|
||||
leaveOnStop: true,
|
||||
leaveOnEmpty: true,
|
||||
leaveOnEmptyCooldown: 0,
|
||||
autoSelfDeaf: true,
|
||||
enableLive: false,
|
||||
ytdlDownloadOptions: {}
|
||||
} as PlayerOptions;
|
||||
}
|
||||
|
||||
static checkFFmpeg(force?: boolean) {
|
||||
try {
|
||||
FFmpeg.getInfo(Boolean(force));
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static alertFFmpeg() {
|
||||
const hasFFmpeg = Util.checkFFmpeg();
|
||||
|
||||
if (!hasFFmpeg)
|
||||
console.warn(
|
||||
'[Discord Player] FFmpeg/Avconv not found! Install via "npm install ffmpeg-static" or download from https://ffmpeg.org/download.html'
|
||||
);
|
||||
}
|
||||
}
|
14
src/index.ts
14
src/index.ts
|
@ -1,7 +1,7 @@
|
|||
export * as AudioFilters from './AudioFilters';
|
||||
export * as Player from './Player';
|
||||
export * as Util from './Util';
|
||||
export * as Track from './Structures/Track';
|
||||
export * as Queue from './Structures/Queue';
|
||||
export * from './types/Player';
|
||||
export { version } from '../package.json';
|
||||
export * as AudioFilters from "./utils/AudioFilters";
|
||||
export * as Player from "./Player";
|
||||
export * as Util from "./utils/Util";
|
||||
export * as Track from "./Structures/Track";
|
||||
export * as Queue from "./Structures/Queue";
|
||||
export * from "./types/types";
|
||||
export { version } from "../package.json";
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
import { downloadOptions } from 'ytdl-core';
|
||||
|
||||
export interface PlayerOptions {
|
||||
leaveOnEnd?: boolean;
|
||||
leaveOnEndCooldown?: number;
|
||||
leaveOnStop?: boolean;
|
||||
leaveOnEmpty?: boolean;
|
||||
leaveOnEmptyCooldown?: number;
|
||||
autoSelfDeaf?: boolean;
|
||||
enableLive?: boolean;
|
||||
ytdlDownloadOptions?: downloadOptions;
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
import { User } from "discord.js";
|
||||
|
||||
export interface TrackData {
|
||||
title: string;
|
||||
description: string;
|
||||
author: string;
|
||||
url: string;
|
||||
thumbnail: string;
|
||||
duration: string;
|
||||
views: number;
|
||||
requestedBy: User;
|
||||
fromPlaylist: boolean;
|
||||
}
|
105
src/types/types.ts
Normal file
105
src/types/types.ts
Normal file
|
@ -0,0 +1,105 @@
|
|||
import { downloadOptions } from "ytdl-core";
|
||||
import { User } from "discord.js";
|
||||
|
||||
export interface PlayerOptions {
|
||||
leaveOnEnd?: boolean;
|
||||
leaveOnEndCooldown?: number;
|
||||
leaveOnStop?: boolean;
|
||||
leaveOnEmpty?: boolean;
|
||||
leaveOnEmptyCooldown?: number;
|
||||
autoSelfDeaf?: boolean;
|
||||
enableLive?: boolean;
|
||||
ytdlDownloadOptions?: downloadOptions;
|
||||
useSafeSearch?: boolean;
|
||||
}
|
||||
|
||||
export type FiltersName =
|
||||
| "bassboost"
|
||||
| "8D"
|
||||
| "vaporwave"
|
||||
| "nightcore"
|
||||
| "phaser"
|
||||
| "tremolo"
|
||||
| "vibrato"
|
||||
| "reverse"
|
||||
| "treble"
|
||||
| "normalizer"
|
||||
| "surrounding"
|
||||
| "pulsator"
|
||||
| "subboost"
|
||||
| "karaoke"
|
||||
| "flanger"
|
||||
| "gate"
|
||||
| "haas"
|
||||
| "mcompand"
|
||||
| "mono"
|
||||
| "mstlr"
|
||||
| "mstrr"
|
||||
| "compressor"
|
||||
| "expander"
|
||||
| "softlimiter"
|
||||
| "chorus"
|
||||
| "chorus2d"
|
||||
| "chorus3d"
|
||||
| "fadein";
|
||||
|
||||
export type TrackSource = "soundcloud" | "youtube" | "arbitrary";
|
||||
|
||||
export interface TrackData {
|
||||
title: string;
|
||||
description: string;
|
||||
author: string;
|
||||
url: string;
|
||||
thumbnail: string;
|
||||
duration: string;
|
||||
views: number;
|
||||
requestedBy: User;
|
||||
fromPlaylist: boolean;
|
||||
source?: TrackSource;
|
||||
engine?: any;
|
||||
}
|
||||
|
||||
export type QueueFilters = {
|
||||
"bassboost": boolean;
|
||||
"8D": boolean;
|
||||
"vaporwave": boolean;
|
||||
"nightcore": boolean;
|
||||
"phaser": boolean;
|
||||
"tremolo": boolean;
|
||||
"vibrato": boolean;
|
||||
"reverse": boolean;
|
||||
"treble": boolean;
|
||||
"normalizer": boolean;
|
||||
"surrounding": boolean;
|
||||
"pulsator": boolean;
|
||||
"subboost": boolean;
|
||||
"karaoke": boolean;
|
||||
"flanger": boolean;
|
||||
"gate": boolean;
|
||||
"haas": boolean;
|
||||
"mcompand": boolean;
|
||||
"mono": boolean;
|
||||
'mstlr': boolean;
|
||||
"mstrr": boolean;
|
||||
"compressor": boolean;
|
||||
"expander": boolean;
|
||||
"softlimiter": boolean;
|
||||
"chorus": boolean;
|
||||
"chorus2d": boolean;
|
||||
"chorus3d": boolean;
|
||||
"fadein": boolean;
|
||||
}
|
||||
|
||||
export type QueryType =
|
||||
| "soundcloud_track"
|
||||
| "soundcloud_playlist"
|
||||
| "spotify_song"
|
||||
| "spotify_album"
|
||||
| "spotify_playlist"
|
||||
| "youtube_video"
|
||||
| "youtube_playlist"
|
||||
| "vimeo"
|
||||
| "facebook"
|
||||
| "reverbnation"
|
||||
| "attachment"
|
||||
| "youtube_search";
|
|
@ -1,4 +1,6 @@
|
|||
export default {
|
||||
import { FiltersName } from "../types/types";
|
||||
|
||||
const FilterList = {
|
||||
bassboost: 'bass=g=20',
|
||||
'8D': 'apulsator=hz=0.09',
|
||||
vaporwave: 'aresample=48000,asetrate=48000*0.8',
|
||||
|
@ -41,6 +43,12 @@ export default {
|
|||
return Object.keys(this).length;
|
||||
},
|
||||
toString() {
|
||||
return `"${Object.values(this).join(',')}"`;
|
||||
return `${Object.values(this).join(',')}`;
|
||||
},
|
||||
create(filter?: FiltersName[]) {
|
||||
if (!filter || !Array.isArray(filter)) return this.toString();
|
||||
return filter.filter(predicate => typeof predicate === "string").map(m => this[m]).join(",");
|
||||
}
|
||||
};
|
||||
|
||||
export default FilterList;
|
16
src/utils/Constants.ts
Normal file
16
src/utils/Constants.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
export const PlayerEvents = {
|
||||
BOT_DISCONNECT: "botDisconnect",
|
||||
CHANNEL_EMPTY: "channelEmpty",
|
||||
ERROR: "error",
|
||||
NO_RESULTS: "noResults",
|
||||
PLAYLIST_ADD: "playlistAdd",
|
||||
PLAYLIST_PARSE_END: "playlistParseEnd",
|
||||
PLAYLIST_PARSE_START: "playlistParseStart",
|
||||
QUEUE_CREATE: "queueCreate",
|
||||
QUEUE_END: "queueEnd",
|
||||
SEARCH_CANCEL: "searchCancel",
|
||||
SEARCH_INVALID_RESPONSE: "searchInvalidResponse",
|
||||
SEARCH_RESULTS: "searchResults",
|
||||
TRACK_ADD: "trackAdd",
|
||||
TRACK_START: "trackStart",
|
||||
};
|
116
src/utils/Util.ts
Normal file
116
src/utils/Util.ts
Normal file
|
@ -0,0 +1,116 @@
|
|||
import { PlayerOptions, QueryType } from "../types/types";
|
||||
import { FFmpeg } from "prism-media";
|
||||
import YouTube from "youtube-sr";
|
||||
import Track from "../Structures/Track";
|
||||
// @ts-ignore
|
||||
import { validateURL as SoundcloudValidateURL } from "soundcloud-scraper";
|
||||
|
||||
const spotifySongRegex = (/https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:track\/|\?uri=spotify:track:)((\w|-){22})/);
|
||||
const spotifyPlaylistRegex = (/https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:playlist\/|\?uri=spotify:playlist:)((\w|-){22})/);
|
||||
const spotifyAlbumRegex = (/https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:album\/|\?uri=spotify:album:)((\w|-){22})/);
|
||||
const vimeoRegex = (/(http|https)?:\/\/(www\.|player\.)?vimeo\.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|video\/|)(\d+)(?:|\/\?)/);
|
||||
const facebookRegex = (/(https?:\/\/)(www\.|m\.)?(facebook|fb).com\/.*\/videos\/.*/);
|
||||
const reverbnationRegex = (/https:\/\/(www.)?reverbnation.com\/(.+)\/song\/(.+)/);
|
||||
|
||||
export default class Util {
|
||||
constructor() {
|
||||
throw new Error(`The ${this.constructor.name} class is static and cannot be instantiated!`);
|
||||
}
|
||||
|
||||
static get DefaultPlayerOptions() {
|
||||
return {
|
||||
leaveOnEnd: true,
|
||||
leaveOnStop: true,
|
||||
leaveOnEmpty: true,
|
||||
leaveOnEmptyCooldown: 0,
|
||||
autoSelfDeaf: true,
|
||||
enableLive: false,
|
||||
ytdlDownloadOptions: {}
|
||||
} as PlayerOptions;
|
||||
}
|
||||
|
||||
static checkFFmpeg(force?: boolean) {
|
||||
try {
|
||||
FFmpeg.getInfo(Boolean(force));
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static alertFFmpeg() {
|
||||
const hasFFmpeg = Util.checkFFmpeg();
|
||||
|
||||
if (!hasFFmpeg)
|
||||
console.warn(
|
||||
"[Discord Player] FFmpeg/Avconv not found! Install via \"npm install ffmpeg-static\" or download from https://ffmpeg.org/download.html"
|
||||
);
|
||||
}
|
||||
|
||||
static getQueryType(query: string): QueryType {
|
||||
if (SoundcloudValidateURL(query) && !query.includes("/sets/")) return "soundcloud_track";
|
||||
if (SoundcloudValidateURL(query) && query.includes("/sets/")) return "soundcloud_playlist";
|
||||
if (spotifySongRegex.test(query)) return "spotify_song";
|
||||
if (spotifyAlbumRegex.test(query)) return "spotify_album";
|
||||
if (spotifyPlaylistRegex.test(query)) return "spotify_playlist";
|
||||
if (YouTube.validate(query, "VIDEO")) return "youtube_video";
|
||||
if (YouTube.validate(query, "PLAYLIST")) return "youtube_playlist";
|
||||
if (vimeoRegex.test(query)) return "vimeo";
|
||||
if (facebookRegex.test(query)) return "facebook";
|
||||
if (reverbnationRegex.test(query)) return "reverbnation";
|
||||
if (Util.isURL(query)) return "attachment";
|
||||
|
||||
return "youtube_search";
|
||||
}
|
||||
|
||||
static isURL(str: string) {
|
||||
const urlRegex = '^(?!mailto:)(?:(?:http|https|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-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,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$';
|
||||
const url = new RegExp(urlRegex, 'i');
|
||||
return str.length < 2083 && url.test(str);
|
||||
}
|
||||
|
||||
static getVimeoID(query: string) {
|
||||
return Util.getQueryType(query) === "vimeo" ? query.split("/").filter(x => !!x).pop() : null
|
||||
}
|
||||
|
||||
static parseMS(milliseconds: number) {
|
||||
// taken from ms package :: https://github.com/sindresorhus/parse-ms/blob/main/index.js
|
||||
const roundTowardsZero = milliseconds > 0 ? Math.floor : Math.ceil;
|
||||
|
||||
return {
|
||||
days: roundTowardsZero(milliseconds / 86400000),
|
||||
hours: roundTowardsZero(milliseconds / 3600000) % 24,
|
||||
minutes: roundTowardsZero(milliseconds / 60000) % 60,
|
||||
seconds: roundTowardsZero(milliseconds / 1000) % 60
|
||||
};
|
||||
}
|
||||
|
||||
static durationString(durObj: object) {
|
||||
return Object.values(durObj).map(m => isNaN(m) ? 0 : m ).join(":");
|
||||
}
|
||||
|
||||
static ytSearch(query: string, options?: any): Promise<Track[]> {
|
||||
return new Promise(async (resolve) => {
|
||||
await YouTube.search(query, {
|
||||
type: "video",
|
||||
safeSearch: Boolean(options?.player.options.useSafeSearch)
|
||||
})
|
||||
.then(results => {
|
||||
resolve(results.map((r) => new Track(options?.player, {
|
||||
title: r.title,
|
||||
description: r.description,
|
||||
author: r.channel.name,
|
||||
url: r.url,
|
||||
thumbnail: r.thumbnail.displayThumbnailURL(),
|
||||
duration: r.durationFormatted,
|
||||
views: r.views,
|
||||
requestedBy: options?.user,
|
||||
fromPlaylist: false,
|
||||
source: "youtube"
|
||||
})));
|
||||
})
|
||||
.catch(() => resolve([]));
|
||||
});
|
||||
}
|
||||
}
|
153
yarn.lock
153
yarn.lock
|
@ -143,6 +143,11 @@ balanced-match@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
|
||||
|
||||
boolbase@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
|
||||
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||
|
@ -165,6 +170,30 @@ chalk@^2.0.0, chalk@^2.3.0:
|
|||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
cheerio-select-tmp@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/cheerio-select-tmp/-/cheerio-select-tmp-0.1.1.tgz#55bbef02a4771710195ad736d5e346763ca4e646"
|
||||
integrity sha512-YYs5JvbpU19VYJyj+F7oYrIE2BOll1/hRU7rEy/5+v9BzkSo3bK81iAeeQEMI92vRIxz677m72UmJUiVwwgjfQ==
|
||||
dependencies:
|
||||
css-select "^3.1.2"
|
||||
css-what "^4.0.0"
|
||||
domelementtype "^2.1.0"
|
||||
domhandler "^4.0.0"
|
||||
domutils "^2.4.4"
|
||||
|
||||
cheerio@^1.0.0-rc.3:
|
||||
version "1.0.0-rc.5"
|
||||
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.5.tgz#88907e1828674e8f9fee375188b27dadd4f0fa2f"
|
||||
integrity sha512-yoqps/VCaZgN4pfXtenwHROTp8NG6/Hlt4Jpz2FEP0ZJQ+ZUkVDd0hAPDNKhj3nakpfPt/CNs57yEtxD1bXQiw==
|
||||
dependencies:
|
||||
cheerio-select-tmp "^0.1.0"
|
||||
dom-serializer "~1.2.0"
|
||||
domhandler "^4.0.0"
|
||||
entities "~2.1.0"
|
||||
htmlparser2 "^6.0.0"
|
||||
parse5 "^6.0.0"
|
||||
parse5-htmlparser2-tree-adapter "^6.0.0"
|
||||
|
||||
chownr@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
|
||||
|
@ -214,6 +243,29 @@ core-util-is@~1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
||||
|
||||
cross-fetch@^3.0.5:
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39"
|
||||
integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==
|
||||
dependencies:
|
||||
node-fetch "2.6.1"
|
||||
|
||||
css-select@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/css-select/-/css-select-3.1.2.tgz#d52cbdc6fee379fba97fb0d3925abbd18af2d9d8"
|
||||
integrity sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA==
|
||||
dependencies:
|
||||
boolbase "^1.0.0"
|
||||
css-what "^4.0.0"
|
||||
domhandler "^4.0.0"
|
||||
domutils "^2.4.3"
|
||||
nth-check "^2.0.0"
|
||||
|
||||
css-what@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/css-what/-/css-what-4.0.0.tgz#35e73761cab2eeb3d3661126b23d7aa0e8432233"
|
||||
integrity sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A==
|
||||
|
||||
debug@4:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
|
||||
|
@ -260,6 +312,46 @@ discord.js@discordjs/discord.js:
|
|||
tweetnacl "^1.0.3"
|
||||
ws "^7.3.1"
|
||||
|
||||
dom-serializer@^1.0.1, dom-serializer@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.2.0.tgz#3433d9136aeb3c627981daa385fc7f32d27c48f1"
|
||||
integrity sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==
|
||||
dependencies:
|
||||
domelementtype "^2.0.1"
|
||||
domhandler "^4.0.0"
|
||||
entities "^2.0.0"
|
||||
|
||||
domelementtype@^2.0.1, domelementtype@^2.1.0, domelementtype@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57"
|
||||
integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==
|
||||
|
||||
domhandler@^4.0.0, domhandler@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.1.0.tgz#c1d8d494d5ec6db22de99e46a149c2a4d23ddd43"
|
||||
integrity sha512-/6/kmsGlMY4Tup/nGVutdrK9yQi4YjWVcVeoQmixpzjOUK1U7pQkvAPHBJeUxOgxF0J8f8lwCJSlCfD0V4CMGQ==
|
||||
dependencies:
|
||||
domelementtype "^2.2.0"
|
||||
|
||||
domutils@^2.4.3, domutils@^2.4.4:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.5.1.tgz#9b8e84b5d9f788499ae77506ea832e9b4f9aa1c0"
|
||||
integrity sha512-hO1XwHMGAthA/1KL7c83oip/6UWo3FlUNIuWiWKltoiQ5oCOiqths8KknvY2jpOohUoUgnwa/+Rm7UpwpSbY/Q==
|
||||
dependencies:
|
||||
dom-serializer "^1.0.1"
|
||||
domelementtype "^2.2.0"
|
||||
domhandler "^4.1.0"
|
||||
|
||||
entities@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
|
||||
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
|
||||
|
||||
entities@~2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
|
||||
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
|
||||
|
||||
escape-string-regexp@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
|
@ -335,6 +427,21 @@ has@^1.0.3:
|
|||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
himalaya@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/himalaya/-/himalaya-1.1.0.tgz#31724ae9d35714cd7c6f4be94888953f3604606a"
|
||||
integrity sha512-LLase1dHCRMel68/HZTFft0N0wti0epHr3nNY7ynpLbyZpmrKMQ8YIpiOV77TM97cNpC8Wb2n6f66IRggwdWPw==
|
||||
|
||||
htmlparser2@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.0.1.tgz#422521231ef6d42e56bd411da8ba40aa36e91446"
|
||||
integrity sha512-GDKPd+vk4jvSuvCbyuzx/unmXkk090Azec7LovXP8as1Hn8q9p3hbjmDGbUqqhknw0ajwit6LiiWqfiTUPMK7w==
|
||||
dependencies:
|
||||
domelementtype "^2.0.1"
|
||||
domhandler "^4.0.0"
|
||||
domutils "^2.4.4"
|
||||
entities "^2.0.0"
|
||||
|
||||
http-proxy-agent@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a"
|
||||
|
@ -406,7 +513,7 @@ lru-cache@^6.0.0:
|
|||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
m3u8stream@^0.8.3:
|
||||
m3u8stream@^0.8.0, m3u8stream@^0.8.3:
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/m3u8stream/-/m3u8stream-0.8.3.tgz#c4624e92b4240eb356d040c4a5e155586cf58108"
|
||||
integrity sha512-0nAcdrF8YJKUkb6PzWdvGftTPyCVWgoiot1AkNVbPKTeIGsWs6DrOjifrJ0Zi8WQfQmD2SuVCjkYIOip12igng==
|
||||
|
@ -487,7 +594,7 @@ node-addon-api@^3.1.0:
|
|||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.1.0.tgz#98b21931557466c6729e51cb77cd39c965f42239"
|
||||
integrity sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw==
|
||||
|
||||
node-fetch@^2.6.0, node-fetch@^2.6.1:
|
||||
node-fetch@2.6.1, node-fetch@^2.6.0, node-fetch@^2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||
|
@ -509,6 +616,13 @@ npmlog@^4.1.2:
|
|||
gauge "~2.7.3"
|
||||
set-blocking "~2.0.0"
|
||||
|
||||
nth-check@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.0.tgz#1bb4f6dac70072fc313e8c9cd1417b5074c0a125"
|
||||
integrity sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==
|
||||
dependencies:
|
||||
boolbase "^1.0.0"
|
||||
|
||||
number-is-nan@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
|
||||
|
@ -526,6 +640,18 @@ once@^1.3.0:
|
|||
dependencies:
|
||||
wrappy "1"
|
||||
|
||||
parse5-htmlparser2-tree-adapter@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6"
|
||||
integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==
|
||||
dependencies:
|
||||
parse5 "^6.0.1"
|
||||
|
||||
parse5@^6.0.0, parse5@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
|
||||
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
|
||||
|
||||
path-is-absolute@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||
|
@ -624,6 +750,29 @@ simple-youtube-api@^5.2.1:
|
|||
iso8601-duration "^1.2.0"
|
||||
node-fetch "^2.6.0"
|
||||
|
||||
soundcloud-scraper@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/soundcloud-scraper/-/soundcloud-scraper-4.0.3.tgz#cd7ed1d7b6ed1d7729fd7580c011281f652b920f"
|
||||
integrity sha512-A0a6sVJ2wkkWIX8Ft3L63sfHBlFDRAaPFif+SWi07KCNLh8YTcylw45pts76pndxlupKwV2NgOTIYeF/F9tg8w==
|
||||
dependencies:
|
||||
cheerio "^1.0.0-rc.3"
|
||||
m3u8stream "^0.8.0"
|
||||
node-fetch "^2.6.1"
|
||||
|
||||
spotify-uri@^2.1.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/spotify-uri/-/spotify-uri-2.2.0.tgz#8db641615cf6e122284874287fe39e89595922df"
|
||||
integrity sha512-uUybj02bfyfCoZ0MJ80MkqbKxtIVRJfbRGk05KJFq1li3zb7yNfN1f+TAw4wcXgp7jLWExeiw2wyPQXZ8PHtfg==
|
||||
|
||||
spotify-url-info@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/spotify-url-info/-/spotify-url-info-2.2.0.tgz#7d14adbae65b54b918c46e2dcfdf02b1146f85c8"
|
||||
integrity sha512-GEMoMf2RF+CSPsSGstY/9c7dgViKOKJ09bFZTwrU4KzQ+JpLq+0Ho4eMCeeGmES94yjBz+GHMtBfTcp+4DxEbA==
|
||||
dependencies:
|
||||
cross-fetch "^3.0.5"
|
||||
himalaya "^1.1.0"
|
||||
spotify-uri "^2.1.0"
|
||||
|
||||
sprintf-js@~1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||
|
|
Loading…
Reference in a new issue