This commit is contained in:
Snowflake107 2021-04-06 18:43:46 +05:45
parent b40c070b2e
commit 073d94e7a9
9 changed files with 244 additions and 211 deletions

View file

@ -1,19 +1,19 @@
import YouTube from "youtube-sr"; import YouTube from 'youtube-sr';
import { EventEmitter } from "events"; import { EventEmitter } from 'events';
import { Client, Collection, Snowflake, Collector, Message } from "discord.js"; import { Client, Collection, Snowflake, Collector, Message } from 'discord.js';
import { PlayerOptions } from "./types/types"; import { PlayerOptions } from './types/types';
import Util from "./utils/Util"; import Util from './utils/Util';
import AudioFilters from "./utils/AudioFilters"; import AudioFilters from './utils/AudioFilters';
import Queue from "./Structures/Queue"; import Queue from './Structures/Queue';
import Track from "./Structures/Track"; import Track from './Structures/Track';
import { PlayerEvents } from "./utils/Constants"; import { PlayerEvents } from './utils/Constants';
// @ts-ignore // @ts-ignore
import spotify from "spotify-url-info"; import spotify from 'spotify-url-info';
// @ts-ignore // @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 { export default class Player extends EventEmitter {
public client!: Client; public client!: Client;
@ -29,7 +29,7 @@ export default class Player extends EventEmitter {
/** /**
* The discord client that instantiated this player * The discord client that instantiated this player
*/ */
Object.defineProperty(this, "client", { Object.defineProperty(this, 'client', {
value: client, value: client,
enumerable: false enumerable: false
}); });
@ -57,43 +57,55 @@ export default class Player extends EventEmitter {
return AudioFilters; return AudioFilters;
} }
private _searchTracks(message: Message, query: string, firstResult?: boolean, isAttachment?: boolean): Promise<Track> { private _searchTracks(
message: Message,
query: string,
firstResult?: boolean,
isAttachment?: boolean
): Promise<Track> {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
let tracks: Track[] = []; let tracks: Track[] = [];
let queryType = Util.getQueryType(query); const queryType = Util.getQueryType(query);
switch(queryType) { switch (queryType) {
case "soundcloud_track": { case 'soundcloud_track':
const data = await SoundCloud.getSongInfo(query).catch(() => { }) {
if (data) { const data = await SoundCloud.getSongInfo(query).catch(() => {});
const track = new Track(this, { if (data) {
title: data.title, const track = new Track(this, {
url: data.url, title: data.title,
duration: Util.durationString(Util.parseMS(data.duration / 1000)), url: data.url,
description: data.description, duration: Util.durationString(Util.parseMS(data.duration / 1000)),
thumbnail: data.thumbnail, description: data.description,
views: data.playCount, thumbnail: data.thumbnail,
author: data.author, views: data.playCount,
requestedBy: message.author, author: data.author,
fromPlaylist: false, requestedBy: message.author,
source: "soundcloud", fromPlaylist: false,
engine: data source: 'soundcloud',
}); engine: data
});
tracks.push(track) 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;
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: default:
tracks = await Util.ytSearch(query, { user: message.author, player: this }); 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 collectorString = `${message.author.id}-${message.channel.id}`;
const currentCollector = this._resultsCollectors.get(collectorString); 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, { const collector = message.channel.createMessageCollector((m) => m.author.id === message.author.id, {
time: 60000 time: 60000
@ -113,8 +125,8 @@ export default class Player extends EventEmitter {
this.emit(PlayerEvents.SEARCH_RESULTS, message, query, tracks, collector); this.emit(PlayerEvents.SEARCH_RESULTS, message, query, tracks, collector);
collector.on("collect", ({ content }) => { collector.on('collect', ({ content }) => {
if (content === "cancel") { if (content === 'cancel') {
collector.stop(); collector.stop();
return this.emit(PlayerEvents.SEARCH_CANCEL, message, query, tracks); return this.emit(PlayerEvents.SEARCH_CANCEL, message, query, tracks);
} }
@ -127,10 +139,10 @@ export default class Player extends EventEmitter {
} else { } else {
this.emit(PlayerEvents.SEARCH_INVALID_RESPONSE, message, query, tracks, content, collector); this.emit(PlayerEvents.SEARCH_INVALID_RESPONSE, message, query, tracks, content, collector);
} }
}) });
collector.on("end", (collected, reason) => { collector.on('end', (collected, reason) => {
if (reason === "time") { if (reason === 'time') {
this.emit(PlayerEvents.SEARCH_CANCEL, message, query, tracks); this.emit(PlayerEvents.SEARCH_CANCEL, message, query, tracks);
} }
}); });

View file

@ -1,9 +1,9 @@
import { Message, Snowflake, VoiceConnection } from "discord.js"; import { Message, Snowflake, VoiceConnection } from 'discord.js';
import AudioFilters from "../utils/AudioFilters"; import AudioFilters from '../utils/AudioFilters';
import Player from "../Player"; import Player from '../Player';
import { EventEmitter } from "events"; import { EventEmitter } from 'events';
import Track from "./Track"; import Track from './Track';
import { QueueFilters } from "../types/types"; import { QueueFilters } from '../types/types';
export default class Queue extends EventEmitter { export default class Queue extends EventEmitter {
public player!: Player; public player!: Player;
@ -28,7 +28,7 @@ export default class Queue extends EventEmitter {
/** /**
* The player that instantiated this Queue * 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 * ID of the guild assigned to this queue
@ -93,7 +93,7 @@ export default class Queue extends EventEmitter {
// @ts-ignore // @ts-ignore
this.filters = {}; this.filters = {};
Object.keys(AudioFilters).forEach(fn => { Object.keys(AudioFilters).forEach((fn) => {
// @ts-ignore // @ts-ignore
this.filters[fn] = false; this.filters[fn] = false;
}); });

View file

@ -1,6 +1,6 @@
import Player from "../Player"; import Player from '../Player';
import { User } from "discord.js"; import { User } from 'discord.js';
import { TrackData } from "../types/types"; import { TrackData } from '../types/types';
export default class Track { export default class Track {
public player!: Player; public player!: Player;
@ -19,31 +19,31 @@ export default class Track {
/** /**
* The player that instantiated this 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); void this._patch(data);
} }
private _patch(data: TrackData) { private _patch(data: TrackData) {
this.title = data.title ?? ""; this.title = data.title ?? '';
this.description = data.description ?? ""; this.description = data.description ?? '';
this.author = data.author ?? ""; this.author = data.author ?? '';
this.url = data.url ?? ""; this.url = data.url ?? '';
this.thumbnail = data.thumbnail ?? ""; this.thumbnail = data.thumbnail ?? '';
this.duration = data.duration ?? ""; this.duration = data.duration ?? '';
this.views = data.views ?? 0; this.views = data.views ?? 0;
this.requestedBy = data.requestedBy; this.requestedBy = data.requestedBy;
this.fromPlaylist = Boolean(data.fromPlaylist); this.fromPlaylist = Boolean(data.fromPlaylist);
// raw // 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 * The queue in which this track is located
*/ */
get queue() { 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 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() { toString() {

View file

@ -1,7 +1,7 @@
export * as AudioFilters from "./utils/AudioFilters"; export * as AudioFilters from './utils/AudioFilters';
export * as Player from "./Player"; export * as Player from './Player';
export * as Util from "./utils/Util"; export * as Util from './utils/Util';
export * as Track from "./Structures/Track"; export * as Track from './Structures/Track';
export * as Queue from "./Structures/Queue"; export * as Queue from './Structures/Queue';
export * from "./types/types"; export * from './types/types';
export { version } from "../package.json"; export { version } from '../package.json';

View file

@ -1,5 +1,5 @@
import { downloadOptions } from "ytdl-core"; import { downloadOptions } from 'ytdl-core';
import { User } from "discord.js"; import { User } from 'discord.js';
export interface PlayerOptions { export interface PlayerOptions {
leaveOnEnd?: boolean; leaveOnEnd?: boolean;
@ -14,36 +14,36 @@ export interface PlayerOptions {
} }
export type FiltersName = export type FiltersName =
| "bassboost" | 'bassboost'
| "8D" | '8D'
| "vaporwave" | 'vaporwave'
| "nightcore" | 'nightcore'
| "phaser" | 'phaser'
| "tremolo" | 'tremolo'
| "vibrato" | 'vibrato'
| "reverse" | 'reverse'
| "treble" | 'treble'
| "normalizer" | 'normalizer'
| "surrounding" | 'surrounding'
| "pulsator" | 'pulsator'
| "subboost" | 'subboost'
| "karaoke" | 'karaoke'
| "flanger" | 'flanger'
| "gate" | 'gate'
| "haas" | 'haas'
| "mcompand" | 'mcompand'
| "mono" | 'mono'
| "mstlr" | 'mstlr'
| "mstrr" | 'mstrr'
| "compressor" | 'compressor'
| "expander" | 'expander'
| "softlimiter" | 'softlimiter'
| "chorus" | 'chorus'
| "chorus2d" | 'chorus2d'
| "chorus3d" | 'chorus3d'
| "fadein"; | 'fadein';
export type TrackSource = "soundcloud" | "youtube" | "arbitrary"; export type TrackSource = 'soundcloud' | 'youtube' | 'arbitrary';
export interface TrackData { export interface TrackData {
title: string; title: string;
@ -60,46 +60,46 @@ export interface TrackData {
} }
export type QueueFilters = { export type QueueFilters = {
"bassboost": boolean; bassboost: boolean;
"8D": boolean; '8D': boolean;
"vaporwave": boolean; vaporwave: boolean;
"nightcore": boolean; nightcore: boolean;
"phaser": boolean; phaser: boolean;
"tremolo": boolean; tremolo: boolean;
"vibrato": boolean; vibrato: boolean;
"reverse": boolean; reverse: boolean;
"treble": boolean; treble: boolean;
"normalizer": boolean; normalizer: boolean;
"surrounding": boolean; surrounding: boolean;
"pulsator": boolean; pulsator: boolean;
"subboost": boolean; subboost: boolean;
"karaoke": boolean; karaoke: boolean;
"flanger": boolean; flanger: boolean;
"gate": boolean; gate: boolean;
"haas": boolean; haas: boolean;
"mcompand": boolean; mcompand: boolean;
"mono": boolean; mono: boolean;
'mstlr': boolean; mstlr: boolean;
"mstrr": boolean; mstrr: boolean;
"compressor": boolean; compressor: boolean;
"expander": boolean; expander: boolean;
"softlimiter": boolean; softlimiter: boolean;
"chorus": boolean; chorus: boolean;
"chorus2d": boolean; chorus2d: boolean;
"chorus3d": boolean; chorus3d: boolean;
"fadein": boolean; fadein: boolean;
} };
export type QueryType = export type QueryType =
| "soundcloud_track" | 'soundcloud_track'
| "soundcloud_playlist" | 'soundcloud_playlist'
| "spotify_song" | 'spotify_song'
| "spotify_album" | 'spotify_album'
| "spotify_playlist" | 'spotify_playlist'
| "youtube_video" | 'youtube_video'
| "youtube_playlist" | 'youtube_playlist'
| "vimeo" | 'vimeo'
| "facebook" | 'facebook'
| "reverbnation" | 'reverbnation'
| "attachment" | 'attachment'
| "youtube_search"; | 'youtube_search';

View file

@ -1,4 +1,4 @@
import { FiltersName } from "../types/types"; import { FiltersName } from '../types/types';
const FilterList = { const FilterList = {
bassboost: 'bass=g=20', bassboost: 'bass=g=20',
@ -47,8 +47,11 @@ const FilterList = {
}, },
create(filter?: FiltersName[]) { create(filter?: FiltersName[]) {
if (!filter || !Array.isArray(filter)) return this.toString(); 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; export default FilterList;

View file

@ -1,16 +1,16 @@
export const PlayerEvents = { export const PlayerEvents = {
BOT_DISCONNECT: "botDisconnect", BOT_DISCONNECT: 'botDisconnect',
CHANNEL_EMPTY: "channelEmpty", CHANNEL_EMPTY: 'channelEmpty',
ERROR: "error", ERROR: 'error',
NO_RESULTS: "noResults", NO_RESULTS: 'noResults',
PLAYLIST_ADD: "playlistAdd", PLAYLIST_ADD: 'playlistAdd',
PLAYLIST_PARSE_END: "playlistParseEnd", PLAYLIST_PARSE_END: 'playlistParseEnd',
PLAYLIST_PARSE_START: "playlistParseStart", PLAYLIST_PARSE_START: 'playlistParseStart',
QUEUE_CREATE: "queueCreate", QUEUE_CREATE: 'queueCreate',
QUEUE_END: "queueEnd", QUEUE_END: 'queueEnd',
SEARCH_CANCEL: "searchCancel", SEARCH_CANCEL: 'searchCancel',
SEARCH_INVALID_RESPONSE: "searchInvalidResponse", SEARCH_INVALID_RESPONSE: 'searchInvalidResponse',
SEARCH_RESULTS: "searchResults", SEARCH_RESULTS: 'searchResults',
TRACK_ADD: "trackAdd", TRACK_ADD: 'trackAdd',
TRACK_START: "trackStart", TRACK_START: 'trackStart'
}; };

View file

@ -1,16 +1,16 @@
import { PlayerOptions, QueryType } from "../types/types"; import { PlayerOptions, QueryType } from '../types/types';
import { FFmpeg } from "prism-media"; import { FFmpeg } from 'prism-media';
import YouTube from "youtube-sr"; import YouTube from 'youtube-sr';
import Track from "../Structures/Track"; import Track from '../Structures/Track';
// @ts-ignore // @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 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 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 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 vimeoRegex = /(http|https)?:\/\/(www\.|player\.)?vimeo\.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|video\/|)(\d+)(?:|\/\?)/;
const facebookRegex = (/(https?:\/\/)(www\.|m\.)?(facebook|fb).com\/.*\/videos\/.*/); const facebookRegex = /(https?:\/\/)(www\.|m\.)?(facebook|fb).com\/.*\/videos\/.*/;
const reverbnationRegex = (/https:\/\/(www.)?reverbnation.com\/(.+)\/song\/(.+)/); const reverbnationRegex = /https:\/\/(www.)?reverbnation.com\/(.+)\/song\/(.+)/;
export default class Util { export default class Util {
constructor() { constructor() {
@ -44,34 +44,40 @@ export default class Util {
if (!hasFFmpeg) if (!hasFFmpeg)
console.warn( 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 { static getQueryType(query: string): QueryType {
if (SoundcloudValidateURL(query) && !query.includes("/sets/")) return "soundcloud_track"; if (SoundcloudValidateURL(query) && !query.includes('/sets/')) return 'soundcloud_track';
if (SoundcloudValidateURL(query) && query.includes("/sets/")) return "soundcloud_playlist"; if (SoundcloudValidateURL(query) && query.includes('/sets/')) return 'soundcloud_playlist';
if (spotifySongRegex.test(query)) return "spotify_song"; if (spotifySongRegex.test(query)) return 'spotify_song';
if (spotifyAlbumRegex.test(query)) return "spotify_album"; if (spotifyAlbumRegex.test(query)) return 'spotify_album';
if (spotifyPlaylistRegex.test(query)) return "spotify_playlist"; if (spotifyPlaylistRegex.test(query)) return 'spotify_playlist';
if (YouTube.validate(query, "VIDEO")) return "youtube_video"; if (YouTube.validate(query, 'VIDEO')) return 'youtube_video';
if (YouTube.validate(query, "PLAYLIST")) return "youtube_playlist"; if (YouTube.validate(query, 'PLAYLIST')) return 'youtube_playlist';
if (vimeoRegex.test(query)) return "vimeo"; if (vimeoRegex.test(query)) return 'vimeo';
if (facebookRegex.test(query)) return "facebook"; if (facebookRegex.test(query)) return 'facebook';
if (reverbnationRegex.test(query)) return "reverbnation"; if (reverbnationRegex.test(query)) return 'reverbnation';
if (Util.isURL(query)) return "attachment"; if (Util.isURL(query)) return 'attachment';
return "youtube_search"; return 'youtube_search';
} }
static isURL(str: string) { 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'); const url = new RegExp(urlRegex, 'i');
return str.length < 2083 && url.test(str); return str.length < 2083 && url.test(str);
} }
static getVimeoID(query: string) { 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) { static parseMS(milliseconds: number) {
@ -87,28 +93,35 @@ export default class Util {
} }
static durationString(durObj: object) { 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<Track[]> { static ytSearch(query: string, options?: any): Promise<Track[]> {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
await YouTube.search(query, { await YouTube.search(query, {
type: "video", type: 'video',
safeSearch: Boolean(options?.player.options.useSafeSearch) safeSearch: Boolean(options?.player.options.useSafeSearch)
}) })
.then(results => { .then((results) => {
resolve(results.map((r) => new Track(options?.player, { resolve(
title: r.title, results.map(
description: r.description, (r) =>
author: r.channel.name, new Track(options?.player, {
url: r.url, title: r.title,
thumbnail: r.thumbnail.displayThumbnailURL(), description: r.description,
duration: r.durationFormatted, author: r.channel.name,
views: r.views, url: r.url,
requestedBy: options?.user, thumbnail: r.thumbnail.displayThumbnailURL(),
fromPlaylist: false, duration: r.durationFormatted,
source: "youtube" views: r.views,
}))); requestedBy: options?.user,
fromPlaylist: false,
source: 'youtube'
})
)
);
}) })
.catch(() => resolve([])); .catch(() => resolve([]));
}); });

View file

@ -8,7 +8,8 @@
"object-literal-sort-keys": false, "object-literal-sort-keys": false,
"interface-name": false, "interface-name": false,
"no-empty": false, "no-empty": false,
"no-console": false "no-console": false,
"radix": false
}, },
"rulesDirectory": [] "rulesDirectory": []
} }