2021-04-19 18:32:10 +05:00
|
|
|
import { QueryType, TimeData } from '../types/types';
|
2021-04-06 17:58:46 +05:00
|
|
|
import { FFmpeg } from 'prism-media';
|
|
|
|
import YouTube from 'youtube-sr';
|
2021-04-06 20:38:17 +05:00
|
|
|
import { Track } from '../Structures/Track';
|
2021-04-06 17:55:29 +05:00
|
|
|
// @ts-ignore
|
2021-04-06 17:58:46 +05:00
|
|
|
import { validateURL as SoundcloudValidateURL } from 'soundcloud-scraper';
|
2021-04-09 15:04:36 +05:00
|
|
|
import { VoiceChannel } from 'discord.js';
|
2021-04-06 17:55:29 +05:00
|
|
|
|
2021-04-06 17:58:46 +05:00
|
|
|
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\/(.+)/;
|
2021-04-09 18:01:16 +05:00
|
|
|
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*)?$/;
|
2021-04-06 17:55:29 +05:00
|
|
|
|
2021-04-06 20:38:17 +05:00
|
|
|
export class Util {
|
2021-04-21 10:08:33 +05:00
|
|
|
/**
|
|
|
|
* Static Player Util class
|
|
|
|
*/
|
2021-04-06 17:55:29 +05:00
|
|
|
constructor() {
|
|
|
|
throw new Error(`The ${this.constructor.name} class is static and cannot be instantiated!`);
|
|
|
|
}
|
|
|
|
|
2021-04-21 10:08:33 +05:00
|
|
|
/**
|
|
|
|
* Checks FFmpeg Version
|
2021-04-21 11:52:59 +05:00
|
|
|
* @param {Boolean} [force] If it should forcefully get the version
|
2021-04-21 10:08:33 +05:00
|
|
|
*/
|
2021-04-19 18:32:10 +05:00
|
|
|
static getFFmpegVersion(force?: boolean): string {
|
2021-04-06 17:55:29 +05:00
|
|
|
try {
|
2021-04-17 09:01:06 +05:00
|
|
|
const info = FFmpeg.getInfo(Boolean(force));
|
2021-04-06 17:55:29 +05:00
|
|
|
|
2021-04-17 09:01:06 +05:00
|
|
|
return info.version;
|
2021-04-06 17:55:29 +05:00
|
|
|
} catch {
|
2021-04-17 09:01:06 +05:00
|
|
|
return null;
|
2021-04-06 17:55:29 +05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-21 10:08:33 +05:00
|
|
|
/**
|
|
|
|
* Checks FFmpeg
|
2021-04-21 11:52:59 +05:00
|
|
|
* @param {Boolean} [force] If it should forcefully get the version
|
2021-04-21 10:08:33 +05:00
|
|
|
*/
|
2021-04-19 18:32:10 +05:00
|
|
|
static checkFFmpeg(force?: boolean): boolean {
|
2021-04-17 09:01:06 +05:00
|
|
|
const version = Util.getFFmpegVersion(force);
|
|
|
|
return version === null ? false : true;
|
|
|
|
}
|
|
|
|
|
2021-04-21 10:08:33 +05:00
|
|
|
/**
|
|
|
|
* Alerts if FFmpeg is not available
|
|
|
|
*/
|
2021-04-19 18:32:10 +05:00
|
|
|
static alertFFmpeg(): void {
|
2021-04-06 17:55:29 +05:00
|
|
|
const hasFFmpeg = Util.checkFFmpeg();
|
|
|
|
|
|
|
|
if (!hasFFmpeg)
|
|
|
|
console.warn(
|
2021-04-06 17:58:46 +05:00
|
|
|
'[Discord Player] FFmpeg/Avconv not found! Install via "npm install ffmpeg-static" or download from https://ffmpeg.org/download.html'
|
2021-04-06 17:55:29 +05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-04-21 10:08:33 +05:00
|
|
|
/**
|
|
|
|
* Resolves query type
|
2021-04-21 11:52:59 +05:00
|
|
|
* @param {String} query The query
|
2021-04-21 10:08:33 +05:00
|
|
|
*/
|
2021-04-06 17:55:29 +05:00
|
|
|
static getQueryType(query: string): QueryType {
|
2021-04-06 17:58:46 +05:00
|
|
|
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, 'PLAYLIST')) return 'youtube_playlist';
|
2021-04-07 19:05:35 +05:00
|
|
|
if (YouTube.validate(query, 'VIDEO')) return 'youtube_video';
|
2021-04-06 17:58:46 +05:00
|
|
|
if (vimeoRegex.test(query)) return 'vimeo';
|
|
|
|
if (facebookRegex.test(query)) return 'facebook';
|
|
|
|
if (reverbnationRegex.test(query)) return 'reverbnation';
|
|
|
|
if (Util.isURL(query)) return 'attachment';
|
2021-04-06 17:55:29 +05:00
|
|
|
|
2021-04-06 17:58:46 +05:00
|
|
|
return 'youtube_search';
|
2021-04-06 17:55:29 +05:00
|
|
|
}
|
|
|
|
|
2021-04-21 10:08:33 +05:00
|
|
|
/**
|
|
|
|
* Checks if the given string is url
|
2021-04-21 11:52:59 +05:00
|
|
|
* @param {String} str URL to check
|
2021-04-21 10:08:33 +05:00
|
|
|
*/
|
2021-04-19 18:32:10 +05:00
|
|
|
static isURL(str: string): boolean {
|
2021-04-09 18:01:16 +05:00
|
|
|
return str.length < 2083 && attachmentRegex.test(str);
|
2021-04-06 17:55:29 +05:00
|
|
|
}
|
|
|
|
|
2021-04-21 10:08:33 +05:00
|
|
|
/**
|
|
|
|
* Returns Vimeo ID
|
2021-04-21 11:52:59 +05:00
|
|
|
* @param {String} query Vimeo link
|
2021-04-21 10:08:33 +05:00
|
|
|
*/
|
2021-04-19 18:32:10 +05:00
|
|
|
static getVimeoID(query: string): string {
|
2021-04-06 17:58:46 +05:00
|
|
|
return Util.getQueryType(query) === 'vimeo'
|
|
|
|
? query
|
|
|
|
.split('/')
|
|
|
|
.filter((x) => !!x)
|
|
|
|
.pop()
|
|
|
|
: null;
|
2021-04-06 17:55:29 +05:00
|
|
|
}
|
|
|
|
|
2021-04-21 10:08:33 +05:00
|
|
|
/**
|
|
|
|
* Parses ms time
|
2021-04-21 11:52:59 +05:00
|
|
|
* @param {Number} milliseconds Time to parse
|
2021-04-21 10:08:33 +05:00
|
|
|
*/
|
2021-04-19 18:32:10 +05:00
|
|
|
static parseMS(milliseconds: number): TimeData {
|
2021-04-06 17:55:29 +05:00
|
|
|
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
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-04-21 10:08:33 +05:00
|
|
|
/**
|
|
|
|
* Creates simple duration string
|
|
|
|
* @param {object} durObj Duration object
|
|
|
|
*/
|
2021-04-19 18:32:10 +05:00
|
|
|
static durationString(durObj: object): string {
|
2021-04-06 17:58:46 +05:00
|
|
|
return Object.values(durObj)
|
|
|
|
.map((m) => (isNaN(m) ? 0 : m))
|
|
|
|
.join(':');
|
2021-04-06 17:55:29 +05:00
|
|
|
}
|
|
|
|
|
2021-04-21 10:08:33 +05:00
|
|
|
/**
|
|
|
|
* Makes youtube searches
|
2021-04-21 11:52:59 +05:00
|
|
|
* @param {String} query The query
|
2021-04-21 10:08:33 +05:00
|
|
|
* @param {any} options Options
|
|
|
|
* @returns {Promise<Track[]>}
|
|
|
|
*/
|
2021-04-06 17:55:29 +05:00
|
|
|
static ytSearch(query: string, options?: any): Promise<Track[]> {
|
|
|
|
return new Promise(async (resolve) => {
|
|
|
|
await YouTube.search(query, {
|
2021-04-06 17:58:46 +05:00
|
|
|
type: 'video',
|
2021-04-07 19:05:35 +05:00
|
|
|
safeSearch: Boolean(options?.player.options.useSafeSearch),
|
|
|
|
limit: options.limit ?? 10
|
2021-04-06 17:55:29 +05:00
|
|
|
})
|
2021-04-06 17:58:46 +05:00
|
|
|
.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(),
|
2021-04-10 22:52:48 +05:00
|
|
|
duration: Util.buildTimeCode(Util.parseMS(r.duration)),
|
2021-04-06 17:58:46 +05:00
|
|
|
views: r.views,
|
|
|
|
requestedBy: options?.user,
|
2021-04-07 19:05:35 +05:00
|
|
|
fromPlaylist: Boolean(options?.pl),
|
2021-04-06 17:58:46 +05:00
|
|
|
source: 'youtube'
|
|
|
|
})
|
|
|
|
)
|
|
|
|
);
|
2021-04-06 17:55:29 +05:00
|
|
|
})
|
|
|
|
.catch(() => resolve([]));
|
|
|
|
});
|
|
|
|
}
|
2021-04-07 19:25:45 +05:00
|
|
|
|
2021-04-21 10:08:33 +05:00
|
|
|
/**
|
|
|
|
* Checks if this system is running in replit.com
|
|
|
|
*/
|
2021-04-19 18:32:10 +05:00
|
|
|
static isRepl(): boolean {
|
2021-04-07 19:25:45 +05:00
|
|
|
if ('DP_REPL_NOCHECK' in process.env) return false;
|
|
|
|
|
|
|
|
const REPL_IT_PROPS = [
|
|
|
|
'REPL_SLUG',
|
|
|
|
'REPL_OWNER',
|
|
|
|
'REPL_IMAGE',
|
|
|
|
'REPL_PUBKEYS',
|
|
|
|
'REPL_ID',
|
|
|
|
'REPL_LANGUAGE',
|
|
|
|
'REPLIT_DB_URL'
|
|
|
|
];
|
|
|
|
|
|
|
|
for (const prop of REPL_IT_PROPS) if (prop in process.env) return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2021-04-09 15:04:36 +05:00
|
|
|
|
2021-04-21 10:08:33 +05:00
|
|
|
/**
|
|
|
|
* Checks if the given voice channel is empty
|
2021-04-21 12:09:16 +05:00
|
|
|
* @param {DiscordVoiceChannel} channel The voice channel
|
2021-04-21 10:08:33 +05:00
|
|
|
*/
|
2021-04-19 18:32:10 +05:00
|
|
|
static isVoiceEmpty(channel: VoiceChannel): boolean {
|
2021-04-09 15:04:36 +05:00
|
|
|
return channel.members.filter((member) => !member.user.bot).size === 0;
|
|
|
|
}
|
|
|
|
|
2021-04-21 10:08:33 +05:00
|
|
|
/**
|
|
|
|
* Builds time code
|
|
|
|
* @param {object} data The data to build time code from
|
|
|
|
*/
|
2021-04-19 18:32:10 +05:00
|
|
|
static buildTimeCode(data: any): string {
|
2021-04-09 15:04:36 +05:00
|
|
|
const items = Object.keys(data);
|
|
|
|
const required = ['days', 'hours', 'minutes', 'seconds'];
|
|
|
|
|
|
|
|
const parsed = items.filter((x) => required.includes(x)).map((m) => (data[m] > 0 ? data[m] : ''));
|
|
|
|
const final = parsed
|
|
|
|
.filter((x) => !!x)
|
|
|
|
.map((x) => x.toString().padStart(2, '0'))
|
|
|
|
.join(':');
|
|
|
|
return final.length <= 3 ? `0:${final.padStart(2, '0') || 0}` : final;
|
|
|
|
}
|
2021-04-19 13:33:34 +05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Manage CJS require
|
2021-04-21 11:52:59 +05:00
|
|
|
* @param {String} id id to require
|
2021-04-19 13:33:34 +05:00
|
|
|
*/
|
|
|
|
static require(id: string): any {
|
|
|
|
try {
|
|
|
|
return require(id);
|
|
|
|
} catch {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2021-04-06 17:55:29 +05:00
|
|
|
}
|
2021-04-06 20:38:17 +05:00
|
|
|
|
|
|
|
export default Util;
|