From 590f29fef1e746286df77350b7cfdb5d33f28ef0 Mon Sep 17 00:00:00 2001 From: skdhg <46562212+skdhg@users.noreply.github.com> Date: Fri, 13 Jan 2023 15:15:09 +0545 Subject: [PATCH 1/4] chore: monorepo setup --- .eslintrc.json | 3 +- .github/workflows/docs-deploy.yml | 2 +- .github/workflows/eslint.yml | 60 +- .github/workflows/npm-publish-test.yml | 21 + .github/workflows/npm-publish.yml | 32 +- .github/workflows/publish-dev.yml | 31 - .gitignore | 4 +- .husky/pre-commit | 2 +- .npmignore | 6 - .prettierrc | 2 +- LICENSE | 2 +- package.json | 106 +-- packages/adapter-local/LICENSE | 21 + packages/adapter-local/README.md | 9 + packages/adapter-local/package.json | 31 + packages/adapter-local/src/index.ts | 1 + packages/adapter-local/tsconfig.json | 5 + packages/adapter-local/tsup.config.ts | 15 + packages/adapter-remote/LICENSE | 21 + packages/adapter-remote/README.md | 9 + packages/adapter-remote/package.json | 34 + packages/adapter-remote/src/index.ts | 1 + packages/adapter-remote/tsconfig.json | 5 + packages/adapter-remote/tsup.config.ts | 15 + packages/core/LICENSE | 21 + packages/core/README.md | 11 + packages/core/package.json | 41 ++ .../core/src/classes/PlayerNodeManager.ts | 141 ++++ packages/core/src/index.ts | 2 + packages/core/src/utils/clients.ts | 4 + packages/core/src/utils/enums.ts | 19 + packages/core/src/worker/AudioNode.ts | 40 + .../core/src/worker/SubscriptionClient.ts | 66 ++ .../src/worker/actions/CREATE_SUBSCRIPTION.ts | 23 + .../src/worker/actions/DELETE_SUBSCRIPTION.ts | 24 + .../src/worker/actions/GATEWAY_PAYLOAD.ts | 23 + .../src/worker/actions/JOIN_VOICE_CHANNEL.ts | 24 + packages/core/src/worker/actions/PLAY.ts | 25 + .../src/worker/actions/base/BaseAction.ts | 32 + packages/core/src/worker/notifier.ts | 6 + packages/core/src/worker/worker.ts | 30 + packages/core/tsconfig.json | 7 + packages/core/tsup.config.ts | 15 + packages/discord-player/LICENSE | 21 + packages/discord-player/README.md | 246 +++++++ packages/discord-player/package.json | 93 +++ .../discord-player/src}/Player.ts | 223 +++--- .../src}/Structures/ExtractorModel.ts | 12 +- .../src}/Structures/PlayerError.ts | 28 +- .../src}/Structures/Playlist.ts | 8 +- .../discord-player/src}/Structures/Queue.ts | 209 +++--- .../discord-player/src}/Structures/Track.ts | 30 +- .../src}/VoiceInterface/StreamDispatcher.ts | 52 +- .../src}/VoiceInterface/VoiceUtils.ts | 10 +- .../src}/VoiceInterface/VolumeTransformer.ts | 26 +- packages/discord-player/src/index.ts | 25 + packages/discord-player/src/smoothVolume.ts | 14 + .../discord-player/src}/types/types.ts | 26 +- .../discord-player/src}/utils/AudioFilters.ts | 66 +- .../discord-player/src}/utils/FFmpegStream.ts | 16 +- .../src}/utils/QueryResolver.ts | 16 +- .../discord-player/src}/utils/Util.ts | 22 +- packages/discord-player/tsconfig.json | 4 + packages/discord-player/tsup.config.ts | 15 + packages/equalizer/LICENSE | 21 + packages/equalizer/README.md | 52 ++ packages/equalizer/package.json | 45 ++ packages/equalizer/src/biquad/Biquad.ts | 66 ++ packages/equalizer/src/biquad/Coefficients.ts | 254 +++++++ packages/equalizer/src/biquad/index.ts | 2 + .../src/equalizer/ChannelProcessor.ts | 61 ++ .../equalizer/src/equalizer/Coefficients.ts | 21 + packages/equalizer/src/equalizer/Equalizer.ts | 46 ++ .../src/equalizer/EqualizerConfiguration.ts | 21 + .../src/equalizer/EqualizerStream.ts | 92 +++ packages/equalizer/src/equalizer/index.ts | 5 + packages/equalizer/src/index.ts | 3 + packages/equalizer/src/utils/Frequency.ts | 34 + packages/equalizer/src/utils/index.ts | 1 + packages/equalizer/tsconfig.json | 4 + packages/equalizer/tsup.config.ts | 15 + packages/tsconfig/LICENSE | 21 + packages/tsconfig/README.md | 3 + packages/tsconfig/base.json | 21 + packages/tsconfig/index.js | 0 packages/tsconfig/package.json | 27 + packages/utils/LICENSE | 21 + packages/utils/README.md | 74 ++ packages/utils/package.json | 36 + packages/utils/src/Collection.ts | 25 + packages/utils/src/Errors.ts | 7 + packages/utils/src/EventEmitter.d.ts | 27 + packages/utils/src/EventEmitter.ts | 1 + packages/utils/src/Queue.ts | 117 +++ packages/utils/src/index.ts | 5 + packages/utils/src/utils.ts | 9 + packages/utils/tsconfig.json | 5 + packages/utils/tsup.config.ts | 15 + scripts/docgen.js | 10 +- src/index.ts | 25 - src/smoothVolume.ts | 14 - tsconfig.json | 18 - turbo.json | 16 + yarn.lock | 693 +++++++++++++----- 104 files changed, 3313 insertions(+), 774 deletions(-) create mode 100644 .github/workflows/npm-publish-test.yml delete mode 100644 .github/workflows/publish-dev.yml delete mode 100644 .npmignore create mode 100644 packages/adapter-local/LICENSE create mode 100644 packages/adapter-local/README.md create mode 100644 packages/adapter-local/package.json create mode 100644 packages/adapter-local/src/index.ts create mode 100644 packages/adapter-local/tsconfig.json create mode 100644 packages/adapter-local/tsup.config.ts create mode 100644 packages/adapter-remote/LICENSE create mode 100644 packages/adapter-remote/README.md create mode 100644 packages/adapter-remote/package.json create mode 100644 packages/adapter-remote/src/index.ts create mode 100644 packages/adapter-remote/tsconfig.json create mode 100644 packages/adapter-remote/tsup.config.ts create mode 100644 packages/core/LICENSE create mode 100644 packages/core/README.md create mode 100644 packages/core/package.json create mode 100644 packages/core/src/classes/PlayerNodeManager.ts create mode 100644 packages/core/src/index.ts create mode 100644 packages/core/src/utils/clients.ts create mode 100644 packages/core/src/utils/enums.ts create mode 100644 packages/core/src/worker/AudioNode.ts create mode 100644 packages/core/src/worker/SubscriptionClient.ts create mode 100644 packages/core/src/worker/actions/CREATE_SUBSCRIPTION.ts create mode 100644 packages/core/src/worker/actions/DELETE_SUBSCRIPTION.ts create mode 100644 packages/core/src/worker/actions/GATEWAY_PAYLOAD.ts create mode 100644 packages/core/src/worker/actions/JOIN_VOICE_CHANNEL.ts create mode 100644 packages/core/src/worker/actions/PLAY.ts create mode 100644 packages/core/src/worker/actions/base/BaseAction.ts create mode 100644 packages/core/src/worker/notifier.ts create mode 100644 packages/core/src/worker/worker.ts create mode 100644 packages/core/tsconfig.json create mode 100644 packages/core/tsup.config.ts create mode 100644 packages/discord-player/LICENSE create mode 100644 packages/discord-player/README.md create mode 100644 packages/discord-player/package.json rename {src => packages/discord-player/src}/Player.ts (77%) rename {src => packages/discord-player/src}/Structures/ExtractorModel.ts (83%) rename {src => packages/discord-player/src}/Structures/PlayerError.ts (58%) rename {src => packages/discord-player/src}/Structures/Playlist.ts (95%) rename {src => packages/discord-player/src}/Structures/Queue.ts (80%) rename {src => packages/discord-player/src}/Structures/Track.ts (85%) rename {src => packages/discord-player/src}/VoiceInterface/StreamDispatcher.ts (83%) rename {src => packages/discord-player/src}/VoiceInterface/VoiceUtils.ts (91%) rename {src => packages/discord-player/src}/VoiceInterface/VolumeTransformer.ts (86%) create mode 100644 packages/discord-player/src/index.ts create mode 100644 packages/discord-player/src/smoothVolume.ts rename {src => packages/discord-player/src}/types/types.ts (95%) rename {src => packages/discord-player/src}/utils/AudioFilters.ts (57%) rename {src => packages/discord-player/src}/utils/FFmpegStream.ts (74%) rename {src => packages/discord-player/src}/utils/QueryResolver.ts (84%) rename {src => packages/discord-player/src}/utils/Util.ts (84%) create mode 100644 packages/discord-player/tsconfig.json create mode 100644 packages/discord-player/tsup.config.ts create mode 100644 packages/equalizer/LICENSE create mode 100644 packages/equalizer/README.md create mode 100644 packages/equalizer/package.json create mode 100644 packages/equalizer/src/biquad/Biquad.ts create mode 100644 packages/equalizer/src/biquad/Coefficients.ts create mode 100644 packages/equalizer/src/biquad/index.ts create mode 100644 packages/equalizer/src/equalizer/ChannelProcessor.ts create mode 100644 packages/equalizer/src/equalizer/Coefficients.ts create mode 100644 packages/equalizer/src/equalizer/Equalizer.ts create mode 100644 packages/equalizer/src/equalizer/EqualizerConfiguration.ts create mode 100644 packages/equalizer/src/equalizer/EqualizerStream.ts create mode 100644 packages/equalizer/src/equalizer/index.ts create mode 100644 packages/equalizer/src/index.ts create mode 100644 packages/equalizer/src/utils/Frequency.ts create mode 100644 packages/equalizer/src/utils/index.ts create mode 100644 packages/equalizer/tsconfig.json create mode 100644 packages/equalizer/tsup.config.ts create mode 100644 packages/tsconfig/LICENSE create mode 100644 packages/tsconfig/README.md create mode 100644 packages/tsconfig/base.json create mode 100644 packages/tsconfig/index.js create mode 100644 packages/tsconfig/package.json create mode 100644 packages/utils/LICENSE create mode 100644 packages/utils/README.md create mode 100644 packages/utils/package.json create mode 100644 packages/utils/src/Collection.ts create mode 100644 packages/utils/src/Errors.ts create mode 100644 packages/utils/src/EventEmitter.d.ts create mode 100644 packages/utils/src/EventEmitter.ts create mode 100644 packages/utils/src/Queue.ts create mode 100644 packages/utils/src/index.ts create mode 100644 packages/utils/src/utils.ts create mode 100644 packages/utils/tsconfig.json create mode 100644 packages/utils/tsup.config.ts delete mode 100644 src/index.ts delete mode 100644 src/smoothVolume.ts delete mode 100644 tsconfig.json create mode 100644 turbo.json diff --git a/.eslintrc.json b/.eslintrc.json index d7521756f..2d6c06bd2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -13,8 +13,9 @@ ], "rules": { "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/no-unused-vars": "error", - "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/ban-ts-comment": "error", "semi": "error", "no-console": "error" diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml index 27a8e7c82..b77a0226c 100644 --- a/.github/workflows/docs-deploy.yml +++ b/.github/workflows/docs-deploy.yml @@ -21,7 +21,7 @@ jobs: node-version: 18 - name: Install dependencies - run: npm install + run: yarn install --immutable - name: Build and deploy documentation uses: discordjs/action-docs@v1 diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index bd277e675..643291678 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -1,37 +1,37 @@ name: ESLint on: - push: - branches: - - '*' - - '!docs' - - '!develop' - - '!master' - - '!gh-pages' - pull_request: - branches: - - '*' - - '!docs' - - '!develop' - - '!master' - - '!gh-pages' + push: + branches: + - '*' + - '!docs' + - '!develop' + - '!master' + - '!gh-pages' + pull_request: + branches: + - '*' + - '!docs' + - '!develop' + - '!master' + - '!gh-pages' jobs: - test: - name: ESLint (Node v16) - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v2 + test: + name: ESLint (Node v16) + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 - - name: Install Node v16 - uses: actions/setup-node@v1 - with: - node-version: 16 + - name: Install Node v16 + uses: actions/setup-node@v1 + with: + node-version: 18 - - name: Install dependencies - run: npm install + - name: Install dependencies + run: yarn install --immutable - - name: Run ESLint - run: npm run lint + - name: Run ESLint + run: yarn lint - - name: Run TSC - run: npm run build:check + - name: Run TSC + run: yarn build:check diff --git a/.github/workflows/npm-publish-test.yml b/.github/workflows/npm-publish-test.yml new file mode 100644 index 000000000..f33719348 --- /dev/null +++ b/.github/workflows/npm-publish-test.yml @@ -0,0 +1,21 @@ +name: Node.js Package + +on: + release: + types: [created] + +jobs: + publish-npm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 18 + registry-url: https://registry.npmjs.org/ + - run: yarn install --immutable + - run: yarn build + - run: cd packages/discord-player + - run: npm publish --dry-run + env: + NODE_AUTH_TOKEN: ${{secrets.npm_token}} diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index fa3c514de..5134e9d57 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -1,21 +1,21 @@ name: Node.js Package on: - release: - types: [created] + release: + types: [created] jobs: - - publish-npm: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: 16 - registry-url: https://registry.npmjs.org/ - - run: npm install - - run: npm run build - - run: npm publish --access public - env: - NODE_AUTH_TOKEN: ${{secrets.npm_token}} + publish-npm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 18 + registry-url: https://registry.npmjs.org/ + - run: yarn install --immutable + - run: yarn build + - run: cd packages/discord-player + - run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{secrets.npm_token}} diff --git a/.github/workflows/publish-dev.yml b/.github/workflows/publish-dev.yml deleted file mode 100644 index 4e8e3573f..000000000 --- a/.github/workflows/publish-dev.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Publish dev -on: - workflow_dispatch: - schedule: - - cron: '0 */12 * * *' -jobs: - npm: - name: npm - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v2 - with: - ref: develop - - - name: Install Node v16 - uses: actions/setup-node@v2 - with: - node-version: 16 - registry-url: https://registry.npmjs.org/ - - - name: Install dependencies - run: npm install - - - name: Publish - run: | - npx tsc --skipLibCheck - npm version --git-tag-version=false $(jq --raw-output '.version' package.json)-dev.$(date +%s).$(git rev-parse --short HEAD) - npm publish --tag dev || true - env: - NODE_AUTH_TOKEN: ${{ secrets.npm_token }} diff --git a/.gitignore b/.gitignore index e76eb96ba..e25584fb6 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,6 @@ dist yarn*.log dev/ -packages/ -.DS_Store \ No newline at end of file +.DS_Store +.turbo/ \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit index 517826d8b..c75f841aa 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -npm run format && npm run lint:fix \ No newline at end of file +yarn format && yarn lint && git add -u \ No newline at end of file diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 412f4d3fd..000000000 --- a/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -src/ -tslint.json -tsconfig.json -.prettierrc -test/ -demo/ \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index 46a28f74a..2764033eb 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,7 +1,7 @@ { "printWidth": 200, "trailingComma": "none", - "singleQuote": false, + "singleQuote": true, "tabWidth": 4, "semi": true } \ No newline at end of file diff --git a/LICENSE b/LICENSE index e1e6f2ca2..fe07fc736 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-present Androz +Copyright (c) 2020 Androz2091 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/package.json b/package.json index ee3791d35..5966da065 100644 --- a/package.json +++ b/package.json @@ -1,103 +1,41 @@ { "name": "discord-player", - "version": "5.4.0", - "description": "Complete framework to facilitate music commands using discord.js", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "files": [ - "dist/" + "version": "0.0.0", + "description": "A complete framework to facilitate music commands using discord.js", + "main": "index.js", + "private": true, + "workspaces": [ + "packages/*" ], - "module": "dist/index.mjs", - "exports": { - ".": { - "require": "./dist/index.js", - "import": "./dist/index.mjs" - }, - "./smoothVolume": "./dist/smoothVolume.js", - "./src/*": "./dist/*", - "./dist/*": "./dist/*" + "dependencies": { + "ansi-colors": "^4.1.3", + "common-tags": "^1.8.2", + "fs-extra": "^11.1.0", + "prettier": "^2.8.2", + "turbo": "^1.6.3" }, "scripts": { - "dev": "cd examples/test && ts-node index.ts", - "build": "rimraf dist && tsc && npm run build:esm", - "build:check": "tsc --noEmit --incremental false", - "prepublishOnly": "rollup-type-bundler -e stream", - "build:esm": "gen-esm-wrapper ./dist/index.js ./dist/index.mjs", - "format": "prettier --write \"src/**/*.ts\"", - "docs": "typedoc --json docs/typedoc.json src/index.ts", - "postdocs": "node scripts/docgen.js", - "lint": "eslint src --ext .ts", - "prepare": "husky install", - "lint:fix": "eslint src --ext .ts --fix" + "build": "turbo run build", + "docs": "turbo run docs", + "build:check": "turbo run build:check", + "lint": "turbo run lint", + "format": "prettier --write \"{packages,scripts}/**/*.{js,ts,mjs,mts}\"" }, - "funding": "https://github.com/Androz2091/discord-player?sponsor=1", - "contributors": [ - "DevAndromeda " - ], "repository": { "type": "git", "url": "git+https://github.com/Androz2091/discord-player.git" }, "keywords": [ - "music", - "player", - "bot", - "framework", + "discord-player", + "voip", "discord", - "volume", - "queue", - "youtube", - "discord.js", - "musicbot", - "discord-music-player", - "discord-music", - "music-player", - "youtube-dl", - "ytdl-core", - "ytdl", - "lavalink", - "api" + "voice", + "opus" ], "author": "Androz2091", "license": "MIT", "bugs": { "url": "https://github.com/Androz2091/discord-player/issues" }, - "homepage": "https://discord-player.js.org", - "dependencies": { - "@discord-player/equalizer": "^0.1.2", - "@discordjs/voice": "^0.11.0", - "libsodium-wrappers": "^0.7.10", - "tiny-typed-emitter": "^2.1.0", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "soundcloud-scraper": "5.x", - "spotify-url-info": "3.x", - "youtube-sr": "4.x", - "ytdl-core": "4.x" - }, - "devDependencies": { - "@discordjs/ts-docgen": "^0.4.1", - "@favware/rollup-type-bundler": "^1.0.10", - "@types/node": "^18.6.3", - "@types/ws": "^8.5.3", - "@typescript-eslint/eslint-plugin": "^5.32.0", - "@typescript-eslint/parser": "^5.32.0", - "discord-api-types": "^0.37.0", - "discord.js": "^14.1.2", - "eslint": "^8.21.0", - "gen-esm-wrapper": "^1.1.3", - "husky": "^8.0.1", - "opusscript": "^0.0.8", - "prettier": "^2.7.1", - "rimraf": "^3.0.2", - "soundcloud-scraper": "^5.0.3", - "spotify-url-info": "^3.1.7", - "ts-node": "^10.9.1", - "typedoc": "^0.23.10", - "typescript": "^4.7.4", - "youtube-sr": "^4.3.4", - "ytdl-core": "^4.11.2" - } + "homepage": "https://github.com/Androz2091/discord-player#readme" } diff --git a/packages/adapter-local/LICENSE b/packages/adapter-local/LICENSE new file mode 100644 index 000000000..fe07fc736 --- /dev/null +++ b/packages/adapter-local/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Androz2091 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/adapter-local/README.md b/packages/adapter-local/README.md new file mode 100644 index 000000000..dd371ec6f --- /dev/null +++ b/packages/adapter-local/README.md @@ -0,0 +1,9 @@ +# `@discord-player/adapter-local` + +Local PlayerNode adapter that provides interface to Discord Player + +## Installation + +```sh +$ npm install @discord-player/adapter-local +``` \ No newline at end of file diff --git a/packages/adapter-local/package.json b/packages/adapter-local/package.json new file mode 100644 index 000000000..1cc78fd11 --- /dev/null +++ b/packages/adapter-local/package.json @@ -0,0 +1,31 @@ +{ + "name": "@discord-player/adapter-local", + "version": "0.1.0", + "description": "Discord Player local adapter", + "keywords": [ + "discord-player" + ], + "author": "skdhg", + "homepage": "https://discord-player.js.org", + "license": "MIT", + "main": "dist/index.js", + "files": [ + "dist" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/Androz2091/discord-player.git" + }, + "scripts": { + "build:check": "tsc --noEmit", + "build": "tsup" + }, + "bugs": { + "url": "https://github.com/Androz2091/discord-player/issues" + }, + "dependencies": {}, + "devDependencies": { + "@discord-player/tsconfig": "*", + "tsup": "^6.2.2" + } +} \ No newline at end of file diff --git a/packages/adapter-local/src/index.ts b/packages/adapter-local/src/index.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/packages/adapter-local/src/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/adapter-local/tsconfig.json b/packages/adapter-local/tsconfig.json new file mode 100644 index 000000000..08981710e --- /dev/null +++ b/packages/adapter-local/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "@discord-player/tsconfig/base.json", + "include": ["src/**/*"], + "exclude": ["node_modules"] +} \ No newline at end of file diff --git a/packages/adapter-local/tsup.config.ts b/packages/adapter-local/tsup.config.ts new file mode 100644 index 000000000..e8bba970a --- /dev/null +++ b/packages/adapter-local/tsup.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + clean: true, + bundle: true, + dts: true, + format: ['cjs', 'esm'], + keepNames: true, + minify: false, + entry: ['./src/index.ts'], + skipNodeModulesBundle: true, + sourcemap: true, + target: 'ES2020', + silent: true +}); diff --git a/packages/adapter-remote/LICENSE b/packages/adapter-remote/LICENSE new file mode 100644 index 000000000..fe07fc736 --- /dev/null +++ b/packages/adapter-remote/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Androz2091 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/adapter-remote/README.md b/packages/adapter-remote/README.md new file mode 100644 index 000000000..d06c7e04d --- /dev/null +++ b/packages/adapter-remote/README.md @@ -0,0 +1,9 @@ +# `@discord-player/adapter-remote` + +Remote PlayerNode adapter that provides interface to Discord Player + +## Installation + +```sh +$ npm install @discord-player/adapter-remote +``` \ No newline at end of file diff --git a/packages/adapter-remote/package.json b/packages/adapter-remote/package.json new file mode 100644 index 000000000..21a994573 --- /dev/null +++ b/packages/adapter-remote/package.json @@ -0,0 +1,34 @@ +{ + "name": "@discord-player/adapter-remote", + "version": "0.1.0", + "description": "Discord Player remote adapter", + "keywords": [ + "discord-player" + ], + "author": "skdhg", + "homepage": "https://discord-player.js.org", + "license": "MIT", + "main": "dist/index.js", + "directories": { + "src": "src" + }, + "files": [ + "dist" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/Androz2091/discord-player.git" + }, + "scripts": { + "build:check": "tsc --noEmit", + "build": "tsup" + }, + "bugs": { + "url": "https://github.com/Androz2091/discord-player/issues" + }, + "dependencies": {}, + "devDependencies": { + "@discord-player/tsconfig": "*", + "tsup": "^6.2.2" + } +} \ No newline at end of file diff --git a/packages/adapter-remote/src/index.ts b/packages/adapter-remote/src/index.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/packages/adapter-remote/src/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/adapter-remote/tsconfig.json b/packages/adapter-remote/tsconfig.json new file mode 100644 index 000000000..08981710e --- /dev/null +++ b/packages/adapter-remote/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "@discord-player/tsconfig/base.json", + "include": ["src/**/*"], + "exclude": ["node_modules"] +} \ No newline at end of file diff --git a/packages/adapter-remote/tsup.config.ts b/packages/adapter-remote/tsup.config.ts new file mode 100644 index 000000000..e8bba970a --- /dev/null +++ b/packages/adapter-remote/tsup.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + clean: true, + bundle: true, + dts: true, + format: ['cjs', 'esm'], + keepNames: true, + minify: false, + entry: ['./src/index.ts'], + skipNodeModulesBundle: true, + sourcemap: true, + target: 'ES2020', + silent: true +}); diff --git a/packages/core/LICENSE b/packages/core/LICENSE new file mode 100644 index 000000000..fe07fc736 --- /dev/null +++ b/packages/core/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Androz2091 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 000000000..7803e9e16 --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,11 @@ +# `@discord-player/core` + +Discord Player core components + +## Installation + +```sh +$ npm install @discord-player/core +``` + +This library is internally used by `discord-player`. This library handles all the work related to voice and provides a way to communicate with nodes. \ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 000000000..5773b73c6 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,41 @@ +{ + "name": "@discord-player/core", + "version": "0.1.0", + "description": "Discord Player core components", + "keywords": [ + "discord-player" + ], + "author": "Androz2091 ", + "homepage": "https://discord-player.js.org", + "license": "MIT", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "directories": { + "dist": "dist", + "src": "src" + }, + "files": [ + "dist" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/Androz2091/discord-player.git" + }, + "scripts": { + "build": "tsup", + "build:check": "tsc --noEmit" + }, + "bugs": { + "url": "https://github.com/Androz2091/discord-player/issues" + }, + "dependencies": { + "@discord-player/utils": "*", + "@discordjs/voice": "^0.11.0", + "discord-api-types": "^0.37.2" + }, + "devDependencies": { + "@discord-player/tsconfig": "*", + "tsup": "^6.2.2" + } +} \ No newline at end of file diff --git a/packages/core/src/classes/PlayerNodeManager.ts b/packages/core/src/classes/PlayerNodeManager.ts new file mode 100644 index 000000000..b65361da1 --- /dev/null +++ b/packages/core/src/classes/PlayerNodeManager.ts @@ -0,0 +1,141 @@ +import { cpus } from 'node:os'; +import { Worker } from 'node:worker_threads'; +import { join } from 'node:path'; +import { Collection, EventEmitter } from '@discord-player/utils'; +import { WorkerEvents, WorkerOp } from '../utils/enums'; + +interface PlayerNodeConfig { + max?: number | 'auto'; + respawn?: boolean; +} + +interface BasicSubscription { + guild_id: string; + client_id: string; +} + +type WorkerResolvable = number | Worker; + +export interface PlayerNodeEvents { + error: (worker: Worker, error: Error) => Awaited; + message: (worker: Worker, message: unknown) => Awaited; + spawn: (worker: Worker) => Awaited; + debug: (message: string) => Awaited; + voiceStateUpdate: (worker: Worker, payload: any) => Awaited; + subscriptionCreate: (worker: Worker, payload: BasicSubscription) => Awaited; + subscriptionDelete: (worker: Worker, payload: BasicSubscription) => Awaited; +} + +export interface ServicePayload { + op: keyof typeof WorkerOp; + d: { + guild_id: string; + client_id: string; + } & T; +} + +export interface WorkerPayload { + t: keyof typeof WorkerEvents; + d: T; +} + +export class PlayerNodeManager extends EventEmitter { + public workers = new Collection(); + public constructor(public config: PlayerNodeConfig) { + super(); + } + + #debug(message: string) { + this.emit('debug', `[${this.constructor.name} | ${new Date().toLocaleString()}] ${message}`); + } + + public get maxThreads() { + const conf = this.config.max; + if (conf === 'auto') return cpus().length; + if (typeof conf !== 'number' || Number.isNaN(conf) || conf < 1 || !Number.isFinite(conf)) return 1; + return conf; + } + + public get spawnable() { + return this.workers.size < this.maxThreads; + } + + // TODO + public getLeastBusy() {} + + public send(workerRes: WorkerResolvable, data: ServicePayload) { + const worker = this.resolveWorker(workerRes); + if (!worker) throw new Error('Worker does not exist'); + this.#debug(`Sending ${JSON.stringify(data)} to thread ${worker.threadId}`); + worker.postMessage(data); + } + + public spawn() { + return new Promise((resolve) => { + if (!this.spawnable) return resolve(this.workers.random()!); + + const worker = new Worker(join(__dirname, '..', 'worker', 'worker.js')); + this.#debug(`Spawned worker at thread ${worker.threadId}`); + + worker.on('online', () => { + this.#debug(`worker ${worker.threadId} is online`); + this.workers.set(worker.threadId, worker); + this.emit('spawn', worker); + return resolve(worker); + }); + + worker.on('message', (message: WorkerPayload) => { + this.#debug(`Incoming message from worker ${worker.threadId}\n\n${JSON.stringify(message)}`); + switch (message.t) { + case WorkerEvents.VOICE_STATE_UPDATE: { + return this.emit('voiceStateUpdate', worker, message.d); + } + case WorkerEvents.ERROR: { + return this.emit('error', worker, new Error((message.d as any).message)); + } + case WorkerEvents.SUBSCRIPTION_CREATE: { + return this.emit('subscriptionCreate', worker, message.d as BasicSubscription); + } + case WorkerEvents.SUBSCRIPTION_DELETE: { + return this.emit('subscriptionDelete', worker, message.d as BasicSubscription); + } + default: { + return this.emit('message', worker, message); + } + } + }); + + worker.on('exit', () => { + this.#debug(`Worker terminated at thread ${worker.threadId}`); + this.workers.delete(worker.threadId); + }); + + worker.on('error', (error) => { + this.#debug(`Incoming error message from worker ${worker.threadId}\n\n${JSON.stringify(error)}`); + this.emit('error', worker, error); + }); + }); + } + + public resolveWorker(worker: WorkerResolvable) { + if (typeof worker === 'number') return this.workers.get(worker); + return this.workers.find((res) => res.threadId === worker.threadId); + } + + public async terminate(worker?: WorkerResolvable) { + if (worker) { + const internalWorker = this.resolveWorker(worker); + if (internalWorker) { + this.#debug(`Terminating worker ${internalWorker.threadId}...`); + await internalWorker.terminate(); + this.workers.delete(internalWorker.threadId); + } + } else { + for (const [id, thread] of this.workers) { + this.#debug(`Terminating worker ${thread.threadId}...`); + await thread.terminate(); + this.workers.delete(id); + } + } + } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 000000000..ddc701fde --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,2 @@ +export * from './utils/enums'; +export * from './classes/PlayerNodeManager'; diff --git a/packages/core/src/utils/clients.ts b/packages/core/src/utils/clients.ts new file mode 100644 index 000000000..6f28f3eeb --- /dev/null +++ b/packages/core/src/utils/clients.ts @@ -0,0 +1,4 @@ +import { Collection } from '@discord-player/utils'; +import type { SubscriptionClient } from '../worker/SubscriptionClient'; + +export const clients = new Collection(); diff --git a/packages/core/src/utils/enums.ts b/packages/core/src/utils/enums.ts new file mode 100644 index 000000000..6b54380f8 --- /dev/null +++ b/packages/core/src/utils/enums.ts @@ -0,0 +1,19 @@ +import { keyMirror } from '@discord-player/utils'; + +// prettier-ignore +export const WorkerOp = keyMirror([ + "JOIN_VOICE_CHANNEL", + "CREATE_SUBSCRIPTION", + "DELETE_SUBSCRIPTION", + "GATEWAY_PAYLOAD", + "PLAY" +]); + +// prettier-ignore +export const WorkerEvents = keyMirror([ + "SUBSCRIPTION_CREATE", + "SUBSCRIPTION_DELETE", + "VOICE_STATE_UPDATE", + "ERROR", + "CONNECTION_DESTROY" +]); diff --git a/packages/core/src/worker/AudioNode.ts b/packages/core/src/worker/AudioNode.ts new file mode 100644 index 000000000..d4d9c549c --- /dev/null +++ b/packages/core/src/worker/AudioNode.ts @@ -0,0 +1,40 @@ +import { createAudioPlayer, createAudioResource, StreamType, VoiceConnection } from '@discordjs/voice'; + +export interface NodePlayerOptions { + query: string; + metadata: unknown; + initialVolume?: number; +} + +export class AudioNode { + public audioPlayer = createAudioPlayer(); + public constructor(public connection: VoiceConnection, public client: string) { + connection.subscribe(this.audioPlayer); + } + + public get guild() { + return this.connection.joinConfig.guildId; + } + + public get channel() { + return this.connection.joinConfig.channelId; + } + + public play(options: NodePlayerOptions) { + const resource = createAudioResource(options.query, { + inputType: StreamType.Arbitrary, + inlineVolume: typeof options.initialVolume === 'number', + metadata: options.metadata + }); + + if ('initialVolume' in options && resource.volume) { + resource.volume.setVolumeLogarithmic(options.initialVolume!); + } + + this.audioPlayer.play(resource); + } + + public destroy() { + this.connection.destroy(); + } +} diff --git a/packages/core/src/worker/SubscriptionClient.ts b/packages/core/src/worker/SubscriptionClient.ts new file mode 100644 index 000000000..fa558d85a --- /dev/null +++ b/packages/core/src/worker/SubscriptionClient.ts @@ -0,0 +1,66 @@ +import { Collection } from '@discord-player/utils'; +import { DiscordGatewayAdapterLibraryMethods, joinVoiceChannel, VoiceConnection } from '@discordjs/voice'; +import { WorkerEvents } from '../utils/enums'; +import { AudioNode } from './AudioNode'; +import { notify } from './notifier'; + +export interface SubscriptionPayload { + channelId: string; + guildId: string; + deafen?: boolean; +} + +export class SubscriptionClient { + public subscriptions = new Collection(); + public adapters = new Collection(); + public constructor(public clientId: string) {} + + public connect(config: SubscriptionPayload) { + const voiceConnection = joinVoiceChannel({ + channelId: config.channelId, + guildId: config.guildId, + selfDeaf: Boolean(config.deafen), + adapterCreator: (adapter) => { + this.adapters.set(config.guildId, adapter); + return { + sendPayload: (payload) => { + notify({ + t: WorkerEvents.VOICE_STATE_UPDATE, + d: payload + }); + return true; + }, + destroy: () => { + this.adapters.delete(config.guildId); + this.subscriptions.delete(config.guildId); + notify({ + t: WorkerEvents.CONNECTION_DESTROY, + d: { + client_id: this.clientId, + guild_id: config.guildId, + channel_id: config.channelId + } + }); + } + }; + } + }); + + this.subscriptions.set(voiceConnection.joinConfig.guildId, new AudioNode(voiceConnection, this.clientId)); + } + + public disconnect(config: Pick) { + const node = this.subscriptions.get(config.guildId); + if (node) { + node.connection.destroy(); + this.subscriptions.delete(config.guildId); + } + } + + public disconnectAll() { + for (const [id, node] of this.subscriptions) { + node.connection.destroy(); + this.subscriptions.delete(id); + } + } +} diff --git a/packages/core/src/worker/actions/CREATE_SUBSCRIPTION.ts b/packages/core/src/worker/actions/CREATE_SUBSCRIPTION.ts new file mode 100644 index 000000000..30d2c75d6 --- /dev/null +++ b/packages/core/src/worker/actions/CREATE_SUBSCRIPTION.ts @@ -0,0 +1,23 @@ +import { ServicePayload } from '../../classes/PlayerNodeManager'; +import { WorkerEvents, WorkerOp } from '../../utils/enums'; +import { SubscriptionClient } from '../SubscriptionClient'; +import { BaseAction } from './base/BaseAction'; + +class CreateSubscription extends BaseAction { + public actionName = WorkerOp.CREATE_SUBSCRIPTION; + + public handle(data: ServicePayload) { + if (this.isSubscribed(data)) return; + const client = new SubscriptionClient(data.d.client_id); + this.setClient(data, client); + this.notify({ + t: WorkerEvents.SUBSCRIPTION_CREATE, + d: { + client_id: data.d.client_id, + guild_id: data.d.guild_id + } + }); + } +} + +export default new CreateSubscription(); diff --git a/packages/core/src/worker/actions/DELETE_SUBSCRIPTION.ts b/packages/core/src/worker/actions/DELETE_SUBSCRIPTION.ts new file mode 100644 index 000000000..28aa1fcfa --- /dev/null +++ b/packages/core/src/worker/actions/DELETE_SUBSCRIPTION.ts @@ -0,0 +1,24 @@ +import { ServicePayload } from '../../classes/PlayerNodeManager'; +import { WorkerEvents, WorkerOp } from '../../utils/enums'; +import { BaseAction } from './base/BaseAction'; + +class DeleteSubscription extends BaseAction { + public actionName = WorkerOp.DELETE_SUBSCRIPTION; + + public handle(data: ServicePayload) { + const client = this.getClient(data); + if (client) { + client.disconnectAll(); + this.deleteClient(data); + this.notify({ + t: WorkerEvents.SUBSCRIPTION_DELETE, + d: { + client_id: client.clientId, + guild_id: data.d.guild_id + } + }); + } + } +} + +export default new DeleteSubscription(); diff --git a/packages/core/src/worker/actions/GATEWAY_PAYLOAD.ts b/packages/core/src/worker/actions/GATEWAY_PAYLOAD.ts new file mode 100644 index 000000000..3b75699b1 --- /dev/null +++ b/packages/core/src/worker/actions/GATEWAY_PAYLOAD.ts @@ -0,0 +1,23 @@ +import { ServicePayload } from '../../classes/PlayerNodeManager'; +import { WorkerOp } from '../../utils/enums'; +import { BaseAction } from './base/BaseAction'; +import { GatewayDispatchEvents } from 'discord-api-types/v10'; + +class JoinVoiceChannel extends BaseAction { + public actionName = WorkerOp.GATEWAY_PAYLOAD; + + public async handle(data: ServicePayload) { + const client = this.getClient(data); + if (!client) return; + const adapter = client.adapters.get(data.d.payload.d.guild_id); + if (!adapter) return; + const message = data.d.payload; + if (message.t === GatewayDispatchEvents.VoiceServerUpdate) { + adapter.onVoiceServerUpdate(message.d); + } else if (message.t === GatewayDispatchEvents.VoiceStateUpdate && message.d.session_id && message.d.user_id === client.clientId) { + adapter.onVoiceStateUpdate(message.d); + } + } +} + +export default new JoinVoiceChannel(); diff --git a/packages/core/src/worker/actions/JOIN_VOICE_CHANNEL.ts b/packages/core/src/worker/actions/JOIN_VOICE_CHANNEL.ts new file mode 100644 index 000000000..0c12a2ba6 --- /dev/null +++ b/packages/core/src/worker/actions/JOIN_VOICE_CHANNEL.ts @@ -0,0 +1,24 @@ +import { ServicePayload } from '../../classes/PlayerNodeManager'; +import { WorkerOp } from '../../utils/enums'; +import { BaseAction } from './base/BaseAction'; + +export interface JoinPayload { + channel_id: string; + self_deaf?: boolean; +} + +class JoinVoiceChannel extends BaseAction { + public actionName = WorkerOp.JOIN_VOICE_CHANNEL; + + public async handle(data: ServicePayload) { + const client = this.getClient(data); + if (client) + await client.connect({ + channelId: data.d.channel_id, + guildId: data.d.guild_id, + deafen: data.d.self_deaf + }); + } +} + +export default new JoinVoiceChannel(); diff --git a/packages/core/src/worker/actions/PLAY.ts b/packages/core/src/worker/actions/PLAY.ts new file mode 100644 index 000000000..7760d1483 --- /dev/null +++ b/packages/core/src/worker/actions/PLAY.ts @@ -0,0 +1,25 @@ +import { ServicePayload } from '../../classes/PlayerNodeManager'; +import { WorkerOp } from '../../utils/enums'; +import { BaseAction } from './base/BaseAction'; + +export interface PlayPayload { + query: string; + metadata: unknown; + initial_volume?: number; +} + +class Play extends BaseAction { + public actionName = WorkerOp.PLAY; + + public async handle(data: ServicePayload) { + const client = this.getClient(data); + if (!client) return; + const node = client.subscriptions.get(data.d.guild_id); + if (node) { + const { query, metadata, initial_volume } = data.d; + node.play({ query, metadata, initialVolume: initial_volume }); + } + } +} + +export default new Play(); diff --git a/packages/core/src/worker/actions/base/BaseAction.ts b/packages/core/src/worker/actions/base/BaseAction.ts new file mode 100644 index 000000000..447df3a2a --- /dev/null +++ b/packages/core/src/worker/actions/base/BaseAction.ts @@ -0,0 +1,32 @@ +import { ServicePayload, WorkerPayload } from '../../../classes/PlayerNodeManager'; +import { clients } from '../../../utils/clients'; +import { WorkerOp } from '../../../utils/enums'; +import { notify } from '../../notifier'; +import { SubscriptionClient } from '../../SubscriptionClient'; + +export class BaseAction { + public clients = clients; + public actionName!: keyof typeof WorkerOp; + + public getClient(data: ServicePayload) { + return this.clients.get(data.d.client_id); + } + + public setClient(data: ServicePayload, client: SubscriptionClient) { + return this.clients.set(data.d.client_id, client); + } + + public deleteClient(data: ServicePayload) { + return this.clients.delete(data.d.client_id); + } + + public isSubscribed(data: ServicePayload) { + return this.clients.has(data.d.client_id); + } + + public handle(data: ServicePayload) {} + + public notify(data: WorkerPayload) { + notify(data); + } +} diff --git a/packages/core/src/worker/notifier.ts b/packages/core/src/worker/notifier.ts new file mode 100644 index 000000000..cfdde3ee3 --- /dev/null +++ b/packages/core/src/worker/notifier.ts @@ -0,0 +1,6 @@ +import { parentPort } from 'node:worker_threads'; +import { WorkerPayload } from '../classes/PlayerNodeManager'; + +export function notify(data: WorkerPayload) { + parentPort?.postMessage(data); +} diff --git a/packages/core/src/worker/worker.ts b/packages/core/src/worker/worker.ts new file mode 100644 index 000000000..45d18c8b2 --- /dev/null +++ b/packages/core/src/worker/worker.ts @@ -0,0 +1,30 @@ +import type { ServicePayload } from '../classes/PlayerNodeManager'; +import { WorkerEvents } from '../utils/enums'; +import type { BaseAction } from './actions/base/BaseAction'; +import { notify } from './notifier'; +import { parentPort } from 'node:worker_threads'; + +parentPort?.on('message', async (message: ServicePayload) => { + const action = getAction(message.op); + if (action) { + try { + return void (await action.handle(message)); + } catch (e) { + return notify({ + t: WorkerEvents.ERROR, + d: { + message: `${(e as any).stack || e}` + } + }); + } + } +}); + +function getAction(op: string) { + try { + const action = require(`${__dirname}/actions/${op}`); + return (action.default || action) as BaseAction; + } catch { + return null; + } +} diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 000000000..f19c9a585 --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@discord-player/tsconfig/base.json", + "include": ["src/**/*"], + "compilerOptions": { + "esModuleInterop": true + } +} \ No newline at end of file diff --git a/packages/core/tsup.config.ts b/packages/core/tsup.config.ts new file mode 100644 index 000000000..e8bba970a --- /dev/null +++ b/packages/core/tsup.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + clean: true, + bundle: true, + dts: true, + format: ['cjs', 'esm'], + keepNames: true, + minify: false, + entry: ['./src/index.ts'], + skipNodeModulesBundle: true, + sourcemap: true, + target: 'ES2020', + silent: true +}); diff --git a/packages/discord-player/LICENSE b/packages/discord-player/LICENSE new file mode 100644 index 000000000..fe07fc736 --- /dev/null +++ b/packages/discord-player/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Androz2091 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/discord-player/README.md b/packages/discord-player/README.md new file mode 100644 index 000000000..9825999cd --- /dev/null +++ b/packages/discord-player/README.md @@ -0,0 +1,246 @@ +# Discord Player +Complete framework to facilitate music commands using **[discord.js](https://discord.js.org)**. + +[![downloadsBadge](https://img.shields.io/npm/dt/discord-player?style=for-the-badge)](https://npmjs.com/discord-player) +[![versionBadge](https://img.shields.io/npm/v/discord-player?style=for-the-badge)](https://npmjs.com/discord-player) +[![discordBadge](https://img.shields.io/discord/558328638911545423?style=for-the-badge&color=7289da)](https://androz2091.fr/discord) +[![wakatime](https://wakatime.com/badge/github/Androz2091/discord-player.svg)](https://wakatime.com/badge/github/Androz2091/discord-player) +[![CodeFactor](https://www.codefactor.io/repository/github/androz2091/discord-player/badge/v5)](https://www.codefactor.io/repository/github/androz2091/discord-player/overview/v5) + +## Installation + +### Install **[discord-player](https://npmjs.com/package/discord-player)** + +```sh +$ npm install --save discord-player +``` + +### Install **[@discordjs/opus](https://npmjs.com/package/@discordjs/opus)** + +```sh +$ npm install --save @discordjs/opus +``` + +### Install FFmpeg or Avconv +- Official FFMPEG Website: **[https://www.ffmpeg.org/download.html](https://www.ffmpeg.org/download.html)** + +- Node Module (FFMPEG): **[https://npmjs.com/package/ffmpeg-static](https://npmjs.com/package/ffmpeg-static)** + +- Avconv: **[https://libav.org/download](https://libav.org/download)** + +# Features +- Simple & easy to use 🤘 +- Beginner friendly 😱 +- Audio filters 🎸 +- Lavalink compatible 15 band equalizer 🎚️ +- Lightweight ☁️ +- Custom extractors support 🌌 +- Multiple sources support ✌ +- Play in multiple servers at the same time 🚗 +- Does not inject anything to discord.js or your discord.js client 💉 +- Allows you to have full control over what is going to be streamed 👑 + +## [Documentation](https://discord-player.js.org) + +## Getting Started + +First of all, you will need to register slash commands: + +```js +const { REST } = require("@discordjs/rest"); +const { Routes, ApplicationCommandOptionType } = require("discord.js"); + +const commands = [ + { + name: "play", + description: "Plays a song!", + options: [ + { + name: "query", + type: ApplicationCommandOptionType.String, + description: "The song you want to play", + required: true + } + ] + } +]; + +const rest = new REST({ version: "10" }).setToken("BOT_TOKEN"); + +(async () => { + try { + console.log("Started refreshing application [/] commands."); + + await rest.put(Routes.applicationGuildCommands(CLIENT_ID, GUILD_ID), { body: commands }); + + console.log("Successfully reloaded application [/] commands."); + } catch(error) { + console.error(error); + } +})(); +``` + +Now you can implement your bot's logic: + +```js +const { Client } = require("discord.js"); +const client = new Discord.Client({ + intents: [ + "Guilds", + "GuildVoiceStates" + ] +}); +const { Player } = require("discord-player"); + +// Create a new Player (you don't need any API Key) +const player = new Player(client); + +// add the trackStart event so when a song will be played this message will be sent +player.on("trackStart", (queue, track) => queue.metadata.channel.send(`🎶 | Now playing **${track.title}**!`)) + +client.once("ready", () => { + console.log("I'm ready !"); +}); + +client.on("interactionCreate", async (interaction) => { + if (!interaction.isChatInputCommand()) return; + + // /play track:Despacito + // will play "Despacito" in the voice channel + if (interaction.commandName === "play") { + if (!interaction.member.voice.channelId) return await interaction.reply({ content: "You are not in a voice channel!", ephemeral: true }); + if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) return await interaction.reply({ content: "You are not in my voice channel!", ephemeral: true }); + const query = interaction.options.getString("query"); + const queue = player.createQueue(interaction.guild, { + metadata: { + channel: interaction.channel + } + }); + + // verify vc connection + try { + if (!queue.connection) await queue.connect(interaction.member.voice.channel); + } catch { + queue.destroy(); + return await interaction.reply({ content: "Could not join your voice channel!", ephemeral: true }); + } + + await interaction.deferReply(); + const track = await player.search(query, { + requestedBy: interaction.user + }).then(x => x.tracks[0]); + if (!track) return await interaction.followUp({ content: `❌ | Track **${query}** not found!` }); + + queue.play(track); + + return await interaction.followUp({ content: `⏱️ | Loading track **${track.title}**!` }); + } +}); + +client.login("BOT_TOKEN"); +``` + +## Supported websites + +By default, discord-player supports **YouTube**, **Spotify** and **SoundCloud** streams only. + +### Optional dependencies + +Discord Player provides an **Extractor API** that enables you to use your custom stream extractor with it. Some packages have been made by the community to add new features using this API. + +#### [@discord-player/extractor](https://github.com/DevAndromeda/discord-player-extractors) (optional) + +Optional package that adds support for `vimeo`, `reverbnation`, `facebook`, `attachment links` and `lyrics`. +You just need to install it using `npm i --save @discord-player/extractor` (discord-player will automatically detect and use it). + +#### [@discord-player/downloader](https://github.com/DevAndromeda/discord-player-downloader) (optional) + +`@discord-player/downloader` is an optional package that brings support for +700 websites. The documentation is available [here](https://github.com/DevAndromeda/discord-player-downloader). + +## Examples of bots made with Discord Player + +These bots are made by the community, they can help you build your own! + +* **[Discord Music Bot](https://github.com/Androz2091/discord-music-bot)** by [Androz2091](https://github.com/Androz2091) +* [Dodong](https://github.com/nizeic/Dodong) by [nizeic](https://github.com/nizeic) +* [Musico](https://github.com/Whirl21/Musico) by [Whirl21](https://github.com/Whirl21) +* [Melody](https://github.com/NerdyTechy/Melody) by [NerdyTechy](https://github.com/NerdyTechy) +* [Eyesense-Music-Bot](https://github.com/naseif/Eyesense-Music-Bot) by [naseif](https://github.com/naseif) +* [Music-bot](https://github.com/ZerioDev/Music-bot) by [ZerioDev](https://github.com/ZerioDev) +* [AtlantaBot](https://github.com/Androz2091/AtlantaBot) by [Androz2091](https://github.com/Androz2091) (**outdated**) +* [Discord-Music](https://github.com/inhydrox/discord-music) by [inhydrox](https://github.com/inhydrox) (**outdated**) + +## Advanced + +### Smooth Volume + +Discord Player will by default try to implement this. If smooth volume does not work, you need to add this line at the top of your main file: + +```js +// CJS +require("discord-player/smoothVolume"); + +// ESM +import "discord-player/smoothVolume" +``` + +> ⚠️ Make sure that line is situated at the **TOP** of your **main** file. + +### Use cookies + +```js +const player = new Player(client, { + ytdlOptions: { + requestOptions: { + headers: { + cookie: "YOUR_YOUTUBE_COOKIE" + } + } + } +}); +``` + +### Use custom proxies + +```js +const HttpsProxyAgent = require("https-proxy-agent"); + +// Remove "user:pass@" if you don't need to authenticate to your proxy. +const proxy = "http://user:pass@111.111.111.111:8080"; +const agent = HttpsProxyAgent(proxy); + +const player = new Player(client, { + ytdlOptions: { + requestOptions: { agent } + } +}); +``` + +> You may also create a simple proxy server and forward requests through it. +> See **[https://github.com/http-party/node-http-proxy](https://github.com/http-party/node-http-proxy)** for more info. + +### Custom stream Engine + +Discord Player by default uses **[node-ytdl-core](https://github.com/fent/node-ytdl-core)** for youtube and some other extractors for other sources. +If you need to modify this behavior without touching extractors, you need to use `createStream` functionality of discord player. +Here's an example on how you can use **[play-dl](https://npmjs.com/package/play-dl)** to download youtube streams instead of using ytdl-core. + +```js +const playdl = require("play-dl"); + +// other code +const queue = player.createQueue(..., { + ..., + async onBeforeCreateStream(track, source, _queue) { + // only trap youtube source + if (source === "youtube") { + // track here would be youtube track + return (await playdl.stream(track.url, { discordPlayerCompatibility : true })).stream; + // we must return readable stream or void (returning void means telling discord-player to look for default extractor) + } + } +}); +``` + +`.onBeforeCreateStream` is called before actually downloading the stream. It is a different concept from extractors, where you are **just** downloading +streams. `source` here will be a video source. Streams from `onBeforeCreateStream` are then piped to `FFmpeg` and finally sent to Discord voice servers. diff --git a/packages/discord-player/package.json b/packages/discord-player/package.json new file mode 100644 index 000000000..51c8892ba --- /dev/null +++ b/packages/discord-player/package.json @@ -0,0 +1,93 @@ +{ + "name": "discord-player", + "version": "5.4.0", + "description": "Complete framework to facilitate music commands using discord.js", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist/" + ], + "module": "dist/index.mjs", + "exports": { + ".": { + "require": "./dist/index.js", + "import": "./dist/index.mjs" + }, + "./smoothVolume": "./dist/smoothVolume.js", + "./src/*": "./dist/*", + "./dist/*": "./dist/*" + }, + "scripts": { + "dev": "cd examples/test && ts-node index.ts", + "build": "tsup", + "build:check": "tsc --noEmit --incremental false", + "docs": "typedoc --json ../../docs/typedoc.json ./src/index.ts", + "postdocs": "node ../../scripts/docgen.js", + "lint": "eslint src --ext .ts --fix" + }, + "funding": "https://github.com/Androz2091/discord-player?sponsor=1", + "contributors": [ + "skdhg" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/Androz2091/discord-player.git" + }, + "keywords": [ + "music", + "player", + "bot", + "framework", + "discord", + "volume", + "queue", + "youtube", + "discord.js", + "musicbot", + "discord-music-player", + "discord-music", + "music-player", + "youtube-dl", + "ytdl-core", + "ytdl", + "lavalink", + "api" + ], + "author": "Androz2091", + "license": "MIT", + "bugs": { + "url": "https://github.com/Androz2091/discord-player/issues" + }, + "homepage": "https://discord-player.js.org", + "dependencies": { + "@discord-player/equalizer": "*", + "@discordjs/voice": "^0.11.0", + "libsodium-wrappers": "^0.7.10", + "tiny-typed-emitter": "^2.1.0" + }, + "peerDependencies": { + "soundcloud-scraper": "5.x", + "spotify-url-info": "3.x", + "youtube-sr": "4.x", + "ytdl-core": "4.x" + }, + "devDependencies": { + "@discord-player/tsconfig": "*", + "@discordjs/ts-docgen": "^0.4.1", + "@types/node": "^18.6.3", + "@types/ws": "^8.5.3", + "@typescript-eslint/eslint-plugin": "^5.32.0", + "@typescript-eslint/parser": "^5.32.0", + "discord-api-types": "^0.37.0", + "discord.js": "^14.1.2", + "eslint": "^8.21.0", + "opusscript": "^0.0.8", + "soundcloud-scraper": "^5.0.3", + "spotify-url-info": "^3.1.7", + "ts-node": "^10.9.1", + "typedoc": "^0.23.10", + "typescript": "^4.7.4", + "youtube-sr": "^4.3.4", + "ytdl-core": "^4.11.2" + } +} diff --git a/src/Player.ts b/packages/discord-player/src/Player.ts similarity index 77% rename from src/Player.ts rename to packages/discord-player/src/Player.ts index 82f0c1aa1..f0352480b 100644 --- a/src/Player.ts +++ b/packages/discord-player/src/Player.ts @@ -1,19 +1,19 @@ -import { Client, Collection, GuildResolvable, Snowflake, User, VoiceState, IntentsBitField } from "discord.js"; -import { TypedEmitter as EventEmitter } from "tiny-typed-emitter"; -import { Queue } from "./Structures/Queue"; -import { VoiceUtils } from "./VoiceInterface/VoiceUtils"; -import { PlayerEvents, PlayerOptions, QueryType, SearchOptions, PlayerInitOptions, PlayerSearchResult, PlaylistInitData } from "./types/types"; -import Track from "./Structures/Track"; -import { QueryResolver } from "./utils/QueryResolver"; -import YouTube from "youtube-sr"; -import { Util } from "./utils/Util"; -import Spotify from "spotify-url-info"; -import { PlayerError, ErrorStatusCode } from "./Structures/PlayerError"; -import { getInfo as ytdlGetInfo } from "ytdl-core"; -import { Client as SoundCloud, SearchResult as SoundCloudSearchResult } from "soundcloud-scraper"; -import { Playlist } from "./Structures/Playlist"; -import { ExtractorModel } from "./Structures/ExtractorModel"; -import { generateDependencyReport } from "@discordjs/voice"; +import { Client, Collection, GuildResolvable, Snowflake, User, VoiceState, IntentsBitField } from 'discord.js'; +import { TypedEmitter as EventEmitter } from 'tiny-typed-emitter'; +import { Queue } from './Structures/Queue'; +import { VoiceUtils } from './VoiceInterface/VoiceUtils'; +import { PlayerEvents, PlayerOptions, QueryType, SearchOptions, PlayerInitOptions, PlayerSearchResult, PlaylistInitData } from './types/types'; +import Track from './Structures/Track'; +import { QueryResolver } from './utils/QueryResolver'; +import YouTube from 'youtube-sr'; +import { Util } from './utils/Util'; +import Spotify from 'spotify-url-info'; +import { PlayerError, ErrorStatusCode } from './Structures/PlayerError'; +import { getInfo as ytdlGetInfo } from 'ytdl-core'; +import { SearchResult, Client as SoundCloud, SearchResult as SoundCloudSearchResult } from 'soundcloud-scraper'; +import { Playlist } from './Structures/Playlist'; +import { ExtractorModel } from './Structures/ExtractorModel'; +import { generateDependencyReport } from '@discordjs/voice'; const soundcloud = new SoundCloud(); @@ -31,7 +31,7 @@ class Player extends EventEmitter { public readonly queues = new Collection(); public readonly voiceUtils = new VoiceUtils(); public readonly extractors = new Collection(); - public requiredEvents = ["error", "connectionError"] as string[]; + public requiredEvents = ['error', 'connectionError'] as string[]; #lastLatency = -1; /** @@ -58,17 +58,17 @@ class Player extends EventEmitter { */ this.options = Object.assign(this.options, options); - this.client.on("voiceStateUpdate", this._handleVoiceState.bind(this)); + this.client.on('voiceStateUpdate', this._handleVoiceState.bind(this)); if (this.options?.autoRegisterExtractor) { 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])); + if ((nv = Util.require('@discord-player/extractor'))) { + ['Attachment', 'Facebook', 'Reverbnation', 'Vimeo'].forEach((ext) => void this.use(ext, nv[ext])); } } - if (typeof this.options.lagMonitor === "number" && this.options.lagMonitor > 0) { + if (typeof this.options.lagMonitor === 'number' && this.options.lagMonitor > 0) { setInterval(() => { const start = performance.now(); setTimeout(() => { @@ -104,18 +104,18 @@ class Player extends EventEmitter { const queue = this.getQueue(oldState.guild.id); if (!queue || !queue.connection) return; - this.emit("voiceStateUpdate", queue, oldState, newState); + this.emit('voiceStateUpdate', queue, oldState, newState); - if (oldState.channelId && !newState.channelId && newState.member.id === newState.guild.members.me.id) { + if (oldState.channelId && !newState.channelId && newState.member!.id === newState.guild.members.me!.id) { try { queue.destroy(); } catch { /* noop */ } - return void this.emit("botDisconnect", queue); + return void this.emit('botDisconnect', queue); } - if (!oldState.channelId && newState.channelId && newState.member.id === newState.guild.members.me.id) { + if (!oldState.channelId && newState.channelId && newState.member!.id === newState.guild.members.me!.id) { if (oldState.serverMute !== newState.serverMute) { // state.serverMute can be null queue.setPaused(!!newState.serverMute); @@ -123,7 +123,7 @@ class Player extends EventEmitter { // state.suppress can be null queue.setPaused(!!newState.suppress); if (newState.suppress) { - newState.guild.members.me.voice.setRequestToSpeak(true).catch(Util.noop); + newState.guild.members.me!.voice.setRequestToSpeak(true).catch(Util.noop); } } } @@ -134,7 +134,7 @@ class Player extends EventEmitter { if (!Util.isVoiceEmpty(queue.connection.channel)) return; if (!this.queues.has(queue.guild.id)) return; if (queue.options.leaveOnEmpty) queue.destroy(true); - this.emit("channelEmpty", queue); + this.emit('channelEmpty', queue); }, queue.options.leaveOnEmptyCooldown || 0).unref(); queue._cooldownsTimeout.set(`empty_${oldState.guild.id}`, timeout); } @@ -149,8 +149,8 @@ class Player extends EventEmitter { } if (oldState.channelId && newState.channelId && oldState.channelId !== newState.channelId) { - if (newState.member.id === newState.guild.members.me.id) { - if (queue.connection && newState.member.id === newState.guild.members.me.id) queue.connection.channel = newState.channel; + if (newState.member!.id === newState.guild.members.me!.id) { + if (queue.connection && newState.member!.id === newState.guild.members.me!.id) queue.connection.channel = newState.channel!; const emptyTimeout = queue._cooldownsTimeout.get(`empty_${oldState.guild.id}`); const channelEmpty = Util.isVoiceEmpty(queue.connection.channel); if (!channelEmpty && emptyTimeout) { @@ -161,7 +161,7 @@ class Player extends EventEmitter { if (queue.connection && !Util.isVoiceEmpty(queue.connection.channel)) return; if (!this.queues.has(queue.guild.id)) return; if (queue.options.leaveOnEmpty) queue.destroy(true); - this.emit("channelEmpty", queue); + this.emit('channelEmpty', queue); }, queue.options.leaveOnEmptyCooldown || 0).unref(); queue._cooldownsTimeout.set(`empty_${oldState.guild.id}`, timeout); } @@ -173,7 +173,7 @@ class Player extends EventEmitter { if (!Util.isVoiceEmpty(queue.connection.channel)) return; if (!this.queues.has(queue.guild.id)) return; if (queue.options.leaveOnEmpty) queue.destroy(true); - this.emit("channelEmpty", queue); + this.emit('channelEmpty', queue); }, queue.options.leaveOnEmptyCooldown || 0).unref(); queue._cooldownsTimeout.set(`empty_${oldState.guild.id}`, timeout); } else { @@ -195,12 +195,12 @@ class Player extends EventEmitter { * @returns {Queue} */ createQueue(guild: GuildResolvable, queueInitOptions: PlayerOptions & { metadata?: T } = {}): Queue { - guild = this.client.guilds.resolve(guild); - if (!guild) throw new PlayerError("Unknown Guild", ErrorStatusCode.UNKNOWN_GUILD); + guild = this.client.guilds.resolve(guild)!; + if (!guild) throw new PlayerError('Unknown Guild', ErrorStatusCode.UNKNOWN_GUILD); if (this.queues.has(guild.id)) return this.queues.get(guild.id) as Queue; const _meta = queueInitOptions.metadata; - delete queueInitOptions["metadata"]; + delete queueInitOptions['metadata']; queueInitOptions.volumeSmoothness ??= this.options.smoothVolume ? 0.08 : 0; queueInitOptions.ytdlOptions ??= this.options.ytdlOptions; const queue = new Queue(this, guild, queueInitOptions); @@ -216,8 +216,8 @@ class Player extends EventEmitter { * @returns {Queue | undefined} */ getQueue(guild: GuildResolvable): Queue | undefined { - guild = this.client.guilds.resolve(guild); - if (!guild) throw new PlayerError("Unknown Guild", ErrorStatusCode.UNKNOWN_GUILD); + guild = this.client.guilds.resolve(guild)!; + if (!guild) throw new PlayerError('Unknown Guild', ErrorStatusCode.UNKNOWN_GUILD); return this.queues.get(guild.id) as Queue; } @@ -227,9 +227,9 @@ class Player extends EventEmitter { * @returns {Queue} */ deleteQueue(guild: GuildResolvable) { - guild = this.client.guilds.resolve(guild); - if (!guild) throw new PlayerError("Unknown Guild", ErrorStatusCode.UNKNOWN_GUILD); - const prev = this.getQueue(guild); + guild = this.client.guilds.resolve(guild)!; + if (!guild) throw new PlayerError('Unknown Guild', ErrorStatusCode.UNKNOWN_GUILD); + const prev = this.getQueue(guild)!; try { prev.destroy(); @@ -252,11 +252,11 @@ class Player extends EventEmitter { */ async search(query: string | Track, options: SearchOptions): Promise { if (query instanceof Track) return { playlist: query.playlist || null, tracks: [query] }; - if (!options) throw new PlayerError("DiscordPlayer#search needs search options!", ErrorStatusCode.INVALID_ARG_TYPE); - options.requestedBy = this.client.users.resolve(options.requestedBy); - if (!("searchEngine" in options)) options.searchEngine = QueryType.AUTO; - if (typeof options.searchEngine === "string" && this.extractors.has(options.searchEngine)) { - const extractor = this.extractors.get(options.searchEngine); + if (!options) throw new PlayerError('DiscordPlayer#search needs search options!', ErrorStatusCode.INVALID_ARG_TYPE); + options.requestedBy = this.client.users.resolve(options.requestedBy)!; + if (!('searchEngine' in options)) options.searchEngine = QueryType.AUTO; + if (typeof options.searchEngine === 'string' && this.extractors.has(options.searchEngine)) { + const extractor = this.extractors.get(options.searchEngine)!; if (!extractor.validate(query)) return { playlist: null, tracks: [] }; const data = await extractor.handle(query); if (data && data.data.length) { @@ -273,7 +273,7 @@ class Player extends EventEmitter { ...m, requestedBy: options.requestedBy as User, duration: Util.buildTimeCode(Util.parseMS(m.duration)), - playlist: playlist + playlist: playlist! }) ); @@ -302,7 +302,7 @@ class Player extends EventEmitter { ...m, requestedBy: options.requestedBy as User, duration: Util.buildTimeCode(Util.parseMS(m.duration)), - playlist: playlist + playlist: playlist! }) ); @@ -320,14 +320,14 @@ class Player extends EventEmitter { const track = new Track(this, { title: info.videoDetails.title, - description: info.videoDetails.description, + description: info.videoDetails.description!, author: info.videoDetails.author?.name, url: info.videoDetails.video_url, requestedBy: options.requestedBy as User, thumbnail: Util.last(info.videoDetails.thumbnails)?.url, - views: parseInt(info.videoDetails.viewCount.replace(/[^0-9]/g, "")) || 0, + views: parseInt(info.videoDetails.viewCount.replace(/[^0-9]/g, '')) || 0, duration: Util.buildTimeCode(Util.parseMS(parseInt(info.videoDetails.lengthSeconds) * 1000)), - source: "youtube", + source: 'youtube', raw: info }); @@ -335,22 +335,22 @@ class Player extends EventEmitter { } case QueryType.YOUTUBE_SEARCH: { const videos = await YouTube.search(query, { - type: "video" + type: 'video' }).catch(Util.noop); if (!videos) return { playlist: null, tracks: [] }; const tracks = videos.map((m) => { - (m as any).source = "youtube"; // eslint-disable-line @typescript-eslint/no-explicit-any + (m as any).source = 'youtube'; // eslint-disable-line @typescript-eslint/no-explicit-any return new Track(this, { - title: m.title, - description: m.description, - author: m.channel?.name, + title: m.title!, + description: m.description!, + author: m.channel?.name as string, url: m.url, requestedBy: options.requestedBy as User, - thumbnail: m.thumbnail?.displayThumbnailURL("maxresdefault"), + thumbnail: m.thumbnail?.displayThumbnailURL('maxresdefault') as string, views: m.views, duration: m.durationFormatted, - source: "youtube", + source: 'youtube', raw: m }); }); @@ -359,7 +359,8 @@ class Player extends EventEmitter { } case QueryType.SOUNDCLOUD_TRACK: case QueryType.SOUNDCLOUD_SEARCH: { - const result: SoundCloudSearchResult[] = QueryResolver.resolve(query) === QueryType.SOUNDCLOUD_TRACK ? [{ url: query }] : await soundcloud.search(query, "track").catch(() => []); + const result: SoundCloudSearchResult[] = + QueryResolver.resolve(query) === QueryType.SOUNDCLOUD_TRACK ? ([{ url: query }] as SearchResult[]) : await soundcloud.search(query, 'track').catch(() => []); if (!result || !result.length) return { playlist: null, tracks: [] }; const res: Track[] = []; @@ -376,7 +377,7 @@ class Player extends EventEmitter { views: trackInfo.playCount, author: trackInfo.author.name, requestedBy: options.requestedBy, - source: "soundcloud", + source: 'soundcloud', engine: trackInfo }); @@ -392,18 +393,18 @@ class Player extends EventEmitter { if (!spotifyData) return { playlist: null, tracks: [] }; const spotifyTrack = new Track(this, { title: spotifyData.name, - description: spotifyData.description ?? "", - author: spotifyData.artists[0]?.name ?? "Unknown Artist", + description: spotifyData.description ?? '', + author: spotifyData.artists[0]?.name ?? 'Unknown Artist', url: spotifyData.external_urls?.spotify ?? query, thumbnail: (spotifyData.coverArt?.sources?.[0]?.url ?? spotifyData.album?.images[0]?.url ?? - (spotifyData.preview_url?.length && `https://i.scdn.co/image/${spotifyData.preview_url?.split("?cid=")[1]}`)) || - "https://www.scdn.co/i/_global/twitter_card-default.jpg", + (spotifyData.preview_url?.length && `https://i.scdn.co/image/${spotifyData.preview_url?.split('?cid=')[1]}`)) || + 'https://www.scdn.co/i/_global/twitter_card-default.jpg', duration: Util.buildTimeCode(Util.parseMS(spotifyData.duration_ms ?? spotifyData.duration ?? spotifyData.maxDuration)), views: 0, requestedBy: options.requestedBy, - source: "spotify" + source: 'spotify' }); return { playlist: null, tracks: [spotifyTrack] }; @@ -417,18 +418,18 @@ class Player extends EventEmitter { const playlist = new Playlist(this, { title: spotifyPlaylist.name ?? spotifyPlaylist.title, - description: spotifyPlaylist.description ?? "", - thumbnail: spotifyPlaylist.coverArt?.sources?.[0]?.url ?? spotifyPlaylist.images[0]?.url ?? "https://www.scdn.co/i/_global/twitter_card-default.jpg", + description: spotifyPlaylist.description ?? '', + thumbnail: spotifyPlaylist.coverArt?.sources?.[0]?.url ?? spotifyPlaylist.images[0]?.url ?? 'https://www.scdn.co/i/_global/twitter_card-default.jpg', type: spotifyPlaylist.type, - source: "spotify", + source: 'spotify', author: - spotifyPlaylist.type !== "playlist" + spotifyPlaylist.type !== 'playlist' ? { - name: spotifyPlaylist.artists[0]?.name ?? "Unknown Artist", + name: spotifyPlaylist.artists[0]?.name ?? 'Unknown Artist', url: spotifyPlaylist.artists[0]?.external_urls?.spotify ?? null } : { - name: spotifyPlaylist.owner?.display_name ?? spotifyPlaylist.owner?.id ?? "Unknown Artist", + name: spotifyPlaylist.owner?.display_name ?? spotifyPlaylist.owner?.id ?? 'Unknown Artist', url: spotifyPlaylist.owner?.external_urls?.spotify ?? null }, tracks: [], @@ -437,20 +438,20 @@ class Player extends EventEmitter { rawPlaylist: spotifyPlaylist }); - if (spotifyPlaylist.type !== "playlist") { + 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 ?? "", - description: m.description ?? "", - author: m.artists[0]?.name ?? "Unknown Artist", + title: m.name ?? '', + description: m.description ?? '', + author: m.artists[0]?.name ?? 'Unknown Artist', url: m.external_urls?.spotify ?? query, - thumbnail: spotifyPlaylist.images[0]?.url ?? "https://www.scdn.co/i/_global/twitter_card-default.jpg", + thumbnail: spotifyPlaylist.images[0]?.url ?? 'https://www.scdn.co/i/_global/twitter_card-default.jpg', duration: Util.buildTimeCode(Util.parseMS(m.duration_ms)), views: 0, requestedBy: options.requestedBy as User, playlist, - source: "spotify" + source: 'spotify' }); return data; @@ -459,16 +460,16 @@ class Player extends EventEmitter { // eslint-disable-next-line @typescript-eslint/no-explicit-any playlist.tracks = spotifyPlaylist.trackList.map((m: any) => { const data = new Track(this, { - title: m.title ?? "", - description: m.description ?? "", - author: m.subtitle ?? "Unknown Artist", + title: m.title ?? '', + description: m.description ?? '', + author: m.subtitle ?? 'Unknown Artist', url: m.external_urls?.spotify ?? query, - thumbnail: m.album?.images?.[0]?.url ?? "https://www.scdn.co/i/_global/twitter_card-default.jpg", + thumbnail: m.album?.images?.[0]?.url ?? 'https://www.scdn.co/i/_global/twitter_card-default.jpg', duration: Util.buildTimeCode(Util.parseMS(m.duration)), views: 0, requestedBy: options.requestedBy as User, playlist, - source: "spotify" + source: 'spotify' }); return data; }) as Track[]; @@ -482,12 +483,12 @@ class Player extends EventEmitter { const res = new Playlist(this, { title: data.title, - description: data.description ?? "", - thumbnail: data.thumbnail ?? "https://soundcloud.com/pwa-icon-192.png", - type: "playlist", - source: "soundcloud", + description: data.description ?? '', + thumbnail: data.thumbnail ?? 'https://soundcloud.com/pwa-icon-192.png', + type: 'playlist', + source: 'soundcloud', author: { - name: data.author?.name ?? data.author?.username ?? "Unknown Artist", + name: data.author?.name ?? data.author?.username ?? 'Unknown Artist', url: data.author?.profile }, tracks: [], @@ -499,15 +500,15 @@ class Player extends EventEmitter { for (const song of data.tracks) { const track = new Track(this, { title: song.title, - description: song.description ?? "", - author: song.author?.username ?? song.author?.name ?? "Unknown Artist", + description: song.description ?? '', + author: song.author?.username ?? song.author?.name ?? 'Unknown Artist', url: song.url, thumbnail: song.thumbnail, duration: Util.buildTimeCode(Util.parseMS(song.duration)), views: song.playCount ?? 0, requestedBy: options.requestedBy, playlist: res, - source: "soundcloud", + source: 'soundcloud', engine: song }); res.tracks.push(track); @@ -522,35 +523,35 @@ class Player extends EventEmitter { await ytpl.fetch().catch(Util.noop); const playlist: Playlist = new Playlist(this, { - title: ytpl.title, + title: ytpl.title!, thumbnail: ytpl.thumbnail as unknown as string, - description: "", - type: "playlist", - source: "youtube", + description: '', + type: 'playlist', + source: 'youtube', author: { - name: ytpl.channel.name, - url: ytpl.channel.url + name: ytpl.channel!.name as string, + url: ytpl.channel!.url as string }, tracks: [], - id: ytpl.id, - url: ytpl.url, + id: ytpl.id as string, + url: ytpl.url as string, rawPlaylist: ytpl }); playlist.tracks = ytpl.videos.map( (video) => new Track(this, { - title: video.title, - description: video.description, - author: video.channel?.name, + title: video.title as string, + description: video.description as string, + author: video.channel?.name as string, url: video.url, requestedBy: options.requestedBy as User, - thumbnail: video.thumbnail.url, + thumbnail: video.thumbnail!.url as string, views: video.views, duration: video.durationFormatted, raw: video, playlist: playlist, - source: "youtube" + source: 'youtube' }) ); @@ -570,15 +571,15 @@ class Player extends EventEmitter { */ // eslint-disable-next-line @typescript-eslint/no-explicit-any use(extractorName: string, extractor: ExtractorModel | any, force = false): ExtractorModel { - if (!extractorName) throw new PlayerError("Cannot use unknown extractor!", ErrorStatusCode.UNKNOWN_EXTRACTOR); - if (this.extractors.has(extractorName) && !force) return this.extractors.get(extractorName); + if (!extractorName) throw new PlayerError('Cannot use unknown extractor!', ErrorStatusCode.UNKNOWN_EXTRACTOR); + if (this.extractors.has(extractorName) && !force) return this.extractors.get(extractorName)!; if (extractor instanceof ExtractorModel) { this.extractors.set(extractorName, extractor); return extractor; } - for (const method of ["validate", "getInfo"]) { - if (typeof extractor[method] !== "function") throw new PlayerError("Invalid extractor data!", ErrorStatusCode.INVALID_EXTRACTOR); + for (const method of ['validate', 'getInfo']) { + if (typeof extractor[method] !== 'function') throw new PlayerError('Invalid extractor data!', ErrorStatusCode.INVALID_EXTRACTOR); } const model = new ExtractorModel(extractorName, extractor); @@ -604,21 +605,21 @@ class Player extends EventEmitter { * @returns {string} */ scanDeps() { - const line = "-".repeat(50); + const line = '-'.repeat(50); const depsReport = generateDependencyReport(); const extractorReport = this.extractors .map((m) => { - return `${m.name} :: ${m.version || "0.1.0"}`; + return `${m.name} :: ${m.version || '0.1.0'}`; }) - .join("\n"); - return `${depsReport}\n${line}\nLoaded Extractors:\n${extractorReport || "None"}`; + .join('\n'); + return `${depsReport}\n${line}\nLoaded Extractors:\n${extractorReport || 'None'}`; } emit(eventName: U, ...args: Parameters): boolean { if (this.requiredEvents.includes(eventName) && !super.eventNames().includes(eventName)) { // eslint-disable-next-line no-console console.error(...args); - process.emitWarning(`[DiscordPlayerWarning] Unhandled "${eventName}" event! Events ${this.requiredEvents.map((m) => `"${m}"`).join(", ")} must have event listeners!`); + process.emitWarning(`[DiscordPlayerWarning] Unhandled "${eventName}" event! Events ${this.requiredEvents.map((m) => `"${m}"`).join(', ')} must have event listeners!`); return false; } else { return super.emit(eventName, ...args); @@ -631,7 +632,7 @@ class Player extends EventEmitter { * @returns {Queue} */ resolveQueue(queueLike: GuildResolvable | Queue): Queue { - return this.getQueue(queueLike instanceof Queue ? queueLike.guild : queueLike); + return this.getQueue(queueLike instanceof Queue ? queueLike.guild : queueLike)!; } *[Symbol.iterator]() { diff --git a/src/Structures/ExtractorModel.ts b/packages/discord-player/src/Structures/ExtractorModel.ts similarity index 83% rename from src/Structures/ExtractorModel.ts rename to packages/discord-player/src/Structures/ExtractorModel.ts index dc8161806..ea45bb1ad 100644 --- a/src/Structures/ExtractorModel.ts +++ b/packages/discord-player/src/Structures/ExtractorModel.ts @@ -1,4 +1,4 @@ -import { ExtractorModelData } from "../types/types"; +import { ExtractorModelData } from '../types/types'; class ExtractorModel { name: string; @@ -23,7 +23,7 @@ class ExtractorModel { * @type {any} * @private */ - Object.defineProperty(this, "_raw", { value: data, configurable: false, writable: false, enumerable: false }); + Object.defineProperty(this, '_raw', { value: data, configurable: false, writable: false, enumerable: false }); } /** @@ -31,14 +31,14 @@ class ExtractorModel { * @param {string} query Query to handle * @returns {Promise} */ - async handle(query: string): Promise { + async handle(query: string): Promise { const data = await this._raw.getInfo(query); if (!data) return null; return { playlist: data.playlist ?? null, data: - (data.info as Omit["data"])?.map((m) => ({ + (data.info as Omit['data'])?.map((m) => ({ title: m.title as string, duration: m.duration as number, thumbnail: m.thumbnail as string, @@ -47,7 +47,7 @@ class ExtractorModel { author: m.author as string, description: m.description as string, url: m.url as string, - source: m.source || "arbitrary" + source: m.source || 'arbitrary' })) ?? [] }; } @@ -66,7 +66,7 @@ class ExtractorModel { * @type {string} */ get version(): string { - return this._raw.version ?? "0.0.0"; + return this._raw.version ?? '0.0.0'; } } diff --git a/src/Structures/PlayerError.ts b/packages/discord-player/src/Structures/PlayerError.ts similarity index 58% rename from src/Structures/PlayerError.ts rename to packages/discord-player/src/Structures/PlayerError.ts index da36da6fc..b23a450c6 100644 --- a/src/Structures/PlayerError.ts +++ b/packages/discord-player/src/Structures/PlayerError.ts @@ -1,18 +1,18 @@ export enum ErrorStatusCode { - STREAM_ERROR = "StreamError", - AUDIO_PLAYER_ERROR = "AudioPlayerError", - PLAYER_ERROR = "PlayerError", - NO_AUDIO_RESOURCE = "NoAudioResource", - UNKNOWN_GUILD = "UnknownGuild", - INVALID_ARG_TYPE = "InvalidArgType", - UNKNOWN_EXTRACTOR = "UnknownExtractor", - INVALID_EXTRACTOR = "InvalidExtractor", - INVALID_CHANNEL_TYPE = "InvalidChannelType", - INVALID_TRACK = "InvalidTrack", - UNKNOWN_REPEAT_MODE = "UnknownRepeatMode", - TRACK_NOT_FOUND = "TrackNotFound", - NO_CONNECTION = "NoConnection", - DESTROYED_QUEUE = "DestroyedQueue" + STREAM_ERROR = 'StreamError', + AUDIO_PLAYER_ERROR = 'AudioPlayerError', + PLAYER_ERROR = 'PlayerError', + NO_AUDIO_RESOURCE = 'NoAudioResource', + UNKNOWN_GUILD = 'UnknownGuild', + INVALID_ARG_TYPE = 'InvalidArgType', + UNKNOWN_EXTRACTOR = 'UnknownExtractor', + INVALID_EXTRACTOR = 'InvalidExtractor', + INVALID_CHANNEL_TYPE = 'InvalidChannelType', + INVALID_TRACK = 'InvalidTrack', + UNKNOWN_REPEAT_MODE = 'UnknownRepeatMode', + TRACK_NOT_FOUND = 'TrackNotFound', + NO_CONNECTION = 'NoConnection', + DESTROYED_QUEUE = 'DestroyedQueue' } export class PlayerError extends Error { diff --git a/src/Structures/Playlist.ts b/packages/discord-player/src/Structures/Playlist.ts similarity index 95% rename from src/Structures/Playlist.ts rename to packages/discord-player/src/Structures/Playlist.ts index d76dd6680..8bd7526fd 100644 --- a/src/Structures/Playlist.ts +++ b/packages/discord-player/src/Structures/Playlist.ts @@ -1,6 +1,6 @@ -import { Player } from "../Player"; -import { Track } from "./Track"; -import { PlaylistInitData, PlaylistJSON, TrackJSON, TrackSource } from "../types/types"; +import { Player } from '../Player'; +import { Track } from './Track'; +import { PlaylistInitData, PlaylistJSON, TrackJSON, TrackSource } from '../types/types'; class Playlist { public readonly player: Player; @@ -8,7 +8,7 @@ class Playlist { public title: string; public description: string; public thumbnail: string; - public type: "album" | "playlist"; + public type: 'album' | 'playlist'; public source: TrackSource; public author: { name: string; diff --git a/src/Structures/Queue.ts b/packages/discord-player/src/Structures/Queue.ts similarity index 80% rename from src/Structures/Queue.ts rename to packages/discord-player/src/Structures/Queue.ts index 21369c04d..ad260ea10 100644 --- a/src/Structures/Queue.ts +++ b/packages/discord-player/src/Structures/Queue.ts @@ -1,30 +1,34 @@ -import { Collection, Guild, StageChannel, VoiceChannel, SnowflakeUtil, GuildChannelResolvable, ChannelType } from "discord.js"; -import { Player } from "../Player"; -import { StreamDispatcher } from "../VoiceInterface/StreamDispatcher"; -import Track from "./Track"; -import { PlayerOptions, PlayerProgressbarOptions, PlayOptions, QueueFilters, QueueRepeatMode, TrackSource } from "../types/types"; -import ytdl from "ytdl-core"; -import { AudioResource, StreamType } from "@discordjs/voice"; -import { Util } from "../utils/Util"; -import YouTube from "youtube-sr"; -import AudioFilters from "../utils/AudioFilters"; -import { PlayerError, ErrorStatusCode } from "./PlayerError"; -import type { Readable } from "stream"; -import { VolumeTransformer } from "../VoiceInterface/VolumeTransformer"; -import { createFFmpegStream } from "../utils/FFmpegStream"; -import os from "os"; -import { parentPort } from "worker_threads"; -import type { EqualizerBand } from "@discord-player/equalizer"; +import { Collection, Guild, StageChannel, VoiceChannel, SnowflakeUtil, GuildChannelResolvable, ChannelType } from 'discord.js'; +import { Player } from '../Player'; +import { StreamDispatcher } from '../VoiceInterface/StreamDispatcher'; +import Track from './Track'; +import { PlayerOptions, PlayerProgressbarOptions, PlayOptions, QueueFilters, QueueRepeatMode, TrackSource } from '../types/types'; +import ytdl from 'ytdl-core'; +import { AudioResource, StreamType } from '@discordjs/voice'; +import { Util } from '../utils/Util'; +import YouTube from 'youtube-sr'; +import AudioFilters from '../utils/AudioFilters'; +import { PlayerError, ErrorStatusCode } from './PlayerError'; +import type { Readable } from 'stream'; +import { VolumeTransformer } from '../VoiceInterface/VolumeTransformer'; +import { createFFmpegStream } from '../utils/FFmpegStream'; +import os from 'os'; +import { parentPort } from 'worker_threads'; +import type { EqualizerBand } from '@discord-player/equalizer'; + +const OBCS_DEFAULT = async () => { + return undefined; +}; class Queue { public readonly guild: Guild; public readonly player: Player; - public connection: StreamDispatcher; + public connection!: StreamDispatcher; public tracks: Track[] = []; public previousTracks: Track[] = []; public options: PlayerOptions; public playing = false; - public metadata?: T = null; + public metadata?: T | null = null; public repeatMode: QueueRepeatMode = 0; public readonly id = SnowflakeUtil.generate().toString(); private _streamTime = 0; @@ -33,7 +37,7 @@ class Queue { private _filtersUpdate = false; private _lastEQBands: EqualizerBand[] = []; #destroyed = false; - public onBeforeCreateStream: (track: Track, source: TrackSource, queue: Queue) => Promise = null; + public onBeforeCreateStream: (track: Track, source: TrackSource, queue: Queue) => Promise = OBCS_DEFAULT; /** * Queue constructor @@ -121,9 +125,10 @@ class Queue { ); if (Array.isArray(options.equalizerBands)) this._lastEQBands = options.equalizerBands; - if ("onBeforeCreateStream" in this.options) this.onBeforeCreateStream = this.options.onBeforeCreateStream; + // eslint-disable-next-line + if ('onBeforeCreateStream' in this.options) this.onBeforeCreateStream = this.options.onBeforeCreateStream!; - this.player.emit("debug", this, `Queue initialized:\n\n${this.player.scanDeps()}`); + this.player.emit('debug', this, `Queue initialized:\n\n${this.player.scanDeps()}`); } /** @@ -216,7 +221,7 @@ class Queue { */ public async forceNext() { if (this.connection.audioResource) { - this.connection.emit("finish", this.connection.audioResource); + this.connection.emit('finish', this.connection.audioResource); } else if (this.tracks.length) { await this.play(); } @@ -264,36 +269,36 @@ class Queue { this.connection = connection; if (_channel.type === ChannelType.GuildStageVoice) { - await _channel.guild.members.me.voice.setSuppressed(false).catch(async () => { - return await _channel.guild.members.me.voice.setRequestToSpeak(true).catch(Util.noop); + await _channel.guild.members.me!.voice.setSuppressed(false).catch(async () => { + return await _channel.guild.members.me!.voice.setRequestToSpeak(true).catch(Util.noop); }); } - this.connection.on("error", (err) => { + this.connection.on('error', (err) => { if (this.#watchDestroyed(false)) return; - this.player.emit("connectionError", this, err); + this.player.emit('connectionError', this, err); }); - this.connection.on("debug", (msg) => { + this.connection.on('debug', (msg) => { if (this.#watchDestroyed(false)) return; - this.player.emit("debug", this, msg); + this.player.emit('debug', this, msg); }); - this.player.emit("connectionCreate", this, this.connection); + this.player.emit('connectionCreate', this, this.connection); - this.connection.on("start", (resource) => { + this.connection.on('start', (resource) => { if (this.#watchDestroyed(false)) return; this.playing = true; - if (!this._filtersUpdate) this.player.emit("trackStart", this, resource?.metadata ?? this.current); + if (!this._filtersUpdate) this.player.emit('trackStart', this, resource?.metadata ?? this.current); this._filtersUpdate = false; }); - this.connection.on("finish", async (resource) => { + this.connection.on('finish', async (resource) => { if (this.#watchDestroyed(false)) return; this.playing = false; if (this._filtersUpdate) return; this._streamTime = 0; - this.player.emit("trackEnd", this, resource.metadata); + this.player.emit('trackEnd', this, resource.metadata); if (!this.tracks.length && this.repeatMode === QueueRepeatMode.OFF) { this.emitEnd(); @@ -316,7 +321,7 @@ class Queue { if (!this.player.queues.has(this.guild.id)) return; if (this.tracks.length || this.current) return; if (this.options.leaveOnEnd) this.destroy(); - this.player.emit("queueEnd", this); + this.player.emit('queueEnd', this); }, this.options.leaveOnEndCooldown || 0).unref(); this._cooldownsTimeout.set(`queueEnd_${this.guild.id}`, timeout); @@ -363,10 +368,10 @@ class Queue { */ addTrack(track: Track) { if (this.#watchDestroyed()) return; - if (!(track instanceof Track)) throw new PlayerError("invalid track", ErrorStatusCode.INVALID_TRACK); + if (!(track instanceof Track)) throw new PlayerError('invalid track', ErrorStatusCode.INVALID_TRACK); this.tracks.push(track); this.refreshEndCooldown(); - this.player.emit("trackAdd", this, track); + this.player.emit('trackAdd', this, track); } /** @@ -375,10 +380,10 @@ class Queue { */ addTracks(tracks: Track[]) { if (this.#watchDestroyed()) return; - if (!tracks.every((y) => y instanceof Track)) throw new PlayerError("invalid track", ErrorStatusCode.INVALID_TRACK); + if (!tracks.every((y) => y instanceof Track)) throw new PlayerError('invalid track', ErrorStatusCode.INVALID_TRACK); this.tracks.push(...tracks); this.refreshEndCooldown(); - this.player.emit("tracksAdd", this, tracks); + this.player.emit('tracksAdd', this, tracks); } /** @@ -397,10 +402,10 @@ class Queue { * @param {number|auto} bitrate bitrate to set * @returns {void} */ - setBitrate(bitrate: number | "auto") { + setBitrate(bitrate: number | 'auto') { if (this.#watchDestroyed()) return; if (!this.connection?.audioResource?.encoder) return; - if (bitrate === "auto") bitrate = this.connection.channel?.bitrate ?? 64000; + if (bitrate === 'auto') bitrate = this.connection.channel?.bitrate ?? 64000; this.connection.audioResource.encoder.setBitrate(bitrate); } @@ -434,7 +439,7 @@ class Queue { * @type {number} */ get volume() { - if (this.#watchDestroyed()) return; + if (this.#watchDestroyed()) return 100; if (!this.connection) return 100; return this.connection.volume; } @@ -448,11 +453,11 @@ class Queue { * @type {number} */ get streamTime() { - if (this.#watchDestroyed()) return; + if (this.#watchDestroyed()) return 0; if (!this.connection) return 0; const playbackTime = this._streamTime + this.connection.streamTime; - const NC = this._activeFilters.includes("nightcore") ? 1.25 : null; - const VW = this._activeFilters.includes("vaporwave") ? 0.8 : null; + const NC = this._activeFilters.includes('nightcore') ? 1.25 : null; + const VW = this._activeFilters.includes('vaporwave') ? 0.8 : null; if (NC && VW) return playbackTime * (NC + VW); return NC ? playbackTime * NC : VW ? playbackTime * VW : playbackTime; @@ -506,7 +511,7 @@ class Queue { if (filters[filter as keyof QueueFilters] === true) _filters.push(filter); } - if (this._activeFilters.join("") === _filters.join("")) return; + if (this._activeFilters.join('') === _filters.join('')) return; const newFilters = AudioFilters.create(_filters).trim(); const streamTime = this.streamTime; @@ -516,7 +521,7 @@ class Queue { immediate: true, filtersUpdate: true, seek: streamTime, - encoderArgs: !_filters.length ? undefined : ["-af", newFilters] + encoderArgs: !_filters.length ? undefined : ['-af', newFilters] }); } @@ -547,7 +552,7 @@ class Queue { async back() { if (this.#watchDestroyed()) return; const prev = this.previousTracks[this.previousTracks.length - 2]; // because last item is the current track - if (!prev) throw new PlayerError("Could not find previous track", ErrorStatusCode.TRACK_NOT_FOUND); + if (!prev) throw new PlayerError('Could not find previous track', ErrorStatusCode.TRACK_NOT_FOUND); return await this.play(prev, { immediate: true }); } @@ -593,16 +598,16 @@ class Queue { */ remove(track: Track | string | number) { if (this.#watchDestroyed()) return; - let trackFound: Track = null; - if (typeof track === "number") { + let trackFound: Track | null = null; + if (typeof track === 'number') { trackFound = this.tracks[track]; if (trackFound) { - this.tracks = this.tracks.filter((t) => t.id !== trackFound.id); + this.tracks = this.tracks.filter((t) => t.id !== trackFound!.id); } } else { - trackFound = this.tracks.find((s) => s.id === (track instanceof Track ? track.id : track)); + trackFound = this.tracks.find((s) => s.id === (track instanceof Track ? track.id : track))!; if (trackFound) { - this.tracks = this.tracks.filter((s) => s.id !== trackFound.id); + this.tracks = this.tracks.filter((s) => s.id !== trackFound!.id); } } @@ -616,7 +621,7 @@ class Queue { */ getTrackPosition(track: number | Track | string) { if (this.#watchDestroyed()) return; - if (typeof track === "number") return this.tracks[track] != null ? track : -1; + if (typeof track === 'number') return this.tracks[track] != null ? track : -1; return this.tracks.findIndex((pred) => pred.id === (track instanceof Track ? track.id : track)); } @@ -628,7 +633,7 @@ class Queue { jump(track: Track | number): void { if (this.#watchDestroyed()) return; const foundTrack = this.remove(track); - if (!foundTrack) throw new PlayerError("Track not found", ErrorStatusCode.TRACK_NOT_FOUND); + if (!foundTrack) throw new PlayerError('Track not found', ErrorStatusCode.TRACK_NOT_FOUND); this.tracks.splice(0, 0, foundTrack); @@ -642,9 +647,9 @@ class Queue { */ skipTo(track: Track | number): void { if (this.#watchDestroyed()) return; - const trackIndex = this.getTrackPosition(track); + const trackIndex = this.getTrackPosition(track)!; const removedTrack = this.remove(track); - if (!removedTrack) throw new PlayerError("Track not found", ErrorStatusCode.TRACK_NOT_FOUND); + if (!removedTrack) throw new PlayerError('Track not found', ErrorStatusCode.TRACK_NOT_FOUND); this.tracks.splice(0, trackIndex, removedTrack); @@ -658,12 +663,12 @@ class Queue { */ insert(track: Track, index = 0) { if (this.#watchDestroyed()) return; - if (!track || !(track instanceof Track)) throw new PlayerError("track must be the instance of Track", ErrorStatusCode.INVALID_TRACK); - if (typeof index !== "number" || index < 0 || !Number.isFinite(index)) throw new PlayerError(`Invalid index "${index}"`, ErrorStatusCode.INVALID_ARG_TYPE); + if (!track || !(track instanceof Track)) throw new PlayerError('track must be the instance of Track', ErrorStatusCode.INVALID_TRACK); + if (typeof index !== 'number' || index < 0 || !Number.isFinite(index)) throw new PlayerError(`Invalid index "${index}"`, ErrorStatusCode.INVALID_ARG_TYPE); this.tracks.splice(index, 0, track); - this.player.emit("trackAdd", this, track); + this.player.emit('trackAdd', this, track); } /** @@ -680,7 +685,7 @@ class Queue { getPlayerTimestamp() { if (this.#watchDestroyed()) return; const currentStreamTime = this.streamTime; - const totalTime = this.current.durationMS; + const totalTime = this.current!.durationMS; const currentTimecode = Util.buildTimeCode(Util.parseMS(currentStreamTime)); const endTimecode = Util.buildTimeCode(Util.parseMS(totalTime)); @@ -699,24 +704,24 @@ class Queue { */ createProgressBar(options: PlayerProgressbarOptions = { timecodes: true }) { if (this.#watchDestroyed()) return; - const length = typeof options.length === "number" ? (options.length <= 0 || options.length === Infinity ? 15 : options.length) : 15; + const length = typeof options.length === 'number' ? (options.length <= 0 || options.length === Infinity ? 15 : options.length) : 15; - const index = Math.round((this.streamTime / this.current.durationMS) * length); - const indicator = typeof options.indicator === "string" && options.indicator.length > 0 ? options.indicator : "🔘"; - const line = typeof options.line === "string" && options.line.length > 0 ? options.line : "▬"; + const index = Math.round((this.streamTime / this.current!.durationMS) * length); + const indicator = typeof options.indicator === 'string' && options.indicator.length > 0 ? options.indicator : '🔘'; + const line = typeof options.line === 'string' && options.line.length > 0 ? options.line : '▬'; if (index >= 1 && index <= length) { - const bar = line.repeat(length - 1).split(""); + const bar = line.repeat(length - 1).split(''); bar.splice(index, 0, indicator); if (options.timecodes) { - const timestamp = this.getPlayerTimestamp(); - return `${timestamp.current} ┃ ${bar.join("")} ┃ ${timestamp.end}`; + const timestamp = this.getPlayerTimestamp()!; + return `${timestamp.current} ┃ ${bar.join('')} ┃ ${timestamp.end}`; } else { - return `${bar.join("")}`; + return `${bar.join('')}`; } } else { if (options.timecodes) { - const timestamp = this.getPlayerTimestamp(); + const timestamp = this.getPlayerTimestamp()!; return `${timestamp.current} ┃ ${indicator}${line.repeat(length - 1)} ┃ ${timestamp.end}`; } else { return `${indicator}${line.repeat(length - 1)}`; @@ -729,7 +734,7 @@ class Queue { * @type {Number} */ get totalTime(): number { - if (this.#watchDestroyed()) return; + if (this.#watchDestroyed()) return 0; return this.tracks.length > 0 ? this.tracks.map((t) => t.durationMS).reduce((p, c) => p + c) : 0; } @@ -747,7 +752,7 @@ class Queue { freeMem: os.freemem(), platform: process.platform }, - isShard: typeof process.send === "function" || parentPort != null, + isShard: typeof process.send === 'function' || parentPort != null, latency: { client: this.player.client.ws.ping, udp: this.connection.voiceConnection.ping.udp, @@ -776,12 +781,12 @@ class Queue { */ async play(src?: Track, options: PlayOptions = {}): Promise { if (this.#watchDestroyed(false)) return; - if (!this.connection || !this.connection.voiceConnection) throw new PlayerError("Voice connection is not available, use .connect()!", ErrorStatusCode.NO_CONNECTION); + if (!this.connection || !this.connection.voiceConnection) throw new PlayerError('Voice connection is not available, use .connect()!', ErrorStatusCode.NO_CONNECTION); if (src && (this.playing || this.tracks.length) && !options.immediate) return this.addTrack(src); const track = options.filtersUpdate && !options.immediate ? src || this.current : src ?? this.tracks.shift(); if (!track) return; - this.player.emit("debug", this, "Received play request"); + this.player.emit('debug', this, 'Received play request'); if (!options.filtersUpdate) { this.previousTracks = this.previousTracks.filter((x) => x.id !== track.id); @@ -789,12 +794,12 @@ class Queue { } let stream = null; - const hasCustomDownloader = typeof this.onBeforeCreateStream === "function"; + const hasCustomDownloader = typeof this.onBeforeCreateStream === 'function'; - if (["youtube", "spotify"].includes(track.raw.source)) { + if (['youtube', 'spotify'].includes(track.raw.source!)) { let spotifyResolved = false; - if (this.options.spotifyBridge && track.raw.source === "spotify" && !track.raw.engine) { - track.raw.engine = await YouTube.search(`${track.author} ${track.title}`, { type: "video" }) + if (this.options.spotifyBridge && track.raw.source === 'spotify' && !track.raw.engine) { + track.raw.engine = await YouTube.search(`${track.author} ${track.title}`, { type: 'video' }) .then((res) => res[0].url) .catch(() => { /* void */ @@ -802,11 +807,11 @@ class Queue { spotifyResolved = true; } - const url = track.raw.source === "spotify" ? track.raw.engine : track.url; + const url = track.raw.source === 'spotify' ? track.raw.engine : track.url; if (!url) return void this.play(this.tracks.shift(), { immediate: true }); if (hasCustomDownloader) { - stream = (await this.onBeforeCreateStream(track, spotifyResolved ? "youtube" : track.raw.source, this)) || null; + stream = (await this.onBeforeCreateStream(track, spotifyResolved ? 'youtube' : track.raw.source!, this)) || null; } if (!stream) { @@ -815,19 +820,19 @@ class Queue { } else { const arbitraryStream = (hasCustomDownloader && (await this.onBeforeCreateStream(track, track.raw.source || track.raw.engine, this))) || null; stream = - arbitraryStream || (track.raw.source === "soundcloud" && typeof track.raw.engine?.downloadProgressive === "function") + arbitraryStream || (track.raw.source === 'soundcloud' && typeof track.raw.engine?.downloadProgressive === 'function') ? await track.raw.engine.downloadProgressive() - : typeof track.raw.engine === "function" + : typeof track.raw.engine === 'function' ? await track.raw.engine() : track.raw.engine; } const ffmpegStream = createFFmpegStream(stream, { - encoderArgs: options.encoderArgs || this._activeFilters.length ? ["-af", AudioFilters.create(this._activeFilters)] : [], + encoderArgs: options.encoderArgs || this._activeFilters.length ? ['-af', AudioFilters.create(this._activeFilters)] : [], seek: options.seek ? options.seek / 1000 : 0, - fmt: "s16le" - }).on("error", (err) => { - if (!`${err}`.toLowerCase().includes("premature close")) this.player.emit("error", this, err); + fmt: 's16le' + }).on('error', (err) => { + if (!`${err}`.toLowerCase().includes('premature close')) this.player.emit('error', this, err); }); const resource: AudioResource = this.connection.createStream(ffmpegStream, { @@ -839,12 +844,12 @@ class Queue { }); if (options.seek) this._streamTime = options.seek; - this._filtersUpdate = options.filtersUpdate; + this._filtersUpdate = options.filtersUpdate!; const volumeTransformer = resource.volume as VolumeTransformer; - if (volumeTransformer && typeof this.options.initialVolume === "number") volumeTransformer.setVolume(Math.pow(this.options.initialVolume / 100, 1.660964)); - if (volumeTransformer?.hasSmoothness && typeof this.options.volumeSmoothness === "number") { - if (typeof volumeTransformer.setSmoothness === "function") volumeTransformer.setSmoothness(this.options.volumeSmoothness || 0); + if (volumeTransformer && typeof this.options.initialVolume === 'number') volumeTransformer.setVolume(Math.pow(this.options.initialVolume / 100, 1.660964)); + if (volumeTransformer?.hasSmoothness && typeof this.options.volumeSmoothness === 'number') { + if (typeof volumeTransformer.setSmoothness === 'function') volumeTransformer.setSmoothness(this.options.volumeSmoothness || 0); } setTimeout(() => { @@ -860,11 +865,11 @@ class Queue { */ private async _handleAutoplay(track: Track): Promise { if (this.#watchDestroyed()) return; - if (!track || ![track.source, track.raw?.source].includes("youtube")) { + if (!track || ![track.source, track.raw?.source].includes('youtube')) { return this.emitEnd(); } let info = await YouTube.getVideo(track.url) - .then((x) => x.videos[0]) + .then((x) => x.videos![0]) .catch(Util.noop); // fallback if (!info) @@ -876,15 +881,15 @@ class Queue { } const nextTrack = new Track(this.player, { - title: info.title, + title: info.title!, url: `https://www.youtube.com/watch?v=${info.id}`, - duration: info.durationFormatted ? Util.buildTimeCode(Util.parseMS(info.duration * 1000)) : "0:00", - description: "", - thumbnail: typeof info.thumbnail === "string" ? info.thumbnail : info.thumbnail.url, + duration: info.durationFormatted ? Util.buildTimeCode(Util.parseMS(info.duration * 1000)) : '0:00', + description: '', + thumbnail: typeof info.thumbnail === 'string' ? info.thumbnail! : info.thumbnail!.url!, views: info.views, - author: info.channel.name, + author: info.channel!.name!, requestedBy: track.requestedBy, - source: "youtube" + source: 'youtube' }); this.play(nextTrack, { immediate: true }); @@ -916,13 +921,13 @@ class Queue { */ toString() { if (this.#watchDestroyed()) return; - if (!this.tracks.length) return "No songs available to display!"; - return `**Upcoming Songs:**\n${this.tracks.map((m, i) => `${i + 1}. **${m.title}**`).join("\n")}`; + if (!this.tracks.length) return 'No songs available to display!'; + return `**Upcoming Songs:**\n${this.tracks.map((m, i) => `${i + 1}. **${m.title}**`).join('\n')}`; } #watchDestroyed(emit = true) { if (this.#destroyed) { - if (emit) this.player.emit("error", this, new PlayerError("Cannot use destroyed queue", ErrorStatusCode.DESTROYED_QUEUE)); + if (emit) this.player.emit('error', this, new PlayerError('Cannot use destroyed queue', ErrorStatusCode.DESTROYED_QUEUE)); return true; } @@ -930,7 +935,7 @@ class Queue { } #getBufferingTimeout() { - const timeout = this.options.bufferingTimeout; + const timeout = this.options.bufferingTimeout!; if (isNaN(timeout) || timeout < 0 || !Number.isFinite(timeout)) return 1000; return timeout; diff --git a/src/Structures/Track.ts b/packages/discord-player/src/Structures/Track.ts similarity index 85% rename from src/Structures/Track.ts rename to packages/discord-player/src/Structures/Track.ts index 8a46a3064..433e0b7e8 100644 --- a/src/Structures/Track.ts +++ b/packages/discord-player/src/Structures/Track.ts @@ -1,8 +1,8 @@ -import { User, escapeMarkdown, SnowflakeUtil } from "discord.js"; -import { Player } from "../Player"; -import { RawTrackData, TrackJSON } from "../types/types"; -import { Playlist } from "./Playlist"; -import { Queue } from "./Queue"; +import { User, escapeMarkdown, SnowflakeUtil } from 'discord.js'; +import { Player } from '../Player'; +import { RawTrackData, TrackJSON } from '../types/types'; +import { Playlist } from './Playlist'; +import { Queue } from './Queue'; class Track { public player!: Player; @@ -30,7 +30,7 @@ class Track { * @type {Player} * @readonly */ - Object.defineProperty(this, "player", { value: player, enumerable: false }); + Object.defineProperty(this, 'player', { value: player, enumerable: false }); /** * Title of this track @@ -109,17 +109,17 @@ class Track { } private _patch(data: RawTrackData) { - this.title = escapeMarkdown(data.title ?? ""); - this.author = data.author ?? ""; - this.url = data.url ?? ""; - this.thumbnail = data.thumbnail ?? ""; - this.duration = data.duration ?? ""; + this.title = escapeMarkdown(data.title ?? ''); + 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.playlist = data.playlist; // raw - Object.defineProperty(this, "raw", { value: Object.assign({}, { source: data.raw?.source ?? data.source }, data.raw ?? data), enumerable: false }); + Object.defineProperty(this, 'raw', { value: Object.assign({}, { source: data.raw?.source ?? data.source }, data.raw ?? data), enumerable: false }); } /** @@ -127,7 +127,7 @@ class Track { * @type {Queue} */ get queue(): Queue { - return this.player.queues.find((q) => q.tracks.some((ab) => ab.id === this.id)); + return this.player.queues.find((q) => q.tracks.some((ab) => ab.id === this.id))!; } /** @@ -142,7 +142,7 @@ class Track { }; return this.duration - .split(":") + .split(':') .reverse() .map((m, i) => parseInt(m) * times(60, i)) .reduce((a, c) => a + c, 0); @@ -153,7 +153,7 @@ class Track { * @type {TrackSource} */ get source() { - return this.raw.source ?? "arbitrary"; + return this.raw.source ?? 'arbitrary'; } /** diff --git a/src/VoiceInterface/StreamDispatcher.ts b/packages/discord-player/src/VoiceInterface/StreamDispatcher.ts similarity index 83% rename from src/VoiceInterface/StreamDispatcher.ts rename to packages/discord-player/src/VoiceInterface/StreamDispatcher.ts index 3626a7962..38ca735d7 100644 --- a/src/VoiceInterface/StreamDispatcher.ts +++ b/packages/discord-player/src/VoiceInterface/StreamDispatcher.ts @@ -10,14 +10,14 @@ import { VoiceConnection, VoiceConnectionStatus, VoiceConnectionDisconnectReason -} from "@discordjs/voice"; -import { StageChannel, VoiceChannel } from "discord.js"; -import { Duplex, Readable } from "stream"; -import { TypedEmitter as EventEmitter } from "tiny-typed-emitter"; -import Track from "../Structures/Track"; -import { Util } from "../utils/Util"; -import { PlayerError, ErrorStatusCode } from "../Structures/PlayerError"; -import { EqualizerBand, EqualizerStream } from "@discord-player/equalizer"; +} from '@discordjs/voice'; +import { StageChannel, VoiceChannel } from 'discord.js'; +import { Duplex, Readable } from 'stream'; +import { TypedEmitter as EventEmitter } from 'tiny-typed-emitter'; +import Track from '../Structures/Track'; +import { Util } from '../utils/Util'; +import { PlayerError, ErrorStatusCode } from '../Structures/PlayerError'; +import { EqualizerBand, EqualizerStream } from '@discord-player/equalizer'; export interface VoiceEvents { /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -32,7 +32,7 @@ class StreamDispatcher extends EventEmitter { public readonly voiceConnection: VoiceConnection; public readonly audioPlayer: AudioPlayer; public channel: VoiceChannel | StageChannel; - public audioResource?: AudioResource; + public audioResource?: AudioResource | null; private readyLock = false; public paused: boolean; public equalizer: EqualizerStream | null = null; @@ -70,7 +70,7 @@ class StreamDispatcher extends EventEmitter { */ this.paused = false; - this.voiceConnection.on("stateChange", async (_, newState) => { + this.voiceConnection.on('stateChange', async (_, newState) => { if (newState.status === VoiceConnectionStatus.Disconnected) { if (newState.reason === VoiceConnectionDisconnectReason.WebSocketClose && newState.closeCode === 4014) { try { @@ -79,7 +79,7 @@ class StreamDispatcher extends EventEmitter { try { this.voiceConnection.destroy(); } catch (err) { - this.emit("error", err as AudioPlayerError); + this.emit('error', err as AudioPlayerError); } } } else if (this.voiceConnection.rejoinAttempts < 5) { @@ -89,7 +89,7 @@ class StreamDispatcher extends EventEmitter { try { this.voiceConnection.destroy(); } catch (err) { - this.emit("error", err as AudioPlayerError); + this.emit('error', err as AudioPlayerError); } } } else if (newState.status === VoiceConnectionStatus.Destroyed) { @@ -103,7 +103,7 @@ class StreamDispatcher extends EventEmitter { try { this.voiceConnection.destroy(); } catch (err) { - this.emit("error", err as AudioPlayerError); + this.emit('error', err as AudioPlayerError); } } } finally { @@ -112,12 +112,12 @@ class StreamDispatcher extends EventEmitter { } }); - this.audioPlayer.on("stateChange", (oldState, newState) => { + this.audioPlayer.on('stateChange', (oldState, newState) => { if (newState.status === AudioPlayerStatus.Playing) { - if (!this.paused) return void this.emit("start", this.audioResource); + if (!this.paused) return void this.emit('start', this.audioResource!); } else if (newState.status === AudioPlayerStatus.Idle && oldState.status !== AudioPlayerStatus.Idle) { if (!this.paused) { - void this.emit("finish", this.audioResource); + void this.emit('finish', this.audioResource!); if (this.equalizer) { this.equalizer.destroy(); this.equalizer = null; @@ -127,8 +127,8 @@ class StreamDispatcher extends EventEmitter { } }); - this.audioPlayer.on("debug", (m) => void this.emit("debug", m)); - this.audioPlayer.on("error", (error) => void this.emit("error", error)); + this.audioPlayer.on('debug', (m) => void this.emit('debug', m)); + this.audioPlayer.on('error', (error) => void this.emit('error', error)); this.voiceConnection.subscribe(this.audioPlayer); } @@ -140,13 +140,13 @@ class StreamDispatcher extends EventEmitter { */ // eslint-disable-next-line @typescript-eslint/no-explicit-any createStream(src: Readable | Duplex | string, ops?: { type?: StreamType; data?: any; disableVolume?: boolean; disableEqualizer?: boolean; eq?: EqualizerBand[] }) { - if (!ops.disableEqualizer) + if (!ops?.disableEqualizer) this.equalizer = new EqualizerStream({ channels: 1, disabled: false, - bandMultiplier: ops.eq || [] + bandMultiplier: ops?.eq || [] }); - const stream = this.equalizer && typeof src !== "string" ? src.pipe(this.equalizer) : src; + const stream = this.equalizer && typeof src !== 'string' ? src.pipe(this.equalizer) : src; this.audioResource = createAudioResource(stream, { inputType: ops?.type ?? StreamType.Arbitrary, @@ -210,24 +210,24 @@ class StreamDispatcher extends EventEmitter { * @param {AudioResource} [resource=this.audioResource] The audio resource to play * @returns {Promise} */ - async playStream(resource: AudioResource = this.audioResource) { - if (!resource) throw new PlayerError("Audio resource is not available!", ErrorStatusCode.NO_AUDIO_RESOURCE); + async playStream(resource: AudioResource = this.audioResource!) { + if (!resource) throw new PlayerError('Audio resource is not available!', ErrorStatusCode.NO_AUDIO_RESOURCE); if (resource.ended) { - return void this.emit("finish", resource); + return void this.emit('finish', resource); } if (!this.audioResource) this.audioResource = resource; if (this.voiceConnection.state.status !== VoiceConnectionStatus.Ready) { try { await entersState(this.voiceConnection, VoiceConnectionStatus.Ready, this.connectionTimeout); } catch (err) { - return void this.emit("error", err as AudioPlayerError); + return void this.emit('error', err as AudioPlayerError); } } try { this.audioPlayer.play(resource); } catch (e) { - this.emit("error", e as AudioPlayerError); + this.emit('error', e as AudioPlayerError); } return this; diff --git a/src/VoiceInterface/VoiceUtils.ts b/packages/discord-player/src/VoiceInterface/VoiceUtils.ts similarity index 91% rename from src/VoiceInterface/VoiceUtils.ts rename to packages/discord-player/src/VoiceInterface/VoiceUtils.ts index ec7a99d08..0deb6eb9d 100644 --- a/src/VoiceInterface/VoiceUtils.ts +++ b/packages/discord-player/src/VoiceInterface/VoiceUtils.ts @@ -1,6 +1,6 @@ -import { VoiceChannel, StageChannel, Collection, Snowflake } from "discord.js"; -import { DiscordGatewayAdapterCreator, joinVoiceChannel, VoiceConnection } from "@discordjs/voice"; -import { StreamDispatcher } from "./StreamDispatcher"; +import { VoiceChannel, StageChannel, Collection, Snowflake } from 'discord.js'; +import { DiscordGatewayAdapterCreator, joinVoiceChannel, VoiceConnection } from '@discordjs/voice'; +import { StreamDispatcher } from './StreamDispatcher'; class VoiceUtils { public cache: Collection; @@ -31,7 +31,7 @@ class VoiceUtils { } ): Promise { const conn = await this.join(channel, options); - const sub = new StreamDispatcher(conn, channel, options.maxTime); + const sub = new StreamDispatcher(conn, channel, options?.maxTime); this.cache.set(channel.guild.id, sub); return sub; } @@ -53,7 +53,7 @@ class VoiceUtils { guildId: channel.guild.id, channelId: channel.id, adapterCreator: channel.guild.voiceAdapterCreator as unknown as DiscordGatewayAdapterCreator, - selfDeaf: Boolean(options.deaf) + selfDeaf: Boolean(options?.deaf) }); return conn; diff --git a/src/VoiceInterface/VolumeTransformer.ts b/packages/discord-player/src/VoiceInterface/VolumeTransformer.ts similarity index 86% rename from src/VoiceInterface/VolumeTransformer.ts rename to packages/discord-player/src/VoiceInterface/VolumeTransformer.ts index d1f46a36b..3c7446b99 100644 --- a/src/VoiceInterface/VolumeTransformer.ts +++ b/packages/discord-player/src/VoiceInterface/VolumeTransformer.ts @@ -1,9 +1,9 @@ // prism's volume transformer with smooth volume support -import { Transform, TransformOptions } from "stream"; +import { Transform, TransformOptions } from 'stream'; export interface VolumeTransformerOptions extends TransformOptions { - type?: "s16le" | "s16be" | "s32le" | "s32be"; + type?: 's16le' | 's16be' | 's32le' | 's32be'; smoothness?: number; volume?: number; } @@ -13,35 +13,35 @@ export class VolumeTransformer extends Transform { private _smoothing: number; private _bytes: number; private _extremum: number; - private _chunk: Buffer; + private _chunk: Buffer | null; public volume: number; private _targetVolume: number; - public type: "s16le" | "s32le" | "s16be" | "s32be"; + public type: 's16le' | 's32le' | 's16be' | 's32be'; constructor(options: VolumeTransformerOptions = {}) { super(options); switch (options.type) { - case "s16le": + case 's16le': this._readInt = (buffer, index) => buffer.readInt16LE(index); this._writeInt = (buffer, int, index) => buffer.writeInt16LE(int, index); this._bits = 16; break; - case "s16be": + case 's16be': this._readInt = (buffer, index) => buffer.readInt16BE(index); this._writeInt = (buffer, int, index) => buffer.writeInt16BE(int, index); this._bits = 16; break; - case "s32le": + case 's32le': this._readInt = (buffer, index) => buffer.readInt32LE(index); this._writeInt = (buffer, int, index) => buffer.writeInt32LE(int, index); this._bits = 32; break; - case "s32be": + case 's32be': this._readInt = (buffer, index) => buffer.readInt32BE(index); this._writeInt = (buffer, int, index) => buffer.writeInt32BE(int, index); this._bits = 32; break; default: - throw new Error("VolumeTransformer type should be one of s16le, s16be, s32le, s32be"); + throw new Error('VolumeTransformer type should be one of s16le, s16be, s32le, s32be'); } this.type = options.type; this._bytes = this._bits / 8; @@ -78,7 +78,7 @@ export class VolumeTransformer extends Transform { const { _bytes, _extremum } = this; - chunk = this._chunk = Buffer.concat([this._chunk, chunk]); + chunk = this._chunk = Buffer.concat([this._chunk!, chunk]); if (chunk.length < _bytes) return done(); const complete = Math.floor(chunk.length / _bytes) * _bytes; @@ -93,14 +93,14 @@ export class VolumeTransformer extends Transform { return done(); } - _destroy(err: Error, cb: (error: Error) => void) { + _destroy(err: Error, cb: (error: Error | null) => void) { super._destroy(err, cb); this._chunk = null; } setVolume(volume: number) { if (Number.isNaN(volume)) volume = 1; - if (typeof volume !== "number") volume = Number(volume); + if (typeof volume !== 'number') volume = Number(volume); if (!Number.isFinite(volume)) volume = volume < 0 ? 0 : 1; this._targetVolume = volume; if (this._smoothing <= 0) this.volume = volume; @@ -131,7 +131,7 @@ export class VolumeTransformer extends Transform { } smoothingEnabled() { - return typeof this._smoothing === "number" && !Number.isNaN(this._smoothing) && Number.isFinite(this._smoothing) && this._smoothing > 0; + return typeof this._smoothing === 'number' && !Number.isNaN(this._smoothing) && Number.isFinite(this._smoothing) && this._smoothing > 0; } get hasSmoothness() { diff --git a/packages/discord-player/src/index.ts b/packages/discord-player/src/index.ts new file mode 100644 index 000000000..014b04538 --- /dev/null +++ b/packages/discord-player/src/index.ts @@ -0,0 +1,25 @@ +// try applying smooth volume patch on load +import './smoothVolume'; +import { version as djsVersion } from 'discord.js'; + +export { AudioFilters } from './utils/AudioFilters'; +export { ExtractorModel } from './Structures/ExtractorModel'; +export { Playlist } from './Structures/Playlist'; +export { Player } from './Player'; +export { PlayerError, ErrorStatusCode } from './Structures/PlayerError'; +export { QueryResolver } from './utils/QueryResolver'; +export { Queue } from './Structures/Queue'; +export { Track } from './Structures/Track'; +export { VoiceUtils } from './VoiceInterface/VoiceUtils'; +export { VoiceEvents, StreamDispatcher } from './VoiceInterface/StreamDispatcher'; +export * from './VoiceInterface/VolumeTransformer'; +export { Util } from './utils/Util'; +export * from './types/types'; +export * from './utils/FFmpegStream'; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +export const version: string = require(`${__dirname}/../package.json`).version; + +if (!djsVersion.startsWith('14')) { + process.emitWarning(`Discord.js v${djsVersion} is incompatible with Discord Player v${version}! Please use >=v14.x of Discord.js`); +} diff --git a/packages/discord-player/src/smoothVolume.ts b/packages/discord-player/src/smoothVolume.ts new file mode 100644 index 000000000..83ee5da43 --- /dev/null +++ b/packages/discord-player/src/smoothVolume.ts @@ -0,0 +1,14 @@ +import { VolumeTransformer as VolumeTransformerMock } from './VoiceInterface/VolumeTransformer'; + +if (!('DISABLE_DISCORD_PLAYER_SMOOTH_VOLUME' in process.env)) { + try { + // eslint-disable-next-line + const mod = require('prism-media') as typeof import('prism-media') & { VolumeTransformer: typeof VolumeTransformerMock }; + + if (typeof mod.VolumeTransformer.hasSmoothing !== 'boolean') { + Reflect.set(mod, 'VolumeTransformer', VolumeTransformerMock); + } + } catch { + /* do nothing */ + } +} diff --git a/src/types/types.ts b/packages/discord-player/src/types/types.ts similarity index 95% rename from src/types/types.ts rename to packages/discord-player/src/types/types.ts index 49bdb1952..27b357276 100644 --- a/src/types/types.ts +++ b/packages/discord-player/src/types/types.ts @@ -1,11 +1,11 @@ -import { Snowflake, User, UserResolvable, VoiceState } from "discord.js"; -import { Readable, Duplex } from "stream"; -import { Queue } from "../Structures/Queue"; -import Track from "../Structures/Track"; -import { Playlist } from "../Structures/Playlist"; -import { StreamDispatcher } from "../VoiceInterface/StreamDispatcher"; -import { downloadOptions } from "ytdl-core"; -import { EqualizerBand } from "@discord-player/equalizer"; +import { Snowflake, User, UserResolvable, VoiceState } from 'discord.js'; +import { Readable, Duplex } from 'stream'; +import { Queue } from '../Structures/Queue'; +import Track from '../Structures/Track'; +import { Playlist } from '../Structures/Playlist'; +import { StreamDispatcher } from '../VoiceInterface/StreamDispatcher'; +import { downloadOptions } from 'ytdl-core'; +import { EqualizerBand } from '@discord-player/equalizer'; export type FiltersName = keyof QueueFilters; @@ -21,7 +21,7 @@ export interface QueueFilters { bassboost_low?: boolean; bassboost?: boolean; bassboost_high?: boolean; - "8D"?: boolean; + '8D'?: boolean; vaporwave?: boolean; nightcore?: boolean; phaser?: boolean; @@ -61,7 +61,7 @@ export interface QueueFilters { * - arbitrary * @typedef {string} TrackSource */ -export type TrackSource = "soundcloud" | "youtube" | "spotify" | "arbitrary"; +export type TrackSource = 'soundcloud' | 'youtube' | 'spotify' | 'arbitrary'; /** * @typedef {object} RawTrackData @@ -197,7 +197,7 @@ export interface ExtractorModelData { title: string; description: string; thumbnail: string; - type: "album" | "playlist"; + type: 'album' | 'playlist'; source: TrackSource; author: { name: string; @@ -423,7 +423,7 @@ export interface PlaylistInitData { title: string; description: string; thumbnail: string; - type: "album" | "playlist"; + type: 'album' | 'playlist'; source: TrackSource; author: { name: string; @@ -481,7 +481,7 @@ export interface PlaylistJSON { title: string; description: string; thumbnail: string; - type: "album" | "playlist"; + type: 'album' | 'playlist'; source: TrackSource; author: { name: string; diff --git a/src/utils/AudioFilters.ts b/packages/discord-player/src/utils/AudioFilters.ts similarity index 57% rename from src/utils/AudioFilters.ts rename to packages/discord-player/src/utils/AudioFilters.ts index 6229f7a7e..c1d2d2696 100644 --- a/src/utils/AudioFilters.ts +++ b/packages/discord-player/src/utils/AudioFilters.ts @@ -1,4 +1,4 @@ -import { FiltersName } from "../types/types"; +import { FiltersName } from '../types/types'; const bass = (g: number) => `bass=g=${g}:f=110:w=0.3`; @@ -11,36 +11,36 @@ class AudioFilters { bassboost_low: bass(15), bassboost: bass(20), bassboost_high: bass(30), - "8D": "apulsator=hz=0.09", - vaporwave: "aresample=48000,asetrate=48000*0.8", - nightcore: "aresample=48000,asetrate=48000*1.25", - phaser: "aphaser=in_gain=0.4", - tremolo: "tremolo", - vibrato: "vibrato=f=6.5", - reverse: "areverse", - treble: "treble=g=5", - normalizer2: "dynaudnorm=g=101", - normalizer: "acompressor", - surrounding: "surround", - pulsator: "apulsator=hz=1", - subboost: "asubboost", - karaoke: "stereotools=mlev=0.03", - flanger: "flanger", - gate: "agate", - haas: "haas", - mcompand: "mcompand", - mono: "pan=mono|c0=.5*c0+.5*c1", - mstlr: "stereotools=mode=ms>lr", - mstrr: "stereotools=mode=ms>rr", - compressor: "compand=points=-80/-105|-62/-80|-15.4/-15.4|0/-12|20/-7.6", - expander: "compand=attacks=0:points=-80/-169|-54/-80|-49.5/-64.6|-41.1/-41.1|-25.8/-15|-10.8/-4.5|0/0|20/8.3", - softlimiter: "compand=attacks=0:points=-80/-80|-12.4/-12.4|-6/-8|0/-6.8|20/-2.8", - chorus: "chorus=0.7:0.9:55:0.4:0.25:2", - chorus2d: "chorus=0.6:0.9:50|60:0.4|0.32:0.25|0.4:2|1.3", - chorus3d: "chorus=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3", - fadein: "afade=t=in:ss=0:d=10", + '8D': 'apulsator=hz=0.09', + vaporwave: 'aresample=48000,asetrate=48000*0.8', + nightcore: 'aresample=48000,asetrate=48000*1.25', + phaser: 'aphaser=in_gain=0.4', + tremolo: 'tremolo', + vibrato: 'vibrato=f=6.5', + reverse: 'areverse', + treble: 'treble=g=5', + normalizer2: 'dynaudnorm=g=101', + normalizer: 'acompressor', + surrounding: 'surround', + pulsator: 'apulsator=hz=1', + subboost: 'asubboost', + karaoke: 'stereotools=mlev=0.03', + flanger: 'flanger', + gate: 'agate', + haas: 'haas', + mcompand: 'mcompand', + mono: 'pan=mono|c0=.5*c0+.5*c1', + mstlr: 'stereotools=mode=ms>lr', + mstrr: 'stereotools=mode=ms>rr', + compressor: 'compand=points=-80/-105|-62/-80|-15.4/-15.4|0/-12|20/-7.6', + expander: 'compand=attacks=0:points=-80/-169|-54/-80|-49.5/-64.6|-41.1/-41.1|-25.8/-15|-10.8/-4.5|0/0|20/8.3', + softlimiter: 'compand=attacks=0:points=-80/-80|-12.4/-12.4|-6/-8|0/-6.8|20/-2.8', + chorus: 'chorus=0.7:0.9:55:0.4:0.25:2', + chorus2d: 'chorus=0.6:0.9:50|60:0.4|0.32:0.25|0.4:2|1.3', + chorus3d: 'chorus=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3', + fadein: 'afade=t=in:ss=0:d=10', dim: `afftfilt="'real=re * (1-clip((b/nb)*b,0,1))':imag='im * (1-clip((b/nb)*b,0,1))'"`, - earrape: "channelsplit,sidechaingate=level_in=64" + earrape: 'channelsplit,sidechaingate=level_in=64' }; public static get(name: K) { @@ -67,7 +67,7 @@ class AudioFilters { } public static toString() { - return this.names.map((m) => (this as any)[m]).join(","); // eslint-disable-line @typescript-eslint/no-explicit-any + return this.names.map((m) => (this as any)[m]).join(','); // eslint-disable-line @typescript-eslint/no-explicit-any } /** @@ -78,9 +78,9 @@ class AudioFilters { public static create(filters?: K[]) { if (!filters || !Array.isArray(filters)) return this.toString(); return filters - .filter((predicate) => typeof predicate === "string") + .filter((predicate) => typeof predicate === 'string') .map((m) => this.get(m)) - .join(","); + .join(','); } /** diff --git a/src/utils/FFmpegStream.ts b/packages/discord-player/src/utils/FFmpegStream.ts similarity index 74% rename from src/utils/FFmpegStream.ts rename to packages/discord-player/src/utils/FFmpegStream.ts index 9f49b283a..fa1d88041 100644 --- a/src/utils/FFmpegStream.ts +++ b/packages/discord-player/src/utils/FFmpegStream.ts @@ -1,5 +1,5 @@ -import { FFmpeg } from "prism-media"; -import type { Duplex, Readable } from "stream"; +import { FFmpeg } from 'prism-media'; +import type { Duplex, Readable } from 'stream'; export interface FFmpegStreamOptions { fmt?: string; @@ -40,18 +40,18 @@ export function FFMPEG_ARGS_PIPED(fmt?: string) { * @param options FFmpeg stream options */ export function createFFmpegStream(stream: Readable | Duplex | string, options?: FFmpegStreamOptions) { - if (options.skip && typeof stream !== "string") return stream; + if (options?.skip && typeof stream !== 'string') return stream; options ??= {}; - const args = typeof stream === "string" ? FFMPEG_ARGS_STRING(stream, options.fmt) : FFMPEG_ARGS_PIPED(options.fmt); + const args = typeof stream === 'string' ? FFMPEG_ARGS_STRING(stream, options.fmt) : FFMPEG_ARGS_PIPED(options.fmt); - if (!Number.isNaN(options.seek)) args.unshift("-ss", String(options.seek)); + if (!Number.isNaN(options.seek)) args.unshift('-ss', String(options.seek)); if (Array.isArray(options.encoderArgs)) args.push(...options.encoderArgs); const transcoder = new FFmpeg({ shell: false, args }); - transcoder.on("close", () => transcoder.destroy()); + transcoder.on('close', () => transcoder.destroy()); - if (typeof stream !== "string") { - stream.on("error", () => transcoder.destroy()); + if (typeof stream !== 'string') { + stream.on('error', () => transcoder.destroy()); stream.pipe(transcoder); } diff --git a/src/utils/QueryResolver.ts b/packages/discord-player/src/utils/QueryResolver.ts similarity index 84% rename from src/utils/QueryResolver.ts rename to packages/discord-player/src/utils/QueryResolver.ts index 18decc011..ad18aa82b 100644 --- a/src/utils/QueryResolver.ts +++ b/packages/discord-player/src/utils/QueryResolver.ts @@ -1,9 +1,9 @@ -import { validateID, validateURL } from "ytdl-core"; -import { YouTube } from "youtube-sr"; -import { QueryType } from "../types/types"; +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"; +import { validateURL as SoundcloudValidateURL } from 'soundcloud-scraper'; // #region scary things below *sigh* const spotifySongRegex = /https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:track\/|\?uri=spotify:track:)((\w|-){22})/; @@ -28,8 +28,8 @@ class QueryResolver { * @returns {QueryType} */ static resolve(query: string): QueryType { - if (SoundcloudValidateURL(query, "track")) return QueryType.SOUNDCLOUD_TRACK; - if (SoundcloudValidateURL(query, "playlist") || query.includes("/sets/")) return QueryType.SOUNDCLOUD_PLAYLIST; + if (SoundcloudValidateURL(query, 'track')) return QueryType.SOUNDCLOUD_TRACK; + if (SoundcloudValidateURL(query, 'playlist') || query.includes('/sets/')) return QueryType.SOUNDCLOUD_PLAYLIST; if (YouTube.isPlaylist(query)) return QueryType.YOUTUBE_PLAYLIST; if (validateID(query) || validateURL(query)) return QueryType.YOUTUBE_VIDEO; if (spotifySongRegex.test(query)) return QueryType.SPOTIFY_SONG; @@ -48,10 +48,10 @@ class QueryResolver { * @param {string} query The query * @returns {string} */ - static getVimeoID(query: string): string { + static getVimeoID(query: string): string | null | undefined { return QueryResolver.resolve(query) === QueryType.VIMEO ? query - .split("/") + .split('/') .filter((x) => !!x) .pop() : null; diff --git a/src/utils/Util.ts b/packages/discord-player/src/utils/Util.ts similarity index 84% rename from src/utils/Util.ts rename to packages/discord-player/src/utils/Util.ts index 2444b0b67..392aa8886 100644 --- a/src/utils/Util.ts +++ b/packages/discord-player/src/utils/Util.ts @@ -1,6 +1,6 @@ -import { StageChannel, VoiceChannel } from "discord.js"; -import { TimeData } from "../types/types"; -import { setTimeout } from "timers/promises"; +import { StageChannel, VoiceChannel } from 'discord.js'; +import { TimeData } from '../types/types'; +import { setTimeout } from 'timers/promises'; class Util { /** @@ -16,7 +16,7 @@ class Util { static durationString(durObj: Record) { return Object.values(durObj) .map((m) => (isNaN(m) ? 0 : m)) - .join(":"); + .join(':'); } /** @@ -43,15 +43,15 @@ class Util { */ static buildTimeCode(duration: TimeData) { const items = Object.keys(duration); - const required = ["days", "hours", "minutes", "seconds"]; + const required = ['days', 'hours', 'minutes', 'seconds']; const parsed = items.filter((x) => required.includes(x)).map((m) => duration[m as keyof TimeData]); const final = parsed .slice(parsed.findIndex((x) => x !== 0)) - .map((x) => x.toString().padStart(2, "0")) - .join(":"); + .map((x) => x.toString().padStart(2, '0')) + .join(':'); - return final.length <= 3 ? `0:${final.padStart(2, "0") || 0}` : final; + return final.length <= 3 ? `0:${final.padStart(2, '0') || 0}` : final; } /** @@ -61,7 +61,7 @@ class Util { */ // eslint-disable-next-line @typescript-eslint/no-explicit-any static last(arr: T[]): T { - if (!Array.isArray(arr)) return; + if (!Array.isArray(arr)) return arr; return arr[arr.length - 1]; } @@ -99,8 +99,8 @@ class Util { static noop() {} // eslint-disable-line @typescript-eslint/no-empty-function static async getFetch() { - if ("fetch" in globalThis) return globalThis.fetch; - for (const lib of ["node-fetch", "undici"]) { + if ('fetch' in globalThis) return globalThis.fetch; + for (const lib of ['node-fetch', 'undici']) { try { return await import(lib).then((res) => res.fetch || res.default?.fetch || res.default); } catch { diff --git a/packages/discord-player/tsconfig.json b/packages/discord-player/tsconfig.json new file mode 100644 index 000000000..b9167ca59 --- /dev/null +++ b/packages/discord-player/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "@discord-player/tsconfig/base.json", + "include": ["src/**/*", "../../test"] +} \ No newline at end of file diff --git a/packages/discord-player/tsup.config.ts b/packages/discord-player/tsup.config.ts new file mode 100644 index 000000000..e8bba970a --- /dev/null +++ b/packages/discord-player/tsup.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + clean: true, + bundle: true, + dts: true, + format: ['cjs', 'esm'], + keepNames: true, + minify: false, + entry: ['./src/index.ts'], + skipNodeModulesBundle: true, + sourcemap: true, + target: 'ES2020', + silent: true +}); diff --git a/packages/equalizer/LICENSE b/packages/equalizer/LICENSE new file mode 100644 index 000000000..eceb839f1 --- /dev/null +++ b/packages/equalizer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Androz2091 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/equalizer/README.md b/packages/equalizer/README.md new file mode 100644 index 000000000..e1929b09a --- /dev/null +++ b/packages/equalizer/README.md @@ -0,0 +1,52 @@ +# `@discord-player/equalizer` + +This library implements Lavaplayer's 15 Band PCM Equalizer & biquad utilities. + +## Installation + +```sh +$ npm install --save @discord-player/equalizer +``` + +## Example + +#### Equalizer + +```js +import { EqualizerStream } from '@discord-player/equalizer'; + +// initialize 15 band equalizer stream +const equalizer = new EqualizerStream(); + +// set equalizer bands, in this case add some bass +equalizer.setEQ([ + { band: 0, gain: 0.25 }, + { band: 1, gain: 0.25 }, + { band: 2, gain: 0.25 } +]); + +// input stream +const input = getPCMAudioSomehow(); + +// pipe input stream to equalizer +const output = input.pipe(equalizer); + +// now do something with the output stream +``` + +#### Biquad + +```js +import { BiquadFilter, Coefficients, FilterType, Q_BUTTERWORTH, Frequency } from '@discord-player/equalizer'; + +const f0 = new Frequency(10).hz(); +const fs = new Frequency(1).khz(); + +const coeffs = Coefficients.from(FilterType.LowPass, fs, f0, Q_BUTTERWORTH); +const biquad = new BiquadFilter(coeffs); + +const input = [0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]; + +const out1 = input.map((i) => biquad.run(i)); +const out2 = input.map((i) => biquad.runTransposed(i)); +``` \ No newline at end of file diff --git a/packages/equalizer/package.json b/packages/equalizer/package.json new file mode 100644 index 000000000..697205d3c --- /dev/null +++ b/packages/equalizer/package.json @@ -0,0 +1,45 @@ +{ + "name": "@discord-player/equalizer", + "version": "0.1.2", + "description": "PCM Equalizer implementation for Discord Player", + "keywords": [ + "discord-player", + "pcm", + "equalizer", + "music", + "bot", + "discord.js", + "javascript", + "voip", + "lavalink", + "lavaplayer" + ], + "author": "Androz2091 ", + "homepage": "https://discord-player.js.org", + "license": "MIT", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "directories": { + "dist": "dist", + "src": "src" + }, + "files": [ + "dist" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/Androz2091/discord-player.git" + }, + "scripts": { + "build": "tsup", + "build:check": "tsc --noEmit" + }, + "bugs": { + "url": "https://github.com/Androz2091/discord-player/issues" + }, + "devDependencies": { + "@discord-player/tsconfig": "*", + "tsup": "^6.2.2" + } +} \ No newline at end of file diff --git a/packages/equalizer/src/biquad/Biquad.ts b/packages/equalizer/src/biquad/Biquad.ts new file mode 100644 index 000000000..74677f776 --- /dev/null +++ b/packages/equalizer/src/biquad/Biquad.ts @@ -0,0 +1,66 @@ +import { Coefficients, FilterType } from './Coefficients'; + +export interface BiquadSetFilterProps { + f0: number; + fs: number; + Q: number; + gain?: number; +} + +export class BiquadFilter { + public x1 = 0.0; + public x2 = 0.0; + public y1 = 0.0; + public y2 = 0.0; + public s1 = 0.0; + public s2 = 0.0; + + public constructor(public coefficients: Coefficients) {} + + public setFilter(filter: FilterType, options: BiquadSetFilterProps) { + const coefficients = Coefficients.from(filter, options.fs, options.f0, options.Q, options.gain); + + this.update(coefficients); + } + + public update(coefficients: Coefficients) { + this.coefficients = coefficients; + } + + public replace(coefficients: Coefficients) { + this.coefficients = coefficients; + } + + public reset() { + this.x1 = 0.0; + this.x2 = 0.0; + this.y1 = 0.0; + this.y2 = 0.0; + this.s1 = 0.0; + this.s2 = 0.0; + } + + public run(input: number) { + const { a1, a2, b0, b1, b2 } = this.coefficients; + + const out = b0 * input + b1 * this.x1 + b2 * this.x2 - a1 * this.y1 - a2 * this.y2; + + this.x2 = this.x1; + this.x1 = input; + this.y2 = this.y1; + this.y1 = out; + + return out; + } + + public runTransposed(input: number) { + const { a1, a2, b0, b1, b2 } = this.coefficients; + + const out = this.s1 + b0 * input; + + this.s1 = this.s2 + b1 * input - a1 * out; + this.s2 = b2 * input - a2 * out; + + return out; + } +} diff --git a/packages/equalizer/src/biquad/Coefficients.ts b/packages/equalizer/src/biquad/Coefficients.ts new file mode 100644 index 000000000..759650206 --- /dev/null +++ b/packages/equalizer/src/biquad/Coefficients.ts @@ -0,0 +1,254 @@ +export enum FilterType { + SinglePoleLowPassApprox, + SinglePoleLowPass, + LowPass, + HighPass, + BandPass, + Notch, + AllPass, + LowShelf, + HighShelf, + PeakingEQ +} + +interface CoefficientsInit { + a1: number; + a2: number; + b0: number; + b1: number; + b2: number; +} + +export const Q_BUTTERWORTH = Math.SQRT1_2; + +export class Coefficients { + // Denominator coefficients + public a1: number = 0; + public a2: number = 0; + + // Nominator coefficients + public b0: number = 0; + public b1: number = 0; + public b2: number = 0; + + public constructor(data?: CoefficientsInit) { + if (data) { + this.a1 = data.a1; + this.a2 = data.a2; + this.b0 = data.b0; + this.b1 = data.b1; + this.b2 = data.b2; + } + } + + public static from(filter: FilterType, samplingFreq: number, cutoffFreq: number, Q: number, dbGain = 0) { + if (2.0 * cutoffFreq > samplingFreq) { + throw new Error(`Cutoff frequency is too big!`); + } + + if (Q < 0) { + throw new Error(`Q may not be negative`); + } + + const omega = (2.0 * Math.PI * cutoffFreq) / samplingFreq; + + switch (filter) { + case FilterType.SinglePoleLowPassApprox: { + const alpha = omega / (omega + 1.0); + + return new Coefficients({ + a1: alpha - 1.0, + a2: 0.0, + b0: alpha, + b1: 0.0, + b2: 0.0 + }); + } + case FilterType.SinglePoleLowPass: { + const omega_t = Math.tan(omega / 2.0); + const a0 = 1.0 + omega_t; + + return new Coefficients({ + a1: (omega_t - 1.0) / a0, + a2: 0.0, + b0: omega_t / a0, + b1: omega_t / a0, + b2: 0.0 + }); + } + case FilterType.LowPass: { + const omega_s = Math.sin(omega); + const omega_c = Math.cos(omega); + const alpha = omega_s / (2.0 * Q); + + const b0 = (1.0 - omega_c) * 0.5; + const b1 = 1.0 - omega_c; + const b2 = (1.0 - omega_c) * 0.5; + const a0 = 1.0 + alpha; + const a1 = -2.0 * omega_c; + const a2 = 1.0 - alpha; + + const div = 1.0 / a0; + + return new Coefficients({ + a1: a1 * div, + a2: a2 * div, + b0: b0 * div, + b1: b1 * div, + b2: b2 * div + }); + } + case FilterType.HighPass: { + const omega_s = Math.sin(omega); + const omega_c = Math.cos(omega); + const alpha = omega_s / (2.0 * Q); + + const b0 = (1.0 + omega_c) * 0.5; + const b1 = -(1.0 + omega_c); + const b2 = (1.0 + omega_c) * 0.5; + const a0 = 1.0 + alpha; + const a1 = -2.0 * omega_c; + const a2 = 1.0 - alpha; + + const div = 1.0 / a0; + + return new Coefficients({ + a1: a1 * div, + a2: a2 * div, + b0: b0 * div, + b1: b1 * div, + b2: b2 * div + }); + } + case FilterType.Notch: { + const omega_s = Math.sin(omega); + const omega_c = Math.cos(omega); + const alpha = omega_s / (2.0 * Q); + + const b0 = 1.0; + const b1 = -2.0 * omega_c; + const b2 = 1.0; + const a0 = 1.0 + alpha; + const a1 = -2.0 * omega_c; + const a2 = 1.0 - alpha; + + const div = 1.0 / a0; + + return new Coefficients({ + a1: a1 * div, + a2: a2 * div, + b0: b0 * div, + b1: b1 * div, + b2: b2 * div + }); + } + case FilterType.BandPass: { + const omega_s = Math.sin(omega); + const omega_c = Math.cos(omega); + const alpha = omega_s / (2.0 * Q); + + let b0 = omega_s / 2.0; + let b1 = 0; + let b2 = -(omega_s / 2.0); + let a0 = 1.0 + alpha; + let a1 = -2.0 * omega_c; + let a2 = 1.0 - alpha; + + let div = 1.0 / a0; + + return new Coefficients({ + a1: a1 * div, + a2: a2 * div, + b0: b0 * div, + b1: b1 * div, + b2: b2 * div + }); + } + case FilterType.AllPass: { + const omega_s = Math.sin(omega); + const omega_c = Math.cos(omega); + const alpha = omega_s / (2.0 * Q); + + let b0 = 1.0 - alpha; + let b1 = -2.0 * omega_c; + let b2 = 1.0 + alpha; + let a0 = 1.0 + alpha; + let a1 = -2.0 * omega_c; + let a2 = 1.0 - alpha; + + return new Coefficients({ + a1: a1 / a0, + a2: a2 / a0, + b0: b0 / a0, + b1: b1 / a0, + b2: b2 / a0 + }); + } + case FilterType.LowShelf: { + let a = Math.pow(10.0, dbGain / 40.0); + const omega_s = Math.sin(omega); + const omega_c = Math.cos(omega); + const alpha = omega_s / (2.0 * Q); + + let b0 = a * (a + 1.0 - (a - 1.0) * omega_c + 2.0 * alpha * Math.sqrt(a)); + let b1 = 2.0 * a * (a - 1.0 - (a + 1.0) * omega_c); + let b2 = a * (a + 1.0 - (a - 1.0) * omega_c - 2.0 * alpha * Math.sqrt(a)); + let a0 = a + 1.0 + (a - 1.0) * omega_c + 2.0 * alpha * Math.sqrt(a); + let a1 = -2.0 * (a - 1.0 + (a + 1.0) * omega_c); + let a2 = a + 1.0 + (a - 1.0) * omega_c - 2.0 * alpha * Math.sqrt(a); + + return new Coefficients({ + a1: a1 / a0, + a2: a2 / a0, + b0: b0 / a0, + b1: b1 / a0, + b2: b2 / a0 + }); + } + case FilterType.HighShelf: { + let a = Math.pow(10.0, dbGain / 40.0); + const omega_s = Math.sin(omega); + const omega_c = Math.cos(omega); + const alpha = omega_s / (2.0 * Q); + + let b0 = a * (a + 1.0 + (a - 1.0) * omega_c + 2.0 * alpha * Math.sqrt(a)); + let b1 = -2.0 * a * (a - 1.0 + (a + 1.0) * omega_c); + let b2 = a * (a + 1.0 + (a - 1.0) * omega_c - 2.0 * alpha * Math.sqrt(a)); + let a0 = a + 1.0 - (a - 1.0) * omega_c + 2.0 * alpha * Math.sqrt(a); + let a1 = 2.0 * (a - 1.0 - (a + 1.0) * omega_c); + let a2 = a + 1.0 - (a - 1.0) * omega_c - 2.0 * alpha * Math.sqrt(a); + + return new Coefficients({ + a1: a1 / a0, + a2: a2 / a0, + b0: b0 / a0, + b1: b1 / a0, + b2: b2 / a0 + }); + } + case FilterType.PeakingEQ: { + let a = Math.pow(10.0, dbGain / 40.0); + const omega_s = Math.sin(omega); + const omega_c = Math.cos(omega); + const alpha = omega_s / (2.0 * Q); + + let b0 = 1.0 + alpha * a; + let b1 = -2.0 * omega_c; + let b2 = 1.0 - alpha * a; + let a0 = 1.0 + alpha / a; + let a1 = -2.0 * omega_c; + let a2 = 1.0 - alpha / a; + + return new Coefficients({ + a1: a1 / a0, + a2: a2 / a0, + b0: b0 / a0, + b1: b1 / a0, + b2: b2 / a0 + }); + } + default: + throw new TypeError('Invalid filter type'); + } + } +} diff --git a/packages/equalizer/src/biquad/index.ts b/packages/equalizer/src/biquad/index.ts new file mode 100644 index 000000000..c8dddff3e --- /dev/null +++ b/packages/equalizer/src/biquad/index.ts @@ -0,0 +1,2 @@ +export * from './Biquad'; +export * from './Coefficients'; diff --git a/packages/equalizer/src/equalizer/ChannelProcessor.ts b/packages/equalizer/src/equalizer/ChannelProcessor.ts new file mode 100644 index 000000000..d9738d0ef --- /dev/null +++ b/packages/equalizer/src/equalizer/ChannelProcessor.ts @@ -0,0 +1,61 @@ +import { Equalizer } from './Equalizer'; + +export class ChannelProcessor { + public history: number[]; + public bandMultipliers: number[]; + public current: number; + public m1: number; + public m2: number; + public _extremum = Math.pow(2, 16 - 1); + + public constructor(bandMultipliers: number[]) { + this.history = new Array(Equalizer.BAND_COUNT * 6).fill(0); + this.bandMultipliers = bandMultipliers; + this.current = 0; + this.m1 = 2; + this.m2 = 1; + } + + public process(samples: Buffer) { + const endIndex = Math.floor(samples.length / 2) * 2; + for (let sampleIndex = 0; sampleIndex < endIndex; sampleIndex += 2) { + const sample = samples.readInt16LE(sampleIndex); + let result = sample * 0.25; + + for (let bandIndex = 0; bandIndex < Equalizer.BAND_COUNT; bandIndex++) { + const x = bandIndex * 6; + const y = x + 3; + + const coefficients = Equalizer.Coefficients48000[bandIndex]; + + const bandResult = coefficients.alpha * (sample - this.history[x + this.m2]) + coefficients.gamma * this.history[y + this.m1] - coefficients.beta * this.history[y + this.m2]; + + this.history[x + this.current] = sample; + this.history[y + this.current] = bandResult; + + result += bandResult * this.bandMultipliers[bandIndex]; + } + + const val = Math.min(this._extremum - 1, Math.max(-this._extremum, result * 4.0)); + samples.writeInt16LE(val, sampleIndex); + + if (++this.current === 3) { + this.current = 0; + } + + if (++this.m1 === 3) { + this.m1 = 0; + } + + if (++this.m2 === 3) { + this.m2 = 0; + } + } + + return samples; + } + + public reset() { + this.history.fill(0.0); + } +} diff --git a/packages/equalizer/src/equalizer/Coefficients.ts b/packages/equalizer/src/equalizer/Coefficients.ts new file mode 100644 index 000000000..e50587f8e --- /dev/null +++ b/packages/equalizer/src/equalizer/Coefficients.ts @@ -0,0 +1,21 @@ +export class EqualizerCoefficients { + public constructor(public beta: number, public alpha: number, public gamma: number) {} + + public setBeta(v: number) { + this.beta = v; + } + + public setAlpha(v: number) { + this.alpha = v; + } + + public setGamma(v: number) { + this.gamma = v; + } + + public toJSON() { + const { alpha, beta, gamma } = this; + + return { alpha, beta, gamma }; + } +} diff --git a/packages/equalizer/src/equalizer/Equalizer.ts b/packages/equalizer/src/equalizer/Equalizer.ts new file mode 100644 index 000000000..d0bb0ce87 --- /dev/null +++ b/packages/equalizer/src/equalizer/Equalizer.ts @@ -0,0 +1,46 @@ +import { ChannelProcessor } from './ChannelProcessor'; +import { EqualizerCoefficients } from './Coefficients'; +import { EqualizerConfiguration } from './EqualizerConfiguration'; + +export class Equalizer extends EqualizerConfiguration { + public static BAND_COUNT = 15 as const; + public static SAMPLE_RATE = 48000 as const; + public static Coefficients48000 = [ + new EqualizerCoefficients(9.9847546664e-1, 7.6226668143e-4, 1.9984647656), + new EqualizerCoefficients(9.9756184654e-1, 1.2190767289e-3, 1.9975344645), + new EqualizerCoefficients(9.9616261379e-1, 1.9186931041e-3, 1.9960947369), + new EqualizerCoefficients(9.9391578543e-1, 3.0421072865e-3, 1.9937449618), + new EqualizerCoefficients(9.9028307215e-1, 4.8584639242e-3, 1.9898465702), + new EqualizerCoefficients(9.8485897264e-1, 7.5705136795e-3, 1.9837962543), + new EqualizerCoefficients(9.7588512657e-1, 1.2057436715e-2, 1.9731772447), + new EqualizerCoefficients(9.6228521814e-1, 1.8857390928e-2, 1.9556164694), + new EqualizerCoefficients(9.4080933132e-1, 2.9595334338e-2, 1.9242054384), + new EqualizerCoefficients(9.0702059196e-1, 4.6489704022e-2, 1.8653476166), + new EqualizerCoefficients(8.5868004289e-1, 7.0659978553e-2, 1.7600401337), + new EqualizerCoefficients(7.8409610788e-1, 1.0795194606e-1, 1.5450725522), + new EqualizerCoefficients(6.8332861002e-1, 1.5833569499e-1, 1.1426447155), + new EqualizerCoefficients(5.5267518228e-1, 2.2366240886e-1, 4.0186190803e-1), + new EqualizerCoefficients(4.1811888447e-1, 2.9094055777e-1, -7.0905944223e-1) + ]; + public channels: ChannelProcessor[] = []; + public channelCount: number; + + public constructor(channelCount: number, bandMultipliers: number[]) { + super(bandMultipliers); + this.channelCount = channelCount; + this.channels = this.createChannelProcessor(); + } + + public createChannelProcessor() { + return Array.from({ length: this.channelCount }, () => { + return new ChannelProcessor(this.bandMultipliers); + }); + } + + public process(input: Buffer[]) { + return this.channels.map((c, i) => { + const inp = input[i]; + return c.process(inp); + }); + } +} diff --git a/packages/equalizer/src/equalizer/EqualizerConfiguration.ts b/packages/equalizer/src/equalizer/EqualizerConfiguration.ts new file mode 100644 index 000000000..f709a3129 --- /dev/null +++ b/packages/equalizer/src/equalizer/EqualizerConfiguration.ts @@ -0,0 +1,21 @@ +export class EqualizerConfiguration { + public constructor(public bandMultipliers: number[]) {} + + public setGain(band: number, value: number) { + if (this.isValidBand(band)) { + this.bandMultipliers[band] = Math.max(Math.min(value, 1.0), -0.25); + } + } + + public getGain(band: number) { + if (this.isValidBand(band)) { + return this.bandMultipliers[band]; + } else { + return 0.0; + } + } + + public isValidBand(band: number) { + return band >= 0 && band < this.bandMultipliers.length; + } +} diff --git a/packages/equalizer/src/equalizer/EqualizerStream.ts b/packages/equalizer/src/equalizer/EqualizerStream.ts new file mode 100644 index 000000000..dbc28cedb --- /dev/null +++ b/packages/equalizer/src/equalizer/EqualizerStream.ts @@ -0,0 +1,92 @@ +import { Transform, TransformCallback, TransformOptions } from 'stream'; +import { Equalizer } from './Equalizer'; + +interface EqualizerStreamOptions extends TransformOptions { + bandMultiplier?: EqualizerBand[]; + disabled?: boolean; + channels?: number; +} + +export interface EqualizerBand { + band: number; + gain: number; +} + +export class EqualizerStream extends Transform { + public disabled = false; + public bandMultipliers: number[] = new Array(Equalizer.BAND_COUNT).fill(0); + public equalizer: Equalizer; + public constructor(options?: EqualizerStreamOptions) { + super(options); + + options = Object.assign( + {}, + { + bandMultiplier: [], + channels: 1, + disabled: false + }, + options || {} + ); + + if (options.disabled) this.disabled = !!options.disabled; + this.equalizer = new Equalizer(options.channels || 1, this.bandMultipliers); + if (Array.isArray(options.bandMultiplier)) this._processBands(options.bandMultiplier); + } + + public _processBands(multiplier: EqualizerBand[]) { + for (const mul of multiplier) { + if (mul.band > Equalizer.BAND_COUNT - 1 || mul.band < 0) throw new RangeError(`Band value out of range. Expected >0 & <${Equalizer.BAND_COUNT - 1}, received "${mul.band}"`); + this.equalizer.setGain(mul.band, mul.gain); + } + } + + public disable() { + this.disabled = true; + } + + public enable() { + this.disabled = false; + } + + public toggle() { + this.disabled = !this.disabled; + } + + public _transform(chunk: Buffer, encoding: BufferEncoding, callback: TransformCallback): void { + if (this.disabled) { + this.push(chunk); + return callback(); + } + + this.equalizer.process([chunk]); + this.push(chunk); + + return callback(); + } + + public getEQ() { + return this.bandMultipliers.map((m, i) => ({ + band: i, + gain: m + })) as EqualizerBand[]; + } + + public setEQ(bands: EqualizerBand[]) { + this._processBands(bands); + } + + public resetEQ() { + this._processBands( + Array.from( + { + length: Equalizer.BAND_COUNT + }, + (_, i) => ({ + band: i, + gain: 0 + }) + ) + ); + } +} diff --git a/packages/equalizer/src/equalizer/index.ts b/packages/equalizer/src/equalizer/index.ts new file mode 100644 index 000000000..f1d77311b --- /dev/null +++ b/packages/equalizer/src/equalizer/index.ts @@ -0,0 +1,5 @@ +export * from './Equalizer'; +export * from './ChannelProcessor'; +export * from './Coefficients'; +export * from './EqualizerConfiguration'; +export * from './EqualizerStream'; diff --git a/packages/equalizer/src/index.ts b/packages/equalizer/src/index.ts new file mode 100644 index 000000000..962532684 --- /dev/null +++ b/packages/equalizer/src/index.ts @@ -0,0 +1,3 @@ +export * from './biquad'; +export * from './equalizer'; +export * from './utils'; diff --git a/packages/equalizer/src/utils/Frequency.ts b/packages/equalizer/src/utils/Frequency.ts new file mode 100644 index 000000000..c71f1cc4b --- /dev/null +++ b/packages/equalizer/src/utils/Frequency.ts @@ -0,0 +1,34 @@ +export class Frequency { + public constructor(private __val: number) { + if (typeof __val !== 'number' || isNaN(__val) || __val === Infinity) throw new TypeError('Frequency value must be a number'); + if (this.__val < 0) throw new Error(`Frequency value cannot be negative (${__val})`); + } + + public khz() { + return this.__val * 1000.0; + } + + public mhz() { + return this.__val * 1_000_000.0; + } + + public hz() { + return this.__val; + } + + public dt() { + return 1.0 / this.__val; + } + + public valueOf() { + return this.__val; + } + + public toString() { + return `${this.__val}Hz`; + } + + public toJSON() { + return this.toString(); + } +} diff --git a/packages/equalizer/src/utils/index.ts b/packages/equalizer/src/utils/index.ts new file mode 100644 index 000000000..2f78a20c6 --- /dev/null +++ b/packages/equalizer/src/utils/index.ts @@ -0,0 +1 @@ +export * from './Frequency'; diff --git a/packages/equalizer/tsconfig.json b/packages/equalizer/tsconfig.json new file mode 100644 index 000000000..ae35afda0 --- /dev/null +++ b/packages/equalizer/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "@discord-player/tsconfig/base.json", + "include": ["src/**/*", "../../test", "src/biquad"] +} \ No newline at end of file diff --git a/packages/equalizer/tsup.config.ts b/packages/equalizer/tsup.config.ts new file mode 100644 index 000000000..e8bba970a --- /dev/null +++ b/packages/equalizer/tsup.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + clean: true, + bundle: true, + dts: true, + format: ['cjs', 'esm'], + keepNames: true, + minify: false, + entry: ['./src/index.ts'], + skipNodeModulesBundle: true, + sourcemap: true, + target: 'ES2020', + silent: true +}); diff --git a/packages/tsconfig/LICENSE b/packages/tsconfig/LICENSE new file mode 100644 index 000000000..fe07fc736 --- /dev/null +++ b/packages/tsconfig/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Androz2091 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/tsconfig/README.md b/packages/tsconfig/README.md new file mode 100644 index 000000000..7b6e46e0b --- /dev/null +++ b/packages/tsconfig/README.md @@ -0,0 +1,3 @@ +# `@discord-player/tsconfig` + +Discord Player TSConfig diff --git a/packages/tsconfig/base.json b/packages/tsconfig/base.json new file mode 100644 index 000000000..ad5233627 --- /dev/null +++ b/packages/tsconfig/base.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Default", + "compilerOptions": { + "target": "ES2020", + "moduleResolution": "node", + "module": "commonjs", + "declaration": true, + "outDir": "./dist", + "strict": true, + "strictNullChecks": true, + "esModuleInterop": true, + "pretty": true, + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/packages/tsconfig/index.js b/packages/tsconfig/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/packages/tsconfig/package.json b/packages/tsconfig/package.json new file mode 100644 index 000000000..e2f522495 --- /dev/null +++ b/packages/tsconfig/package.json @@ -0,0 +1,27 @@ +{ + "name": "@discord-player/tsconfig", + "version": "0.0.0", + "description": "TSConfig", + "author": "skdhg", + "homepage": "https://discord-player.js.org", + "license": "MIT", + "main": "index.js", + "private": true, + "files": [ + "index.js", + "base.json" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/Androz2091/discord-player.git" + }, + "bugs": { + "url": "https://github.com/Androz2091/discord-player/issues" + }, + "dependencies": { + "typescript": "^4.7.4" + }, + "devDependencies": { + "@types/node": "^18.7.5" + } +} \ No newline at end of file diff --git a/packages/utils/LICENSE b/packages/utils/LICENSE new file mode 100644 index 000000000..fe07fc736 --- /dev/null +++ b/packages/utils/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Androz2091 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/utils/README.md b/packages/utils/README.md new file mode 100644 index 000000000..875063fdf --- /dev/null +++ b/packages/utils/README.md @@ -0,0 +1,74 @@ +# `@discord-player/utils` + +Discord Player utilities + +## Installation + +```sh +$ npm install @discord-player/utils +``` + +## Example + +### Queue + +```js +import { Queue } from "@discord-player/utils"; + +// initialize queue with last-in-first-out strategy +const lifo = new Queue("LIFO"); +// initialize queue with first-in-first-out strategy +const fifo = new Queue("FIFO"); + +// add some data to the queue +lifo.add([1, 2, 3, 4]); +fifo.add([1, 2, 3, 4]); + +// dispatches last inserted item from the queue +console.log(lifo.dispatch()); // 4 + +// dispatches first inserted item from the queue +console.log(fifo.dispatch()); // 1 + +console.log(lifo.at(0)); // 3 +console.log(fifo.at(0)); // 2 +``` + +### Collection + +```ts +import { Collection } from "@discord-player/utils"; + +// utility data structure based on Map +const store = new Collection(); + +store.set("foo", 1); + +console.log(store.get("foo")); // 1 +store.delete("foo"); // true +console.log(store.get("foo")); // undefined +store.delete("foo"); // false +``` + +### Key Mirror + +```ts +import { keyMirror } from "@discord-player/utils"; + +// creates read-only object with same value as the key +const enums = keyMirror([ + "SUNDAY", + "MONDAY", + "TUESDAY" +]); + +console.log(enums); + +/* +{ + "SUNDAY": "SUNDAY", + "MONDAY": "MONDAY", + "TUESDAY": "TUESDAY" +} +*/ +``` \ No newline at end of file diff --git a/packages/utils/package.json b/packages/utils/package.json new file mode 100644 index 000000000..4fa48e8a4 --- /dev/null +++ b/packages/utils/package.json @@ -0,0 +1,36 @@ +{ + "name": "@discord-player/utils", + "version": "0.1.0", + "description": "Discord Player Utilities", + "keywords": [ + "discord-player" + ], + "author": "skdhg", + "homepage": "https://discord-player.js.org", + "license": "MIT", + "main": "dist/index.js", + "directories": { + "src": "src" + }, + "files": [ + "dist" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/Androz2091/discord-player.git" + }, + "scripts": { + "build:check": "tsc --noEmit", + "build": "tsup" + }, + "bugs": { + "url": "https://github.com/Androz2091/discord-player/issues" + }, + "dependencies": { + "@discordjs/collection": "^1.1.0" + }, + "devDependencies": { + "@discord-player/tsconfig": "*", + "tsup": "^6.2.2" + } +} \ No newline at end of file diff --git a/packages/utils/src/Collection.ts b/packages/utils/src/Collection.ts new file mode 100644 index 000000000..74d3bc785 --- /dev/null +++ b/packages/utils/src/Collection.ts @@ -0,0 +1,25 @@ +import { Collection as CollectionNative, Keep, ReadonlyCollection } from '@discordjs/collection'; + +export class Collection extends CollectionNative { + #array!: V[] | null; + + /** + * @returns {Array} Array of this collection + */ + public array(): V[] { + if (this.#array) return this.#array; + this.#array = [...this.values()]; + return this.#array; + } + + public set(key: K, value: V): this { + this.#array = null; + super.set(key, value); + return this; + } + + public delete(key: K): boolean { + this.#array = null; + return super.delete(key); + } +} diff --git a/packages/utils/src/Errors.ts b/packages/utils/src/Errors.ts new file mode 100644 index 000000000..1160f42dd --- /dev/null +++ b/packages/utils/src/Errors.ts @@ -0,0 +1,7 @@ +export type PlayerExceptionMessage = string | Record; + +export class PlayerException extends Error { + public constructor(message: PlayerExceptionMessage) { + super(typeof message === 'string' ? message : JSON.stringify(message, null, 2)); + } +} diff --git a/packages/utils/src/EventEmitter.d.ts b/packages/utils/src/EventEmitter.d.ts new file mode 100644 index 000000000..8122b120f --- /dev/null +++ b/packages/utils/src/EventEmitter.d.ts @@ -0,0 +1,27 @@ +export class EventEmitter = DefaultListener> { + public static defaultMaxListeners: number; + public constructor(options?: { captureRejections?: boolean }); + public addListener(event: U, listener: L[U]): this; + public prependListener(event: U, listener: L[U]): this; + public prependOnceListener(event: U, listener: L[U]): this; + public removeListener(event: U, listener: L[U]): this; + public removeAllListeners(event?: keyof L): this; + public once(event: U, listener: L[U]): this; + public on(event: U, listener: L[U]): this; + public off(event: U, listener: L[U]): this; + public emit(event: U, ...args: Parameters): boolean; + public eventNames(): U[]; + public listenerCount(type: keyof L): number; + public listeners(type: U): L[U][]; + public rawListeners(type: U): L[U][]; + public getMaxListeners(): number; + public setMaxListeners(n: number): this; +} + +export type ListenerSignature = { + [E in keyof L]: (...args: any[]) => any; +}; + +export type DefaultListener = { + [k: string]: (...args: any[]) => any; +}; diff --git a/packages/utils/src/EventEmitter.ts b/packages/utils/src/EventEmitter.ts new file mode 100644 index 000000000..164e6bca0 --- /dev/null +++ b/packages/utils/src/EventEmitter.ts @@ -0,0 +1 @@ +export { EventEmitter } from 'node:events'; diff --git a/packages/utils/src/Queue.ts b/packages/utils/src/Queue.ts new file mode 100644 index 000000000..ed31f04fd --- /dev/null +++ b/packages/utils/src/Queue.ts @@ -0,0 +1,117 @@ +export type QueueStrategy = 'LIFO' | 'FIFO'; + +export type QueueItemFilter = (value: T, idx: number, array: T[]) => boolean; + +export class Queue { + public store: T[]; + public constructor(public strategy: QueueStrategy = 'FIFO', initializer: T[] = []) { + if (!['FIFO', 'LIFO'].includes(strategy)) throw new TypeError(`Invalid queue strategy "${strategy}"!`); + this.store = Array.isArray(initializer) ? initializer : []; + + Object.defineProperty(this, 'store', { + writable: true, + configurable: true, + enumerable: false + }); + } + + public static from(data: T[], strategy: QueueStrategy = 'FIFO') { + return new Queue(strategy, data); + } + + public isFIFO() { + return this.strategy === 'FIFO'; + } + + public isLIFO() { + return this.strategy === 'LIFO'; + } + + public add(item: T | T[]) { + if (this.strategy === 'FIFO') { + if (Array.isArray(item)) { + this.store.push(...item); + } else { + this.store.push(item); + } + } else { + if (Array.isArray(item)) { + this.store.unshift(...item); + } else { + this.store.unshift(item); + } + } + } + + public clear() { + this.store = []; + } + + public shuffle() { + for (let i = this.store.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [this.store[i], this.store[j]] = [this.store[j], this.store[i]]; + } + } + + public remove(itemFilter: QueueItemFilter) { + const items = this.store.filter(itemFilter); + if (items.length) this.store = this.store.filter((res) => !items.includes(res)); + } + + public removeOne(itemFilter: QueueItemFilter) { + const item = this.store.find(itemFilter); + if (item) this.store = this.store.filter((res) => res !== item); + } + + public find(itemFilter: QueueItemFilter) { + return this.store.find(itemFilter); + } + + public filter(itemFilter: QueueItemFilter) { + return this.store.filter(itemFilter); + } + + public some(itemFilter: QueueItemFilter) { + return this.store.some(itemFilter); + } + + public every(itemFilter: QueueItemFilter) { + return this.store.every(itemFilter); + } + + public map(itemFilter: QueueItemFilter) { + const arr = this.toArray(); + return arr.map(itemFilter); + } + + public at(idx: number) { + const arr = this.toArray(); + return typeof Array.prototype.at === 'function' ? arr.at(idx) : arr[idx]; + } + + public dispatch() { + const dispatchBeginning = this.strategy === 'FIFO'; + return dispatchBeginning ? this.store.shift() : this.store.pop(); + } + + public clone() { + return new Queue(this.strategy, this.store.slice()); + } + + public get size() { + return this.store.length; + } + + public toString() { + return `Queue<${this.store.length} items>`; + } + + public toArray() { + return this.strategy === 'FIFO' ? this.store.slice() : this.store.slice().reverse(); + } + + public toJSON() { + return this.store; + } +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts new file mode 100644 index 000000000..6afabc657 --- /dev/null +++ b/packages/utils/src/index.ts @@ -0,0 +1,5 @@ +export * from './Collection'; +export * from './Queue'; +export * from './EventEmitter'; +export * from './utils'; +export * from './Errors'; diff --git a/packages/utils/src/utils.ts b/packages/utils/src/utils.ts new file mode 100644 index 000000000..d9f079fa9 --- /dev/null +++ b/packages/utils/src/utils.ts @@ -0,0 +1,9 @@ +function createEnum(data: K[]) { + const obj = {} as Record; + + for (const item of data) obj[item] = item; + + return Object.freeze(obj); +} + +export { createEnum, createEnum as keyMirror }; diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json new file mode 100644 index 000000000..08981710e --- /dev/null +++ b/packages/utils/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "@discord-player/tsconfig/base.json", + "include": ["src/**/*"], + "exclude": ["node_modules"] +} \ No newline at end of file diff --git a/packages/utils/tsup.config.ts b/packages/utils/tsup.config.ts new file mode 100644 index 000000000..e8bba970a --- /dev/null +++ b/packages/utils/tsup.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + clean: true, + bundle: true, + dts: true, + format: ['cjs', 'esm'], + keepNames: true, + minify: false, + entry: ['./src/index.ts'], + skipNodeModulesBundle: true, + sourcemap: true, + target: 'ES2020', + silent: true +}); diff --git a/scripts/docgen.js b/scripts/docgen.js index b628ab0e6..79e07fa5e 100644 --- a/scripts/docgen.js +++ b/scripts/docgen.js @@ -1,8 +1,8 @@ /* eslint-disable */ -const { runGenerator } = require("@discordjs/ts-docgen"); +const { runGenerator } = require('@discordjs/ts-docgen'); runGenerator({ - existingOutput: "docs/typedoc.json", - custom: "docs/config.yml", - output: "docs/docs.json" -}); \ No newline at end of file + existingOutput: `${__dirname}/../docs/typedoc.json`, + custom: `${__dirname}/../docs/config.yml`, + output: `${__dirname}/../docs/docs.json` +}); diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 6db0196e1..000000000 --- a/src/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -// try applying smooth volume patch on load -import "./smoothVolume"; -import { version as djsVersion } from "discord.js"; - -export { AudioFilters } from "./utils/AudioFilters"; -export { ExtractorModel } from "./Structures/ExtractorModel"; -export { Playlist } from "./Structures/Playlist"; -export { Player } from "./Player"; -export { PlayerError, ErrorStatusCode } from "./Structures/PlayerError"; -export { QueryResolver } from "./utils/QueryResolver"; -export { Queue } from "./Structures/Queue"; -export { Track } from "./Structures/Track"; -export { VoiceUtils } from "./VoiceInterface/VoiceUtils"; -export { VoiceEvents, StreamDispatcher } from "./VoiceInterface/StreamDispatcher"; -export * from "./VoiceInterface/VolumeTransformer"; -export { Util } from "./utils/Util"; -export * from "./types/types"; -export * from "./utils/FFmpegStream"; - -// eslint-disable-next-line @typescript-eslint/no-var-requires -export const version: string = require(`${__dirname}/../package.json`).version; - -if (!djsVersion.startsWith("14")) { - process.emitWarning(`Discord.js v${djsVersion} is incompatible with Discord Player v${version}! Please use >=v14.x of Discord.js`); -} diff --git a/src/smoothVolume.ts b/src/smoothVolume.ts deleted file mode 100644 index d81f0e1cd..000000000 --- a/src/smoothVolume.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { VolumeTransformer as VolumeTransformerMock } from "./VoiceInterface/VolumeTransformer"; - -if (!("DISABLE_DISCORD_PLAYER_SMOOTH_VOLUME" in process.env)) { - try { - // eslint-disable-next-line - const mod = require("prism-media") as typeof import("prism-media") & { VolumeTransformer: typeof VolumeTransformerMock }; - - if (typeof mod.VolumeTransformer.hasSmoothing !== "boolean") { - Reflect.set(mod, "VolumeTransformer", VolumeTransformerMock); - } - } catch { - /* do nothing */ - } -} diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 0476f7595..000000000 --- a/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "module": "commonjs", - "declaration": true, - "outDir": "./dist", - "strict": true, - "strictNullChecks": false, - "esModuleInterop": true, - "pretty": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "importHelpers": true - }, - "include": [ - "src/**/*" - ] -} diff --git a/turbo.json b/turbo.json new file mode 100644 index 000000000..365668ccd --- /dev/null +++ b/turbo.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://turborepo.org/schema.json", + "pipeline": { + "build": { + "dependsOn": [ + "^build" + ], + "outputs": [ + "dist/**" + ] + }, + "docs": {}, + "build:check": {}, + "lint": {} + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 3a5304a2d..f84874097 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,27 +2,6 @@ # yarn lockfile v1 -"@babel/code-frame@^7.16.7": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" - integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== - dependencies: - "@babel/highlight" "^7.18.6" - -"@babel/helper-validator-identifier@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" - integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== - -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== - dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" - js-tokens "^4.0.0" - "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -30,11 +9,6 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@discord-player/equalizer@^0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@discord-player/equalizer/-/equalizer-0.1.2.tgz#df37c07077671c9a109783c90265fd8ffe8e3084" - integrity sha512-OHhQ1CA8N+L3zvax4bkSiRoZ8AY4s3KoPXdHHBDNNdaH8XEDsnSlcrlEQZgnMnEyoWS4BzUWGRcVPaDySmt6fA== - "@discordjs/builders@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@discordjs/builders/-/builders-1.1.0.tgz#4366a4fe069238c3e6e674b74404c79f3ba76525" @@ -51,6 +25,11 @@ resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-1.0.1.tgz#4acc41dfdacd6a14a7cf3109968044a2d40593dd" integrity sha512-5V/wswzR3r2RVYXLxxg4TvrAnBhVCNgHTXhC+OUtLoriJ072rPMHo+Iw1SS1vrCckp8Es40XM411+WkNRPaXFw== +"@discordjs/collection@^1.1.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-1.3.0.tgz#65bf9674db72f38c25212be562bb28fa0dba6aa3" + integrity sha512-ylt2NyZ77bJbRij4h9u/wVy7qYw/aDqQLWnadjvDqW/WoWCxrsX6M3CIw9GVP5xcGCDxsrKj5e0r5evuFYwrKg== + "@discordjs/rest@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@discordjs/rest/-/rest-1.0.1.tgz#5c1c379de9eb4170b4964a11ced8add1d5ae808d" @@ -83,6 +62,16 @@ tslib "^2.4.0" ws "^8.8.1" +"@esbuild/android-arm@0.15.18": + version "0.15.18" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.15.18.tgz#266d40b8fdcf87962df8af05b76219bc786b4f80" + integrity sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw== + +"@esbuild/linux-loong64@0.15.18": + version "0.15.18" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz#128b76ecb9be48b60cf5cfc1c63a4f00691a3239" + integrity sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ== + "@eslint/eslintrc@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" @@ -98,19 +87,6 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@favware/rollup-type-bundler@^1.0.10": - version "1.0.10" - resolved "https://registry.yarnpkg.com/@favware/rollup-type-bundler/-/rollup-type-bundler-1.0.10.tgz#3b71310ff76cd5d5b3d162b393cac5a49a9adf63" - integrity sha512-7UDim5gncFBA58XLlXUR6jTmuwZCEQ/wATIALGfxf3jklXA/gnWlpfww3uaFEv5Oy5GUsOvqIcHk0bCMGWklDw== - dependencies: - "@sapphire/utilities" "^3.8.0" - colorette "^2.0.19" - commander "^9.4.0" - js-yaml "^4.1.0" - rollup "^2.77.2" - rollup-plugin-dts "^4.2.2" - typescript "^4.7.4" - "@humanwhocodes/config-array@^0.10.4": version "0.10.4" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.10.4.tgz#01e7366e57d2ad104feea63e72248f22015c520c" @@ -187,11 +163,6 @@ resolved "https://registry.yarnpkg.com/@sapphire/snowflake/-/snowflake-3.2.2.tgz#faacdc1b5f7c43145a71eddba917de2b707ef780" integrity sha512-ula2O0kpSZtX9rKXNeQMrHwNd7E4jPDJYUXmEGTFdMRfyfMw+FPyh04oKMjAiDuOi64bYgVkOV3MjK+loImFhQ== -"@sapphire/utilities@^3.8.0": - version "3.8.0" - resolved "https://registry.yarnpkg.com/@sapphire/utilities/-/utilities-3.8.0.tgz#a75b0ed7107d769a453973b4e609ec031319ca65" - integrity sha512-y33Uuk11z07qjUCuDOHihFF/dF1pq4YfSrgNNRQk6Y0yy5buxfoA8OPTGr4YTIdGq9359ncfIty+HT9htMbygQ== - "@tokenizer/token@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" @@ -232,6 +203,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.6.3.tgz#4e4a95b6fe44014563ceb514b2598b3e623d1c98" integrity sha512-6qKpDtoaYLM+5+AFChLhHermMQxc3TOEFIDzrZLPRGHPrLEwqFkkT5Kx3ju05g6X7uDPazz3jHbKPX0KzCjntg== +"@types/node@^18.7.5": + version "18.11.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" + integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== + "@types/ws@^8.5.3": version "8.5.3" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" @@ -354,18 +330,16 @@ ajv@^6.10.0, ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ansi-colors@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -373,6 +347,19 @@ ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -388,19 +375,16 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -assert@^1.4.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" - integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== - dependencies: - object-assign "^4.1.1" - util "0.10.3" - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + boolbase@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" @@ -421,27 +405,30 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.1: +braces@^3.0.1, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" +bundle-require@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bundle-require/-/bundle-require-3.1.2.tgz#1374a7bdcb8b330a7ccc862ccbf7c137cc43ad27" + integrity sha512-Of6l6JBAxiyQ5axFxUM6dYeP/W7X2Sozeo/4EYB9sJhL+dqL7TKjg+shwxp6jlu/6ZSERfsYtIpSJ1/x3XkAEA== + dependencies: + load-tsconfig "^0.2.0" + +cac@^6.7.12: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -chalk@^2.0.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - chalk@^4.0.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -475,12 +462,20 @@ cheerio@^1.0.0-rc.10: parse5 "^7.0.0" parse5-htmlparser2-tree-adapter "^7.0.0" -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" +chokidar@^3.5.1: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" color-convert@^2.0.1: version "2.0.1" @@ -489,25 +484,20 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -colorette@^2.0.19: - version "2.0.19" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" - integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== +commander@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== -commander@^9.4.0: - version "9.4.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.0.tgz#bc4a40918fefe52e22450c111ecd6b7acce6f11c" - integrity sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw== +common-tags@^1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6" + integrity sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA== concat-map@0.0.1: version "0.0.1" @@ -519,7 +509,7 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-spawn@^7.0.2: +cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -551,7 +541,7 @@ debug@^4.1.1, debug@^4.3.2: dependencies: ms "2.1.2" -debug@^4.3.4: +debug@^4.3.1, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -590,6 +580,11 @@ discord-api-types@^0.37.0: resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.37.0.tgz#c16f0bcc5630edc2b0597d8f273ec8385473a3be" integrity sha512-6LlL0xceiZs/kQ5PeKe5inkcjR73vagt3oACsP/C5IWKjXfzLGKrXn6yRYgiHIeJyFZ1xVPRJYE4W/u8UTT4ig== +discord-api-types@^0.37.2: + version "0.37.28" + resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.37.28.tgz#874c7cf58c997513cb4c6f65a2ec7fd019a12a89" + integrity sha512-K0fw7m7km9th3dCQ2AR90q/FwX3uAj+OLc+Zuo39VY9vCn0Ux/iObM4y1zJYIH3vTc+QlrksVErUvyeONjOKMQ== + discord.js@^14.1.2: version "14.1.2" resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-14.1.2.tgz#6897f9019be3213388950c1cf6d7c9ea4661d42a" @@ -649,10 +644,133 @@ entities@^4.2.0, entities@^4.3.0, entities@^4.4.0: resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174" integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA== -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" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +esbuild-android-64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz#20a7ae1416c8eaade917fb2453c1259302c637a5" + integrity sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA== + +esbuild-android-arm64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz#9cc0ec60581d6ad267568f29cf4895ffdd9f2f04" + integrity sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ== + +esbuild-darwin-64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz#428e1730ea819d500808f220fbc5207aea6d4410" + integrity sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg== + +esbuild-darwin-arm64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz#b6dfc7799115a2917f35970bfbc93ae50256b337" + integrity sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA== + +esbuild-freebsd-64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz#4e190d9c2d1e67164619ae30a438be87d5eedaf2" + integrity sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA== + +esbuild-freebsd-arm64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz#18a4c0344ee23bd5a6d06d18c76e2fd6d3f91635" + integrity sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA== + +esbuild-linux-32@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz#9a329731ee079b12262b793fb84eea762e82e0ce" + integrity sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg== + +esbuild-linux-64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz#532738075397b994467b514e524aeb520c191b6c" + integrity sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw== + +esbuild-linux-arm64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz#5372e7993ac2da8f06b2ba313710d722b7a86e5d" + integrity sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug== + +esbuild-linux-arm@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz#e734aaf259a2e3d109d4886c9e81ec0f2fd9a9cc" + integrity sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA== + +esbuild-linux-mips64le@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz#c0487c14a9371a84eb08fab0e1d7b045a77105eb" + integrity sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ== + +esbuild-linux-ppc64le@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz#af048ad94eed0ce32f6d5a873f7abe9115012507" + integrity sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w== + +esbuild-linux-riscv64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz#423ed4e5927bd77f842bd566972178f424d455e6" + integrity sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg== + +esbuild-linux-s390x@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz#21d21eaa962a183bfb76312e5a01cc5ae48ce8eb" + integrity sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ== + +esbuild-netbsd-64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz#ae75682f60d08560b1fe9482bfe0173e5110b998" + integrity sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg== + +esbuild-openbsd-64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz#79591a90aa3b03e4863f93beec0d2bab2853d0a8" + integrity sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ== + +esbuild-sunos-64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz#fd528aa5da5374b7e1e93d36ef9b07c3dfed2971" + integrity sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw== + +esbuild-windows-32@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz#0e92b66ecdf5435a76813c4bc5ccda0696f4efc3" + integrity sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ== + +esbuild-windows-64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz#0fc761d785414284fc408e7914226d33f82420d0" + integrity sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw== + +esbuild-windows-arm64@0.15.18: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz#5b5bdc56d341d0922ee94965c89ee120a6a86eb7" + integrity sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ== + +esbuild@^0.15.1: + version "0.15.18" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.15.18.tgz#ea894adaf3fbc036d32320a00d4d6e4978a2f36d" + integrity sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q== + optionalDependencies: + "@esbuild/android-arm" "0.15.18" + "@esbuild/linux-loong64" "0.15.18" + esbuild-android-64 "0.15.18" + esbuild-android-arm64 "0.15.18" + esbuild-darwin-64 "0.15.18" + esbuild-darwin-arm64 "0.15.18" + esbuild-freebsd-64 "0.15.18" + esbuild-freebsd-arm64 "0.15.18" + esbuild-linux-32 "0.15.18" + esbuild-linux-64 "0.15.18" + esbuild-linux-arm "0.15.18" + esbuild-linux-arm64 "0.15.18" + esbuild-linux-mips64le "0.15.18" + esbuild-linux-ppc64le "0.15.18" + esbuild-linux-riscv64 "0.15.18" + esbuild-linux-s390x "0.15.18" + esbuild-netbsd-64 "0.15.18" + esbuild-openbsd-64 "0.15.18" + esbuild-sunos-64 "0.15.18" + esbuild-windows-32 "0.15.18" + esbuild-windows-64 "0.15.18" + esbuild-windows-arm64 "0.15.18" escape-string-regexp@^4.0.0: version "4.0.0" @@ -784,6 +902,21 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -861,6 +994,15 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2" integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== +fs-extra@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.0.tgz#5784b102104433bb0e090f48bfc4a30742c357ed" + integrity sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -876,14 +1018,12 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -gen-esm-wrapper@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/gen-esm-wrapper/-/gen-esm-wrapper-1.1.3.tgz#f0dd910ea1e36d46e708eb0e9fb74ac4bc2f9c76" - integrity sha512-LNHZ+QpaCW/0VhABIbXn45V+P8kFvjjwuue9hbV23eOjuFVz6c0FE3z1XpLX9pSjLW7UmtCkXo5F9vhZWVs8oQ== - dependencies: - is-valid-identifier "^2.0.2" +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== -glob-parent@^5.1.2: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -897,6 +1037,18 @@ glob-parent@^6.0.1: dependencies: is-glob "^4.0.3" +glob@7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.1.3: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" @@ -927,7 +1079,7 @@ globals@^13.15.0: dependencies: type-fest "^0.20.2" -globby@^11.1.0: +globby@^11.0.3, globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -939,16 +1091,16 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + grapheme-splitter@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" @@ -969,10 +1121,10 @@ htmlparser2@^8.0.1: domutils "^3.0.1" entities "^4.3.0" -husky@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.1.tgz#511cb3e57de3e3190514ae49ed50f6bc3f50b3e9" - integrity sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw== +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== ieee754@^1.2.1: version "1.2.1" @@ -1010,17 +1162,19 @@ inherits@2, inherits@^2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -inherits@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" - integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -1032,22 +1186,20 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-valid-identifier@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-valid-identifier/-/is-valid-identifier-2.0.2.tgz#146d9dbf29821b8118580b039d2203aa4bd1da4b" - integrity sha512-mpS5EGqXOwzXtKAg6I44jIAqeBfntFLxpAth1rrKbxtKyI6LPktyDYpHBI+tHlduhhX/SF26mFXmxQu995QVqg== - dependencies: - assert "^1.4.1" +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +joycon@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" + integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== js-yaml@^4.1.0: version "4.1.0" @@ -1071,6 +1223,15 @@ jsonc-parser@^3.0.0: resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22" integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA== +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -1091,6 +1252,21 @@ libsodium@^0.7.0: resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.9.tgz#4bb7bcbf662ddd920d8795c227ae25bbbfa3821b" integrity sha512-gfeADtR4D/CM0oRUviKBViMGXZDgnFdMKMzHsvBdqLBHd9ySi6EtYnmuhHVDDYgYpAO8eU8hEY+F8vIUAPh08A== +lilconfig@^2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4" + integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +load-tsconfig@^0.2.0: + version "0.2.3" + resolved "https://registry.yarnpkg.com/load-tsconfig/-/load-tsconfig-0.2.3.tgz#08af3e7744943caab0c75f8af7f1703639c3ef1f" + integrity sha512-iyT2MXws+dc2Wi6o3grCFtGXpeMvHmJqS27sMPGtV2eUu4PeFnG+33I8BlFK1t1NWMjOpcx9bridn5yxLDX2gQ== + locate-path@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" @@ -1108,6 +1284,11 @@ lodash.snakecase@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" integrity sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw== +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== + lodash.uniqwith@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniqwith/-/lodash.uniqwith-4.5.0.tgz#7a0cbf65f43b5928625a9d4d0dc54b18cadc7ef3" @@ -1133,13 +1314,6 @@ m3u8stream@^0.8.4, m3u8stream@^0.8.6: miniget "^4.2.2" sax "^1.2.4" -magic-string@^0.26.1: - version "0.26.2" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.26.2.tgz#5331700e4158cd6befda738bb6b0c7b93c0d4432" - integrity sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A== - dependencies: - sourcemap-codec "^1.4.8" - make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" @@ -1155,6 +1329,11 @@ marked@^4.0.18: resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.18.tgz#cd0ac54b2e5610cfb90e8fd46ccaa8292c9ed569" integrity sha512-wbLDJ7Zh0sqA0Vdg6aqlbT+yPxqLblpAZh1mK2+AO2twQkPywvvqQNfEPVwSSRjZ7dZcdeVBIAgiO7MMp3Dszw== +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -1168,6 +1347,11 @@ micromatch@^4.0.4: braces "^3.0.1" picomatch "^2.2.3" +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + miniget@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/miniget/-/miniget-4.2.2.tgz#db20320f265efdc4c1826a0be431d56753074475" @@ -1199,6 +1383,15 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -1211,6 +1404,18 @@ node-fetch@^2.6.1: dependencies: whatwg-url "^5.0.0" +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + nth-check@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" @@ -1218,7 +1423,7 @@ nth-check@^2.0.1: dependencies: boolbase "^1.0.0" -object-assign@^4.1.1: +object-assign@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -1230,6 +1435,13 @@ once@^1.3.0: dependencies: wrappy "1" +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + optionator@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" @@ -1293,7 +1505,7 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-key@^3.1.0: +path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== @@ -1308,20 +1520,33 @@ peek-readable@^5.0.0: resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-5.0.0.tgz#7ead2aff25dc40458c60347ea76cfdfd63efdfec" integrity sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A== -picomatch@^2.2.3: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pirates@^4.0.1: + version "4.0.5" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" + integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== + +postcss-load-config@^3.0.1: + version "3.1.4" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855" + integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg== + dependencies: + lilconfig "^2.0.5" + yaml "^1.10.2" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prettier@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" - integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== +prettier@^2.8.2: + version "2.8.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.2.tgz#c4ea1b5b454d7c4b59966db2e06ed7eec5dfd160" + integrity sha512-BtRV9BcncDyI2tsuS19zzhzoxD8Dh8LiCx7j7tHzrkz8GFXAexeWFdi22mjE1d16dftH2qNaytVxqiRTGlMfpw== prism-media@^1.3.4: version "1.3.4" @@ -1354,6 +1579,13 @@ readable-web-to-node-stream@^3.0.2: dependencies: readable-stream "^3.6.0" +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + regexpp@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" @@ -1364,6 +1596,11 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -1376,19 +1613,10 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -rollup-plugin-dts@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/rollup-plugin-dts/-/rollup-plugin-dts-4.2.2.tgz#82876b8784213af29b02cf260b45e404ff835ce1" - integrity sha512-A3g6Rogyko/PXeKoUlkjxkP++8UDVpgA7C+Tdl77Xj4fgEaIjPSnxRmR53EzvoYy97VMVwLAOcWJudaVAuxneQ== - dependencies: - magic-string "^0.26.1" - optionalDependencies: - "@babel/code-frame" "^7.16.7" - -rollup@^2.77.2: - version "2.77.2" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.77.2.tgz#6b6075c55f9cc2040a5912e6e062151e42e2c4e3" - integrity sha512-m/4YzYgLcpMQbxX3NmAqDvwLATZzxt8bIegO78FZLl+lAgKJBd1DRAOeEiZcKOIOPjxE6ewHWHNgGEalFXuz1g== +rollup@^3.2.5: + version "3.10.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.10.0.tgz#6eb19196d8b3b375ca651cb78261faac48e24cd6" + integrity sha512-JmRYz44NjC1MjVF2VKxc0M1a97vn+cDxeqWmnwyAF4FvpjK8YFdHpaqvQB+3IxCvX05vJxKZkoMDU8TShhmJVA== optionalDependencies: fsevents "~2.3.2" @@ -1437,6 +1665,11 @@ shiki@^0.10.1: vscode-oniguruma "^1.6.1" vscode-textmate "5.2.0" +signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -1451,10 +1684,12 @@ soundcloud-scraper@^5.0.3: m3u8stream "^0.8.4" node-fetch "^2.6.1" -sourcemap-codec@^1.4.8: - version "1.4.8" - resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" - integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== +source-map@0.8.0-beta.0: + version "0.8.0-beta.0" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11" + integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA== + dependencies: + whatwg-url "^7.0.0" spotify-uri@~3.0.3: version "3.0.3" @@ -1483,6 +1718,11 @@ strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -1496,12 +1736,17 @@ strtok3@^7.0.0-alpha.9: "@tokenizer/token" "^0.3.0" peek-readable "^5.0.0" -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== +sucrase@^3.20.3: + version "3.29.0" + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.29.0.tgz#3207c5bc1b980fdae1e539df3f8a8a518236da7d" + integrity sha512-bZPAuGA5SdFHuzqIhTAqt9fvNEo9rESqXIG3oiKdF8K4UmkQxC4KlNL3lVyAErXp+mPvUqZ5l13qx6TrDIGf3A== dependencies: - has-flag "^3.0.0" + commander "^4.0.0" + glob "7.1.6" + lines-and-columns "^1.1.6" + mz "^2.7.0" + pirates "^4.0.1" + ts-interface-checker "^0.1.9" supports-color@^7.1.0: version "7.2.0" @@ -1515,6 +1760,20 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + tiny-typed-emitter@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz#b3b027fdd389ff81a152c8e847ee2f5be9fad7b5" @@ -1535,11 +1794,28 @@ token-types@^5.0.0-alpha.2: "@tokenizer/token" "^0.3.0" ieee754 "^1.2.1" +tr46@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" + integrity sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA== + dependencies: + punycode "^2.1.0" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +tree-kill@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + +ts-interface-checker@^0.1.9: + version "0.1.13" + resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" + integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== + ts-mixer@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.1.tgz#7c2627fb98047eb5f3c7f2fee39d1521d18fe87a" @@ -1574,6 +1850,26 @@ tslib@^2.4.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +tsup@^6.2.2: + version "6.5.0" + resolved "https://registry.yarnpkg.com/tsup/-/tsup-6.5.0.tgz#1be97481b7a56385b7c40d01bdabb4196f3649cf" + integrity sha512-36u82r7rYqRHFkD15R20Cd4ercPkbYmuvRkz3Q1LCm5BsiFNUgpo36zbjVhCOgvjyxNBWNKHsaD5Rl8SykfzNA== + dependencies: + bundle-require "^3.1.2" + cac "^6.7.12" + chokidar "^3.5.1" + debug "^4.3.1" + esbuild "^0.15.1" + execa "^5.0.0" + globby "^11.0.3" + joycon "^3.0.1" + postcss-load-config "^3.0.1" + resolve-from "^5.0.0" + rollup "^3.2.5" + source-map "0.8.0-beta.0" + sucrase "^3.20.3" + tree-kill "^1.2.2" + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -1581,6 +1877,48 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" +turbo-darwin-64@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-1.7.0.tgz#e53ed2e791ce4d146a76aa7c276c84d590134550" + integrity sha512-hSGAueSf5Ko8J67mpqjpt9FsP6ePn1nMcl7IVPoJq5dHsgX3anCP/BPlexJ502bNK+87DDyhQhJ/LPSJXKrSYQ== + +turbo-darwin-arm64@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-1.7.0.tgz#d5fdeccb38f1092b5c888f83a2dcd9ae97068dad" + integrity sha512-BLLOW5W6VZxk5+0ZOj5AO1qjM0P5isIgjbEuyAl8lHZ4s9antUbY4CtFrspT32XxPTYoDl4UjviPMcSsbcl3WQ== + +turbo-linux-64@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-1.7.0.tgz#48cf63aa51cde67d5a56eb70df6d7a93548376fa" + integrity sha512-aw2qxmfZa+kT87SB3GNUoFimqEPzTlzlRqhPgHuAAT6Uf0JHnmebPt4K+ZPtDNl5yfVmtB05bhHPqw+5QV97Yg== + +turbo-linux-arm64@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-1.7.0.tgz#1b9b047d72071f9b8090057ede40ca54db945ae7" + integrity sha512-AJEx2jX+zO5fQtJpO3r6uhTabj4oSA5ZhB7zTs/rwu/XqoydsvStA4X8NDW4poTbOjF7DcSHizqwi04tSMzpJw== + +turbo-windows-64@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-1.7.0.tgz#4683b84c5ba806cba8ab3ec28e4a62f46bdc9901" + integrity sha512-ewj7PPv2uxqv0r31hgnBa3E5qwUu7eyVRP5M1gB/TJXfSHduU79gbxpKCyxIZv2fL/N2/3U7EPOQPSZxBAoljA== + +turbo-windows-arm64@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-1.7.0.tgz#4cdcae60868df73e2af06dcaf39e1c725176cf7f" + integrity sha512-LzjOUzveWkvTD0jP8DBMYiAnYemmydsvqxdSmsUapHHJkl6wKZIOQNSO7pxsy+9XM/1/+0f9Y9F9ZNl5lePTEA== + +turbo@^1.6.3: + version "1.7.0" + resolved "https://registry.yarnpkg.com/turbo/-/turbo-1.7.0.tgz#8e7d2fbc0b57f8835d51195e957766969929616f" + integrity sha512-cwympNwQNnQZ/TffBd8yT0i0O10Cf/hlxccCYgUcwhcGEb9rDjE5thDbHoHw1hlJQUF/5ua7ERJe7Zr0lNE/ww== + optionalDependencies: + turbo-darwin-64 "1.7.0" + turbo-darwin-arm64 "1.7.0" + turbo-linux-64 "1.7.0" + turbo-linux-arm64 "1.7.0" + turbo-windows-64 "1.7.0" + turbo-windows-arm64 "1.7.0" + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -1624,6 +1962,11 @@ undici@^5.8.0: resolved "https://registry.yarnpkg.com/undici/-/undici-5.8.0.tgz#dec9a8ccd90e5a1d81d43c0eab6503146d649a4f" integrity sha512-1F7Vtcez5w/LwH2G2tGnFIihuWUlc58YidwLiCv+jR2Z50x0tNXpRRw7eOIJ+GvqCqIkg9SB7NWAJ/T9TLfv8Q== +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -1636,13 +1979,6 @@ util-deprecate@^1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -util@0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" - integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= - dependencies: - inherits "2.0.1" - v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -1668,6 +2004,11 @@ webidl-conversions@^3.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== +webidl-conversions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -1676,6 +2017,15 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" +whatwg-url@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" + integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -1703,6 +2053,11 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yaml@^1.10.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" From 6e4067ae7b90d9dda4f5ef713dfc0bf7b9e2e2c3 Mon Sep 17 00:00:00 2001 From: skdhg <46562212+skdhg@users.noreply.github.com> Date: Fri, 13 Jan 2023 15:38:54 +0545 Subject: [PATCH 2/4] chore: monorepo scripts --- .husky/pre-commit | 0 CONTRIBUTING.md | 12 +++ package.json | 4 + packages/discord-player/package.json | 3 - packages/discord-player/tsconfig.json | 2 +- packages/equalizer/tsconfig.json | 2 +- scripts/bootstrap.js | 102 ++++++++++++++++++++++++++ scripts/template/LICENSE | 21 ++++++ scripts/template/src/index.ts | 1 + scripts/template/tsup.config.ts | 15 ++++ 10 files changed, 157 insertions(+), 5 deletions(-) mode change 100644 => 100755 .husky/pre-commit create mode 100644 scripts/bootstrap.js create mode 100644 scripts/template/LICENSE create mode 100644 scripts/template/src/index.ts create mode 100644 scripts/template/tsup.config.ts diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100644 new mode 100755 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e0cd9db60..924b62a2f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,18 @@ # Hello This document is for people who want to contribute to this project! +# Creating a new package + +Run `yarn bootstrap ` to create new package. + +Example: + +```sh +$ yarn bootstrap example-lib +``` + +This will create a package `@discord-player/example-lib` under `packages` dir. + # Code Style ## Formatting diff --git a/package.json b/package.json index 5966da065..cb579ec04 100644 --- a/package.json +++ b/package.json @@ -8,13 +8,17 @@ "packages/*" ], "dependencies": { + "@typescript-eslint/eslint-plugin": "^5.32.0", + "@typescript-eslint/parser": "^5.32.0", "ansi-colors": "^4.1.3", "common-tags": "^1.8.2", + "eslint": "^8.21.0", "fs-extra": "^11.1.0", "prettier": "^2.8.2", "turbo": "^1.6.3" }, "scripts": { + "bootstrap": "node ./scripts/bootstrap.js", "build": "turbo run build", "docs": "turbo run docs", "build:check": "turbo run build:check", diff --git a/packages/discord-player/package.json b/packages/discord-player/package.json index 51c8892ba..331f4d1f9 100644 --- a/packages/discord-player/package.json +++ b/packages/discord-player/package.json @@ -76,11 +76,8 @@ "@discordjs/ts-docgen": "^0.4.1", "@types/node": "^18.6.3", "@types/ws": "^8.5.3", - "@typescript-eslint/eslint-plugin": "^5.32.0", - "@typescript-eslint/parser": "^5.32.0", "discord-api-types": "^0.37.0", "discord.js": "^14.1.2", - "eslint": "^8.21.0", "opusscript": "^0.0.8", "soundcloud-scraper": "^5.0.3", "spotify-url-info": "^3.1.7", diff --git a/packages/discord-player/tsconfig.json b/packages/discord-player/tsconfig.json index b9167ca59..ff19098dc 100644 --- a/packages/discord-player/tsconfig.json +++ b/packages/discord-player/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "@discord-player/tsconfig/base.json", - "include": ["src/**/*", "../../test"] + "include": ["src/**/*"] } \ No newline at end of file diff --git a/packages/equalizer/tsconfig.json b/packages/equalizer/tsconfig.json index ae35afda0..ff19098dc 100644 --- a/packages/equalizer/tsconfig.json +++ b/packages/equalizer/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "@discord-player/tsconfig/base.json", - "include": ["src/**/*", "../../test", "src/biquad"] + "include": ["src/**/*"] } \ No newline at end of file diff --git a/scripts/bootstrap.js b/scripts/bootstrap.js new file mode 100644 index 000000000..bed468ce4 --- /dev/null +++ b/scripts/bootstrap.js @@ -0,0 +1,102 @@ +/* eslint-disable */ + +const { stripIndents } = require('common-tags'); +const { copy, exists, ensureDir, writeFile } = require('fs-extra'); +const chalk = require('ansi-colors'); + +const destName = process.argv[2]; +const DP_PKG = /^@discord\-player\/(.+)$/; +const TARGET_DIR = `${__dirname}/../packages`; + +function getData(name) { + name = name.startsWith('@discord-player/') ? name : `@discord-player/${name}`; + + return [ + { name: 'package.json', data: getPackageJSON(name) }, + { name: 'README.md', data: getReadMe(name) }, + { name: 'tsconfig.json', data: getTSConfig() } + ]; +} + +function getTSConfig() { + return JSON.stringify( + { + extends: '@discord-player/tsconfig/base.json', + include: ['src/**/*'] + }, + null, + 4 + ); +} + +function getReadMe(name) { + return stripIndents`# \`${name}\`\n + Discord Player \`${name}\` library\n + ## Installation\n + \`\`\`sh + $ npm install --save ${name} + \`\`\`\n + ## Example\n + \`\`\`js\nimport pkg from "${name}"\n\`\`\`\n`; +} + +function getPackageJSON(name) { + const packageJson = JSON.stringify( + { + name, + version: '0.1.0', + description: 'A complete framework to simplify the implementation of music commands for Discord bots', + keywords: ['discord-player', 'music', 'bot', 'discord.js', 'javascript', 'voip', 'lavalink', 'lavaplayer'], + author: 'Androz2091 ', + homepage: 'https://discord-player.js.org', + license: 'MIT', + main: 'dist/index.js', + module: 'dist/index.mjs', + types: 'dist/index.d.ts', + files: ['dist'], + repository: { + type: 'git', + url: 'git+https://github.com/Androz2091/discord-player.git' + }, + scripts: { + build: 'tsup', + 'build:check': 'tsc --noEmit', + lint: 'eslint src --ext .ts --fix' + }, + bugs: { + url: 'https://github.com/Androz2091/discord-player/issues' + }, + devDependencies: { + '@discord-player/tsconfig': '*', + tsup: '^6.2.2' + } + }, + null, + 2 + ); + + return packageJson; +} + +async function main() { + if (!destName) return console.log(chalk.redBright('✘ Package name is required!')); + const match = destName.match(DP_PKG); + const name = match?.[1] || destName; + + if (await exists(`${TARGET_DIR}/${name}`)) return console.log(chalk.redBright(`✘ Cannot create ${name} as it already exists.`)); + + console.log(chalk.cyanBright(`▲ Generating project...`)); + await ensureDir(`${TARGET_DIR}/${name}`); + + await copy(`${__dirname}/template`, `${TARGET_DIR}/${name}`); + + for (const data of getData(destName)) { + await writeFile(`${TARGET_DIR}/${name}/${data.name}`, data.data); + } + + console.log(chalk.greenBright(`✔ Successfully bootstrapped ${name}!`)); +} + +main().catch((e) => { + console.log(`${chalk.redBright('Failed to bootstrap!')}\n\n${chalk.red(`${e}`)}`); +}); diff --git a/scripts/template/LICENSE b/scripts/template/LICENSE new file mode 100644 index 000000000..fe07fc736 --- /dev/null +++ b/scripts/template/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Androz2091 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/scripts/template/src/index.ts b/scripts/template/src/index.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/scripts/template/src/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/scripts/template/tsup.config.ts b/scripts/template/tsup.config.ts new file mode 100644 index 000000000..e8bba970a --- /dev/null +++ b/scripts/template/tsup.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + clean: true, + bundle: true, + dts: true, + format: ['cjs', 'esm'], + keepNames: true, + minify: false, + entry: ['./src/index.ts'], + skipNodeModulesBundle: true, + sourcemap: true, + target: 'ES2020', + silent: true +}); From 55d1b58df23952ed6b4799ca474394d9ee259ad9 Mon Sep 17 00:00:00 2001 From: skdhg <46562212+skdhg@users.noreply.github.com> Date: Fri, 13 Jan 2023 15:46:32 +0545 Subject: [PATCH 3/4] ci: concurrency config --- .github/workflows/docs-deploy.yml | 52 ++++++++++++++------------ .github/workflows/eslint.yml | 12 +++--- .github/workflows/npm-publish-test.yml | 8 ++-- .github/workflows/npm-publish.yml | 6 ++- 4 files changed, 41 insertions(+), 37 deletions(-) diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml index b77a0226c..714123c2b 100644 --- a/.github/workflows/docs-deploy.yml +++ b/.github/workflows/docs-deploy.yml @@ -1,29 +1,33 @@ -name: Deployment +name: Docs Deployment on: - push: - branches: - - '*' - - '!docs' - - '!gh-pages' - tags: - - '*' + workflow_dispatch: + push: + branches: + - '*' + - '!docs' + - '!gh-pages' + tags: + - '*' +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: - docs: - name: Documentation - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@master + docs: + name: Documentation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@master - - name: Install Node v18 - uses: actions/setup-node@master - with: - node-version: 18 + - name: Install Node v18 + uses: actions/setup-node@master + with: + node-version: 18 - - name: Install dependencies - run: yarn install --immutable + - name: Install dependencies + run: yarn install --immutable - - name: Build and deploy documentation - uses: discordjs/action-docs@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Build and deploy documentation + uses: discordjs/action-docs@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index 643291678..f07c7d6cc 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -1,19 +1,17 @@ name: ESLint on: - push: - branches: - - '*' - - '!docs' - - '!develop' - - '!master' - - '!gh-pages' + workflow_dispatch: pull_request: + push: branches: - '*' - '!docs' - '!develop' - '!master' - '!gh-pages' +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: test: name: ESLint (Node v16) diff --git a/.github/workflows/npm-publish-test.yml b/.github/workflows/npm-publish-test.yml index f33719348..1c364c27d 100644 --- a/.github/workflows/npm-publish-test.yml +++ b/.github/workflows/npm-publish-test.yml @@ -1,9 +1,9 @@ name: Node.js Package - on: - release: - types: [created] - + workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: publish-npm: runs-on: ubuntu-latest diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 5134e9d57..cd144366e 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -1,9 +1,11 @@ name: Node.js Package - on: + workflow_dispatch: release: types: [created] - +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: publish-npm: runs-on: ubuntu-latest From d2241c6eb8902dbb5cba5d54aef87ad39e804813 Mon Sep 17 00:00:00 2001 From: skdhg <46562212+skdhg@users.noreply.github.com> Date: Fri, 13 Jan 2023 15:51:18 +0545 Subject: [PATCH 4/4] ci: workflow configurations --- .github/workflows/eslint.yml | 7 +++++-- packages/core/src/classes/PlayerNodeManager.ts | 4 +++- packages/discord-player/package.json | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index f07c7d6cc..50ba6dd63 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -14,13 +14,13 @@ concurrency: cancel-in-progress: true jobs: test: - name: ESLint (Node v16) + name: ESLint runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v2 - - name: Install Node v16 + - name: Install Node v18 uses: actions/setup-node@v1 with: node-version: 18 @@ -28,6 +28,9 @@ jobs: - name: Install dependencies run: yarn install --immutable + - name: Build + run: yarn build + - name: Run ESLint run: yarn lint diff --git a/packages/core/src/classes/PlayerNodeManager.ts b/packages/core/src/classes/PlayerNodeManager.ts index b65361da1..620dd819c 100644 --- a/packages/core/src/classes/PlayerNodeManager.ts +++ b/packages/core/src/classes/PlayerNodeManager.ts @@ -61,7 +61,9 @@ export class PlayerNodeManager extends EventEmitter { } // TODO - public getLeastBusy() {} + public getLeastBusy() { + return; + } public send(workerRes: WorkerResolvable, data: ServicePayload) { const worker = this.resolveWorker(workerRes); diff --git a/packages/discord-player/package.json b/packages/discord-player/package.json index 331f4d1f9..b76bb861f 100644 --- a/packages/discord-player/package.json +++ b/packages/discord-player/package.json @@ -20,7 +20,7 @@ "scripts": { "dev": "cd examples/test && ts-node index.ts", "build": "tsup", - "build:check": "tsc --noEmit --incremental false", + "build:check": "tsc --noEmit", "docs": "typedoc --json ../../docs/typedoc.json ./src/index.ts", "postdocs": "node ../../scripts/docgen.js", "lint": "eslint src --ext .ts --fix"