diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..f675f34 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,7 @@ +example/ +node_modules/ +lib/ +.github/ +docs/ + +*.d.ts \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..13921e1 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,20 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "env": { + "node": true + }, + "plugins": [ + "@typescript-eslint" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/ban-ts-comment": "error" + } +} \ No newline at end of file diff --git a/example/music-bot/index.js b/example/music-bot/index.js index f46a2f2..fcd9f57 100644 --- a/example/music-bot/index.js +++ b/example/music-bot/index.js @@ -172,7 +172,7 @@ client.on("interaction", async (interaction) => { requestedBy: interaction.user, searchEngine: interaction.commandName === "soundcloud" ? QueryType.SOUNDCLOUD_SEARCH : QueryType.AUTO }) - .catch(() => {}); + .catch(Util.noop); if (!searchResult || !searchResult.tracks.length) return void interaction.followUp({ content: "No results were found!" }); const queue = await player.createQueue(interaction.guild, { diff --git a/package.json b/package.json index b3cebe7..e16bc81 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,12 @@ "scripts": { "dev": "cd example/test && ts-node index.ts", "build": "rimraf lib && tsc", + "build:check": "tsc --noEmit --incremental false", "format": "prettier --write \"src/**/*.ts\" \"example/**/*.ts\"", - "lint": "tslint -p tsconfig.json", "docs": "docgen --jsdoc jsdoc.json --source src/*.ts src/**/*.ts --custom docs/index.yml --output docs/docs.json", - "docs:test": "docgen --jsdoc jsdoc.json --source src/*.ts src/**/*.ts --custom docs/index.yml" + "docs:test": "docgen --jsdoc jsdoc.json --source src/*.ts src/**/*.ts --custom docs/index.yml", + "lint": "eslint src --ext .ts", + "lint:fix": "eslint src --ext .ts --fix" }, "funding": "https://github.com/Androz2091/discord-player?sponsor=1", "contributors": [ @@ -68,15 +70,16 @@ "@discordjs/opus": "^0.5.3", "@types/node": "^15.12.2", "@types/ws": "^7.4.4", + "@typescript-eslint/eslint-plugin": "^4.28.0", + "@typescript-eslint/parser": "^4.28.0", "discord-api-types": "^0.18.1", "discord.js": "^13.0.0-dev.c850ae10270076c4b2e10b130dd8f88eed4ed201", "discord.js-docgen": "discordjs/docgen#ts-patch", + "eslint": "^7.29.0", "jsdoc-babel": "^0.5.0", "prettier": "^2.3.1", "rimraf": "^3.0.2", "ts-node": "^10.0.0", - "tslint": "^6.1.3", - "tslint-config-prettier": "^1.18.0", - "typescript": "^4.3.2" + "typescript": "^4.3.4" } } diff --git a/src/Player.ts b/src/Player.ts index 509c421..b78a590 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -8,6 +8,7 @@ import { QueryResolver } from "./utils/QueryResolver"; import YouTube from "youtube-sr"; import { Util } from "./utils/Util"; import Spotify from "spotify-url-info"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import { Client as SoundCloud } from "soundcloud-scraper"; import { Playlist } from "./Structures/Playlist"; @@ -49,7 +50,7 @@ class Player extends EventEmitter { this.client.on("voiceStateUpdate", this._handleVoiceState.bind(this)); if (this.options?.autoRegisterExtractor) { - let nv: any; + let nv: any; // eslint-disable-line @typescript-eslint/no-explicit-any if ((nv = Util.require("@discord-player/extractor"))) { ["Attachment", "Facebook", "Reverbnation", "Vimeo"].forEach((ext) => void this.use(ext, nv[ext])); @@ -139,7 +140,7 @@ class Player extends EventEmitter { try { prev.destroy(); - } catch {} + } catch {} // eslint-disable-line no-empty this.queues.delete(guild.id); return prev; @@ -162,6 +163,7 @@ class Player extends EventEmitter { options.requestedBy = this.client.users.resolve(options.requestedBy); if (!("searchEngine" in options)) options.searchEngine = QueryType.AUTO; + // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const [_, extractor] of this.extractors) { if (!extractor.validate(query)) continue; const data = await extractor.handle(query); @@ -194,11 +196,11 @@ class Player extends EventEmitter { case QueryType.YOUTUBE_SEARCH: { const videos = await YouTube.search(query, { type: "video" - }).catch(() => {}); + }).catch(Util.noop); // eslint-disable-line @typescript-eslint/no-empty-function if (!videos) return { playlist: null, tracks: [] }; const tracks = videos.map((m) => { - (m as any).source = "youtube"; + (m as any).source = "youtube"; // eslint-disable-line @typescript-eslint/no-explicit-any return new Track(this, { title: m.title, description: m.description, @@ -216,12 +218,13 @@ class Player extends EventEmitter { } case QueryType.SOUNDCLOUD_TRACK: case QueryType.SOUNDCLOUD_SEARCH: { - const result: any[] = QueryResolver.resolve(query) === QueryType.SOUNDCLOUD_TRACK ? [{ url: query }] : await soundcloud.search(query, "track").catch(() => {}); + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-function + const result: any[] = QueryResolver.resolve(query) === QueryType.SOUNDCLOUD_TRACK ? [{ url: query }] : await soundcloud.search(query, "track").catch(Util.noop); if (!result || !result.length) return { playlist: null, tracks: [] }; const res: Track[] = []; for (const r of result) { - const trackInfo = await soundcloud.getSongInfo(r.url).catch(() => {}); + const trackInfo = await soundcloud.getSongInfo(r.url).catch(Util.noop); // eslint-disable-line @typescript-eslint/no-empty-function if (!trackInfo) continue; const track = new Track(this, { @@ -243,7 +246,7 @@ class Player extends EventEmitter { return { playlist: null, tracks: res }; } case QueryType.SPOTIFY_SONG: { - const spotifyData = await Spotify.getData(query).catch(() => {}); + const spotifyData = await Spotify.getData(query).catch(Util.noop); // eslint-disable-line @typescript-eslint/no-empty-function if (!spotifyData) return { playlist: null, tracks: [] }; const spotifyTrack = new Track(this, { title: spotifyData.name, @@ -264,7 +267,7 @@ class Player extends EventEmitter { } case QueryType.SPOTIFY_PLAYLIST: case QueryType.SPOTIFY_ALBUM: { - const spotifyPlaylist = await Spotify.getData(query).catch(() => {}); + const spotifyPlaylist = await Spotify.getData(query).catch(Util.noop); // eslint-disable-line @typescript-eslint/no-empty-function if (!spotifyPlaylist) return { playlist: null, tracks: [] }; const playlist = new Playlist(this, { @@ -290,6 +293,7 @@ class Player extends EventEmitter { }); if (spotifyPlaylist.type !== "playlist") { + // eslint-disable-next-line @typescript-eslint/no-explicit-any playlist.tracks = spotifyPlaylist.tracks.items.map((m: any) => { const data = new Track(this, { title: m.name ?? "", @@ -307,6 +311,7 @@ class Player extends EventEmitter { return data; }) as Track[]; } else { + // eslint-disable-next-line @typescript-eslint/no-explicit-any playlist.tracks = spotifyPlaylist.tracks.items.map((m: any) => { const data = new Track(this, { title: m.track.name ?? "", @@ -328,7 +333,7 @@ class Player extends EventEmitter { return { playlist: playlist, tracks: playlist.tracks }; } case QueryType.SOUNDCLOUD_PLAYLIST: { - const data = await SoundCloud.getPlaylist(query).catch(() => {}); + const data = await SoundCloud.getPlaylist(query).catch(Util.noop); // eslint-disable-line @typescript-eslint/no-empty-function if (!data) return { playlist: null, tracks: [] }; const res = new Playlist(this, { @@ -367,11 +372,11 @@ class Player extends EventEmitter { return { playlist: res, tracks: res.tracks }; } case QueryType.YOUTUBE_PLAYLIST: { - const ytpl = await YouTube.getPlaylist(query).catch(() => {}); + const ytpl = await YouTube.getPlaylist(query).catch(Util.noop); // eslint-disable-line @typescript-eslint/no-empty-function if (!ytpl) return { playlist: null, tracks: [] }; // @todo: better way of handling large playlists - await ytpl.fetch().catch(() => {}); + await ytpl.fetch().catch(Util.noop); // eslint-disable-line @typescript-eslint/no-empty-function const playlist: Playlist = new Playlist(this, { title: ytpl.title, @@ -420,6 +425,7 @@ class Player extends EventEmitter { * @param {boolean} [force=false] Overwrite existing extractor with this name (if available) * @returns {ExtractorModel} */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any use(extractorName: string, extractor: ExtractorModel | any, force = false): ExtractorModel { if (!extractorName) throw new Error("Cannot use unknown extractor!"); if (this.extractors.has(extractorName) && !force) return this.extractors.get(extractorName); diff --git a/src/Structures/ExtractorModel.ts b/src/Structures/ExtractorModel.ts index 7fec971..f85d438 100644 --- a/src/Structures/ExtractorModel.ts +++ b/src/Structures/ExtractorModel.ts @@ -2,13 +2,14 @@ import { ExtractorModelData } from "../types/types"; class ExtractorModel { name: string; - private _raw: any; + private _raw: any; // eslint-disable-line @typescript-eslint/no-explicit-any /** * Model for raw Discord Player extractors * @param {string} extractorName Name of the extractor * @param {object} data Extractor object */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any constructor(extractorName: string, data: any) { /** * The extractor name @@ -37,6 +38,7 @@ class ExtractorModel { return { playlist: data.playlist ?? null, data: + // eslint-disable-next-line @typescript-eslint/no-explicit-any data.info?.map((m: any) => ({ title: m.title, duration: m.duration, diff --git a/src/Structures/Playlist.ts b/src/Structures/Playlist.ts index 5cbfa98..d76dd66 100644 --- a/src/Structures/Playlist.ts +++ b/src/Structures/Playlist.ts @@ -16,7 +16,7 @@ class Playlist { }; public id: string; public url: string; - public readonly rawPlaylist?: any; + public readonly rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any /** * Playlist constructor diff --git a/src/Structures/Queue.ts b/src/Structures/Queue.ts index e9a8d84..f2c5d67 100644 --- a/src/Structures/Queue.ts +++ b/src/Structures/Queue.ts @@ -2,7 +2,7 @@ import { Collection, Guild, StageChannel, VoiceChannel } from "discord.js"; import { Player } from "../Player"; import { StreamDispatcher } from "../VoiceInterface/BasicStreamDispatcher"; import Track from "./Track"; -import { FiltersName, PlayerOptions, PlayOptions, QueueFilters, QueueRepeatMode } from "../types/types"; +import { PlayerOptions, PlayOptions, QueueFilters, QueueRepeatMode } from "../types/types"; import ytdl from "discord-ytdl-core"; import { AudioResource, StreamType } from "@discordjs/voice"; import { Util } from "../utils/Util"; @@ -19,9 +19,9 @@ class Queue { public playing = false; public metadata?: T = null; public repeatMode: QueueRepeatMode = 0; - private _streamTime: number = 0; + private _streamTime = 0; public _cooldownsTimeout = new Collection(); - private _activeFilters: any[] = []; + private _activeFilters: any[] = []; // eslint-disable-line @typescript-eslint/no-explicit-any private _filtersUpdate = false; public destroyed = false; @@ -134,7 +134,7 @@ class Queue { }); this.connection = connection; - if (channel.type === "stage") await channel.guild.me.voice.setRequestToSpeak(true).catch(() => {}); + if (channel.type === "stage") await channel.guild.me.voice.setRequestToSpeak(true).catch(Util.noop); // eslint-disable-line @typescript-eslint/no-empty-function this.connection.on("error", (err) => this.player.emit("connectionError", this, err)); this.connection.on("debug", (msg) => this.player.emit("debug", this, msg)); @@ -332,7 +332,7 @@ class Queue { }); } - const _filters: any[] = []; + const _filters: any[] = []; // eslint-disable-line @typescript-eslint/no-explicit-any for (const filter in filters) { if (filters[filter as keyof QueueFilters] === true) _filters.push(filter); @@ -466,7 +466,7 @@ class Queue { const info = await ytdl .getInfo(track.url) .then((x) => x.related_videos[0]) - .catch(() => {}); + .catch(Util.noop); // eslint-disable-line @typescript-eslint/no-empty-function if (!info) { if (this.options.leaveOnEnd) this.destroy(); return void this.player.emit("queueEnd", this); diff --git a/src/VoiceInterface/BasicStreamDispatcher.ts b/src/VoiceInterface/BasicStreamDispatcher.ts index f7a0294..d1a27a9 100644 --- a/src/VoiceInterface/BasicStreamDispatcher.ts +++ b/src/VoiceInterface/BasicStreamDispatcher.ts @@ -18,10 +18,10 @@ import Track from "../Structures/Track"; import { Util } from "../utils/Util"; export interface VoiceEvents { - error: (error: AudioPlayerError) => any; - debug: (message: string) => any; - start: () => any; - finish: () => any; + error: (error: AudioPlayerError) => any; // eslint-disable-line @typescript-eslint/no-explicit-any + debug: (message: string) => any; // eslint-disable-line @typescript-eslint/no-explicit-any + start: () => any; // eslint-disable-line @typescript-eslint/no-explicit-any + finish: () => any; // eslint-disable-line @typescript-eslint/no-explicit-any } class StreamDispatcher extends EventEmitter { @@ -29,7 +29,7 @@ class StreamDispatcher extends EventEmitter { public readonly audioPlayer: AudioPlayer; public readonly channel: VoiceChannel | StageChannel; public audioResource?: AudioResource; - private readyLock: boolean = false; + private readyLock = false; /** * Creates new connection object @@ -108,6 +108,7 @@ class StreamDispatcher extends EventEmitter { * @param {object} [ops={}] Options * @returns {AudioResource} */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any createStream(src: Readable | Duplex | string, ops?: { type?: StreamType; data?: any }) { this.audioResource = createAudioResource(src, { inputType: ops?.type ?? StreamType.Arbitrary, @@ -134,7 +135,7 @@ class StreamDispatcher extends EventEmitter { try { this.audioPlayer.stop(true); this.voiceConnection.destroy(); - } catch {} + } catch {} // eslint-disable-line no-empty } /** diff --git a/src/VoiceInterface/VoiceUtils.ts b/src/VoiceInterface/VoiceUtils.ts index f8eb601..085e0ad 100644 --- a/src/VoiceInterface/VoiceUtils.ts +++ b/src/VoiceInterface/VoiceUtils.ts @@ -52,7 +52,7 @@ class VoiceUtils { let conn = joinVoiceChannel({ guildId: channel.guild.id, channelId: channel.id, - adapterCreator: (channel.guild as any).voiceAdapterCreator, + adapterCreator: (channel.guild as any).voiceAdapterCreator, // eslint-disable-line @typescript-eslint/no-explicit-any selfDeaf: Boolean(options.deaf) }); diff --git a/src/types/types.ts b/src/types/types.ts index 96f7ad2..2bd2dd7 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -84,9 +84,9 @@ export interface RawTrackData { requestedBy: User; playlist?: Playlist; source?: TrackSource; - engine?: any; + engine?: any; // eslint-disable-line @typescript-eslint/no-explicit-any live?: boolean; - raw?: any; + raw?: any; // eslint-disable-line @typescript-eslint/no-explicit-any } /** @@ -182,7 +182,7 @@ export interface ExtractorModelData { }; id: string; url: string; - rawPlaylist?: any; + rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any }; data: { title: string; @@ -304,6 +304,7 @@ export enum QueryType { * @param {Track} track The track */ +/* eslint-disable @typescript-eslint/no-explicit-any */ export interface PlayerEvents { botDisconnect: (queue: Queue) => any; channelEmpty: (queue: Queue) => any; @@ -317,6 +318,8 @@ export interface PlayerEvents { trackStart: (queue: Queue, track: Track) => any; } +/* eslint-enable @typescript-eslint/no-explicit-any */ + /** * @typedef {object} PlayOptions * @property {boolean} [filtersUpdate=false] If this play was triggered for filters update @@ -384,7 +387,7 @@ export interface PlaylistInitData { }; id: string; url: string; - rawPlaylist?: any; + rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any } /** diff --git a/src/utils/AudioFilters.ts b/src/utils/AudioFilters.ts index cc9ac90..8afefbf 100644 --- a/src/utils/AudioFilters.ts +++ b/src/utils/AudioFilters.ts @@ -90,7 +90,7 @@ const FilterList = { }, toString() { - return this.names.map((m) => (this as any)[m]).join(","); + return this.names.map((m) => (this as any)[m]).join(","); // eslint-disable-line @typescript-eslint/no-explicit-any }, create(filter?: FiltersName[]): string { diff --git a/src/utils/QueryResolver.ts b/src/utils/QueryResolver.ts index 19554a8..2193e08 100644 --- a/src/utils/QueryResolver.ts +++ b/src/utils/QueryResolver.ts @@ -1,6 +1,7 @@ import { validateID, validateURL } from "ytdl-core"; import { YouTube } from "youtube-sr"; import { QueryType } from "../types/types"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import { validateURL as SoundcloudValidateURL } from "soundcloud-scraper"; diff --git a/src/utils/Util.ts b/src/utils/Util.ts index 187813b..27d07b7 100644 --- a/src/utils/Util.ts +++ b/src/utils/Util.ts @@ -14,7 +14,7 @@ class Util { * @param {object} durObj The duration object * @returns {string} */ - static durationString(durObj: object) { + static durationString(durObj: Record) { return Object.values(durObj) .map((m) => (isNaN(m) ? 0 : m)) .join(":"); @@ -58,6 +58,7 @@ class Util { * @param {any[]} arr The array * @returns {any} */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any static last(arr: T[]): T { if (!Array.isArray(arr)) return; return arr[arr.length - 1]; @@ -93,6 +94,10 @@ class Util { static wait(time: number) { return new Promise((r) => setTimeout(r, time).unref()); } + + static get noop() { + return () => {}; // eslint-disable-line @typescript-eslint/no-empty-function + } } export { Util }; diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 0000000..6a357fa --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "**/*.ts", + "**/*.js" + ], + "exclude": [] +} \ No newline at end of file diff --git a/tslint.json b/tslint.json deleted file mode 100644 index 4e5571c..0000000 --- a/tslint.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "defaultSeverity": "error", - "extends": ["tslint:recommended", "tslint-config-prettier"], - "jsRules": { - "no-unused-expression": true - }, - "rules": { - "object-literal-sort-keys": false, - "interface-name": false, - "no-empty": false, - "no-console": false, - "radix": false - }, - "rulesDirectory": [] -} \ No newline at end of file