diff --git a/src/Player.ts b/src/Player.ts index 414d691..261ecb8 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -1,19 +1,19 @@ -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"; +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"; +import spotify from 'spotify-url-info'; // @ts-ignore -import { Client as SoundCloudClient } from "soundcloud-scraper"; +import { Client as SoundCloudClient } from 'soundcloud-scraper'; -const SoundCloud = new SoundCloudClient; +const SoundCloud = new SoundCloudClient(); export default class Player extends EventEmitter { public client!: Client; @@ -29,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 }); @@ -57,43 +57,55 @@ export default class Player extends EventEmitter { return AudioFilters; } - private _searchTracks(message: Message, query: string, firstResult?: boolean, isAttachment?: boolean): Promise { + private _searchTracks( + message: Message, + query: string, + firstResult?: boolean, + isAttachment?: boolean + ): Promise { return new Promise(async (resolve) => { let tracks: Track[] = []; - let queryType = Util.getQueryType(query); + const 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 - }); + 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 }); + tracks.push(track); } } - } - break; + 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 }); } @@ -103,7 +115,7 @@ export default class Player extends EventEmitter { const collectorString = `${message.author.id}-${message.channel.id}`; const currentCollector = this._resultsCollectors.get(collectorString); - if (currentCollector) currentCollector.stop() + if (currentCollector) currentCollector.stop(); const collector = message.channel.createMessageCollector((m) => m.author.id === message.author.id, { time: 60000 @@ -113,8 +125,8 @@ export default class Player extends EventEmitter { this.emit(PlayerEvents.SEARCH_RESULTS, message, query, tracks, collector); - collector.on("collect", ({ content }) => { - if (content === "cancel") { + collector.on('collect', ({ content }) => { + if (content === 'cancel') { collector.stop(); return this.emit(PlayerEvents.SEARCH_CANCEL, message, query, tracks); } @@ -127,10 +139,10 @@ export default class Player extends EventEmitter { } else { this.emit(PlayerEvents.SEARCH_INVALID_RESPONSE, message, query, tracks, content, collector); } - }) + }); - collector.on("end", (collected, reason) => { - if (reason === "time") { + collector.on('end', (collected, reason) => { + if (reason === 'time') { this.emit(PlayerEvents.SEARCH_CANCEL, message, query, tracks); } }); diff --git a/src/Structures/Queue.ts b/src/Structures/Queue.ts index c853b5c..41a48ab 100644 --- a/src/Structures/Queue.ts +++ b/src/Structures/Queue.ts @@ -1,9 +1,9 @@ -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"; +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 extends EventEmitter { public player!: Player; @@ -28,7 +28,7 @@ export default class Queue extends EventEmitter { /** * 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 @@ -93,7 +93,7 @@ export default class Queue extends EventEmitter { // @ts-ignore this.filters = {}; - Object.keys(AudioFilters).forEach(fn => { + Object.keys(AudioFilters).forEach((fn) => { // @ts-ignore this.filters[fn] = false; }); diff --git a/src/Structures/Track.ts b/src/Structures/Track.ts index 970aeb4..b74bef0 100644 --- a/src/Structures/Track.ts +++ b/src/Structures/Track.ts @@ -1,6 +1,6 @@ -import Player from "../Player"; -import { User } from "discord.js"; -import { TrackData } from "../types/types"; +import Player from '../Player'; +import { User } from 'discord.js'; +import { TrackData } from '../types/types'; export default class Track { public player!: Player; @@ -19,31 +19,31 @@ 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); // raw - Object.defineProperty(this, "raw", { get: () => data, enumerable: false }); + 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)); + return this.player.queues.find((q) => q.tracks.includes(this)); } /** @@ -56,7 +56,11 @@ export default class Track { 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); + return this.duration + .split(':') + .reverse() + .map((m, i) => parseInt(m) * times(60, i)) + .reduce((a, c) => a + c, 0); } toString() { diff --git a/src/index.ts b/src/index.ts index f54dbd7..9cce00d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ -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"; +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'; diff --git a/src/types/types.ts b/src/types/types.ts index 56762fe..69c5214 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1,5 +1,5 @@ -import { downloadOptions } from "ytdl-core"; -import { User } from "discord.js"; +import { downloadOptions } from 'ytdl-core'; +import { User } from 'discord.js'; export interface PlayerOptions { leaveOnEnd?: boolean; @@ -14,36 +14,36 @@ export interface PlayerOptions { } 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"; + | '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 type TrackSource = 'soundcloud' | 'youtube' | 'arbitrary'; export interface TrackData { title: string; @@ -60,46 +60,46 @@ export interface TrackData { } 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; -} + 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"; \ No newline at end of file +export type QueryType = + | 'soundcloud_track' + | 'soundcloud_playlist' + | 'spotify_song' + | 'spotify_album' + | 'spotify_playlist' + | 'youtube_video' + | 'youtube_playlist' + | 'vimeo' + | 'facebook' + | 'reverbnation' + | 'attachment' + | 'youtube_search'; diff --git a/src/utils/AudioFilters.ts b/src/utils/AudioFilters.ts index e8de4f8..736e300 100644 --- a/src/utils/AudioFilters.ts +++ b/src/utils/AudioFilters.ts @@ -1,4 +1,4 @@ -import { FiltersName } from "../types/types"; +import { FiltersName } from '../types/types'; const FilterList = { bassboost: 'bass=g=20', @@ -47,8 +47,11 @@ const FilterList = { }, create(filter?: FiltersName[]) { if (!filter || !Array.isArray(filter)) return this.toString(); - return filter.filter(predicate => typeof predicate === "string").map(m => this[m]).join(","); + return filter + .filter((predicate) => typeof predicate === 'string') + .map((m) => this[m]) + .join(','); } }; -export default FilterList; \ No newline at end of file +export default FilterList; diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts index e4f21a7..1b501e7 100644 --- a/src/utils/Constants.ts +++ b/src/utils/Constants.ts @@ -1,16 +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", -}; \ No newline at end of file + 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' +}; diff --git a/src/utils/Util.ts b/src/utils/Util.ts index fa4aeb4..3e7bb19 100644 --- a/src/utils/Util.ts +++ b/src/utils/Util.ts @@ -1,16 +1,16 @@ -import { PlayerOptions, QueryType } from "../types/types"; -import { FFmpeg } from "prism-media"; -import YouTube from "youtube-sr"; -import Track from "../Structures/Track"; +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"; +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\/(.+)/); +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() { @@ -44,34 +44,40 @@ export default class Util { if (!hasFFmpeg) console.warn( - "[Discord Player] FFmpeg/Avconv not found! Install via \"npm install ffmpeg-static\" or download from https://ffmpeg.org/download.html" + '[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"; + 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"; + 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 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 + return Util.getQueryType(query) === 'vimeo' + ? query + .split('/') + .filter((x) => !!x) + .pop() + : null; } static parseMS(milliseconds: number) { @@ -87,28 +93,35 @@ export default class Util { } static durationString(durObj: object) { - return Object.values(durObj).map(m => isNaN(m) ? 0 : m ).join(":"); + return Object.values(durObj) + .map((m) => (isNaN(m) ? 0 : m)) + .join(':'); } static ytSearch(query: string, options?: any): Promise { return new Promise(async (resolve) => { await YouTube.search(query, { - type: "video", + 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" - }))); + .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([])); }); diff --git a/tslint.json b/tslint.json index 5d61f36..4e5571c 100644 --- a/tslint.json +++ b/tslint.json @@ -8,7 +8,8 @@ "object-literal-sort-keys": false, "interface-name": false, "no-empty": false, - "no-console": false + "no-console": false, + "radix": false }, "rulesDirectory": [] } \ No newline at end of file