From 038fed3bc81a74767a5c88df55fe5eac7d188261 Mon Sep 17 00:00:00 2001 From: Googlefan256 Date: Thu, 30 Nov 2023 10:09:00 +0900 Subject: [PATCH] update: 4.0.0 alpha --- .github/workflows/build.yaml | 34 + .prettierrc | 4 + .vscode/settings.json | 4 + Dockerfile | 12 + package.json | 94 +-- pnpm-lock.yaml | 1213 ++++++++++++++++++++++++++++++++++ src/client.ts | 29 + src/command.ts | 268 ++++++++ src/commands/dc.ts | 12 - src/commands/delete.ts | 37 -- src/commands/index.ts | 37 -- src/commands/loop.ts | 57 -- src/commands/np.ts | 37 -- src/commands/pause.ts | 11 - src/commands/play.ts | 102 --- src/commands/playlist.ts | 79 --- src/commands/queue.ts | 52 -- src/commands/restart.ts | 24 - src/commands/resume.ts | 11 - src/commands/search.ts | 17 - src/commands/shuffle.ts | 18 - src/commands/skip.ts | 18 - src/commands/status.ts | 75 --- src/commands/stop.ts | 24 - src/commands/update.ts | 58 -- src/commands/volume.ts | 37 -- src/embed.ts | 8 + src/env.ts | 27 + src/event.ts | 21 + src/index.ts | 70 +- src/lib/client.ts | 36 - src/lib/env.ts | 25 - src/lib/helper/embed.ts | 13 - src/lib/helper/index.ts | 4 - src/lib/helper/link.ts | 11 - src/lib/helper/search.ts | 80 --- src/lib/helper/status.ts | 64 -- src/lib/index.ts | 6 - src/lib/logger.ts | 30 - src/lib/manager/command.ts | 45 -- src/lib/manager/index.ts | 2 - src/lib/manager/voice.ts | 365 ---------- src/lib/update.ts | 68 -- src/logger.ts | 30 + src/voice.ts | 122 ++++ src/yt.ts | 35 + tsconfig.json | 20 +- 47 files changed, 1882 insertions(+), 1564 deletions(-) create mode 100644 .github/workflows/build.yaml create mode 100644 .prettierrc create mode 100644 .vscode/settings.json create mode 100644 Dockerfile create mode 100644 pnpm-lock.yaml create mode 100644 src/client.ts create mode 100644 src/command.ts delete mode 100644 src/commands/dc.ts delete mode 100644 src/commands/delete.ts delete mode 100644 src/commands/index.ts delete mode 100644 src/commands/loop.ts delete mode 100644 src/commands/np.ts delete mode 100644 src/commands/pause.ts delete mode 100644 src/commands/play.ts delete mode 100644 src/commands/playlist.ts delete mode 100644 src/commands/queue.ts delete mode 100644 src/commands/restart.ts delete mode 100644 src/commands/resume.ts delete mode 100644 src/commands/search.ts delete mode 100644 src/commands/shuffle.ts delete mode 100644 src/commands/skip.ts delete mode 100644 src/commands/status.ts delete mode 100644 src/commands/stop.ts delete mode 100644 src/commands/update.ts delete mode 100644 src/commands/volume.ts create mode 100644 src/embed.ts create mode 100644 src/env.ts create mode 100644 src/event.ts delete mode 100644 src/lib/client.ts delete mode 100644 src/lib/env.ts delete mode 100644 src/lib/helper/embed.ts delete mode 100644 src/lib/helper/index.ts delete mode 100644 src/lib/helper/link.ts delete mode 100644 src/lib/helper/search.ts delete mode 100644 src/lib/helper/status.ts delete mode 100644 src/lib/index.ts delete mode 100644 src/lib/logger.ts delete mode 100644 src/lib/manager/command.ts delete mode 100644 src/lib/manager/index.ts delete mode 100644 src/lib/manager/voice.ts delete mode 100644 src/lib/update.ts create mode 100644 src/logger.ts create mode 100644 src/voice.ts create mode 100644 src/yt.ts diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..ffa56e2 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,34 @@ +name: Build and Publish Docker + +on: + workflow_dispatch: + +jobs: + build_and_push: + runs-on: ubuntu-latest + env: + IMAGE_NAME: yt-bot + steps: + - name: checkout + uses: actions/checkout@v3 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v4 + with: + platforms: linux/amd64,linux/arm64 + context: . + push: true + tags: | + ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:latest + ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ github.sha }} diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..14225fa --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 4, + "useTabs": false +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..89d1965 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode" +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..be80465 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM node:slim AS build +WORKDIR /app +COPY package.json ./ +RUN npm install -g pnpm && pnpm install +COPY . . +RUN pnpm run build +FROM node:slim +WORKDIR /app +COPY --from=build /app/dist ./dist +COPY --from=build /app/package.json ./package.json +RUN npm install --omit=dev +CMD ["node", "dist/index.js"] \ No newline at end of file diff --git a/package.json b/package.json index a4140fd..92eb739 100644 --- a/package.json +++ b/package.json @@ -1,50 +1,50 @@ { - "name": "yt-bot", - "version": "3.0.0-alpha.0.0.3", - "description": "Youtube bot for discord", - "type": "commonjs", - "scripts": { - "start": "node dist/index.js", - "build": "ncc build src/index.ts -o dist && terser dist/index.js -o dist/index.js && terser dist/502.index.js -o dist/502.index.js", - "format": "prettier --write \"src/**/*.ts\"", - "dev": "NODE_ENV=development ts-node src/index.ts" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/googlefan256/yt-bot.git" - }, - "author": "Googlefan256 (https://googlefan.net/)", - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/googlefan256/yt-bot/issues" - }, - "homepage": "https://github.com/googlefan256/yt-bot#readme", - "dependencies": { - "@discordjs/opus": "^0.9.0", - "@discordjs/voice": "^0.14.0", - "chalk": "4.1.2", - "discord.js": "^14.6.0", - "dotenv": "^16.0.3", - "ffmpeg-static": "^5.1.0", - "opusscript": "^0.0.8", - "tweetnacl": "^1.0.3", - "yt-search": "^2.10.3", - "ytdl-core": "^4.11.2" - }, - "devDependencies": { - "@types/yt-search": "^2.3.2", - "@vercel/ncc": "^0.36.0", - "prettier": "^2.7.1", - "terser": "^5.16.1", - "typescript": "^4.8.4" - }, - "engines": { - "node": ">=16.0.0" - }, - "licenses": [ - { - "type": "Apache-2.0", - "url": "https://www.apache.org/licenses/LICENSE-2.0" + "name": "yt-bot", + "version": "4.0.0-alpha-1", + "description": "Youtube bot for discord", + "type": "commonjs", + "scripts": { + "start": "node dist/index.js", + "build": "rimraf dist && tsc", + "format": "prettier --write \"src/**/*.ts\"", + "dev": "cross-env NODE_ENV=development ts-node src/index.ts" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/googlefan256/yt-bot.git" + }, + "author": "Googlefan256 (https://googlefan.net/)", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/googlefan256/yt-bot/issues" + }, + "homepage": "https://github.com/googlefan256/yt-bot#readme", + "devDependencies": { + "@types/yt-search": "^2.10.3", + "cross-env": "^7.0.3", + "prettier": "^3.1.0", + "rimraf": "^5.0.5", + "ts-node": "^10.9.1", + "typescript": "^5.3.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "licenses": [ + { + "type": "Apache-2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0" + } + ], + "dependencies": { + "@discordjs/voice": "^0.16.1", + "chalk": "^4.1.2", + "discord.js": "^14.14.1", + "dotenv": "^16.3.1", + "ffmpeg-static": "^5.2.0", + "opusscript": "^0.0.8", + "tweetnacl": "^1.0.3", + "yt-search": "^2.10.4", + "ytdl-core": "^4.11.5" } - ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..bf41bac --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1213 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + '@discordjs/voice': + specifier: ^0.16.1 + version: 0.16.1(ffmpeg-static@5.2.0)(opusscript@0.0.8) + chalk: + specifier: ^4.1.2 + version: 4.1.2 + discord.js: + specifier: ^14.14.1 + version: 14.14.1 + dotenv: + specifier: ^16.3.1 + version: 16.3.1 + ffmpeg-static: + specifier: ^5.2.0 + version: 5.2.0 + opusscript: + specifier: ^0.0.8 + version: 0.0.8 + tweetnacl: + specifier: ^1.0.3 + version: 1.0.3 + yt-search: + specifier: ^2.10.4 + version: 2.10.4 + ytdl-core: + specifier: ^4.11.5 + version: 4.11.5 + +devDependencies: + '@types/yt-search': + specifier: ^2.10.3 + version: 2.10.3 + cross-env: + specifier: ^7.0.3 + version: 7.0.3 + prettier: + specifier: ^3.1.0 + version: 3.1.0 + rimraf: + specifier: ^5.0.5 + version: 5.0.5 + ts-node: + specifier: ^10.9.1 + version: 10.9.1(@types/node@20.10.0)(typescript@5.3.2) + typescript: + specifier: ^5.3.2 + version: 5.3.2 + +packages: + + /@cspotcode/source-map-support@0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + dev: true + + /@derhuerst/http-basic@8.2.4: + resolution: {integrity: sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw==} + engines: {node: '>=6.0.0'} + dependencies: + caseless: 0.12.0 + concat-stream: 2.0.0 + http-response-object: 3.0.2 + parse-cache-control: 1.0.1 + dev: false + + /@discordjs/builders@1.7.0: + resolution: {integrity: sha512-GDtbKMkg433cOZur8Dv6c25EHxduNIBsxeHrsRoIM8+AwmEZ8r0tEpckx/sHwTLwQPOF3e2JWloZh9ofCaMfAw==} + engines: {node: '>=16.11.0'} + dependencies: + '@discordjs/formatters': 0.3.3 + '@discordjs/util': 1.0.2 + '@sapphire/shapeshift': 3.9.3 + discord-api-types: 0.37.61 + fast-deep-equal: 3.1.3 + ts-mixer: 6.0.3 + tslib: 2.6.2 + dev: false + + /@discordjs/collection@1.5.3: + resolution: {integrity: sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==} + engines: {node: '>=16.11.0'} + dev: false + + /@discordjs/collection@2.0.0: + resolution: {integrity: sha512-YTWIXLrf5FsrLMycpMM9Q6vnZoR/lN2AWX23/Cuo8uOOtS8eHB2dyQaaGnaF8aZPYnttf2bkLMcXn/j6JUOi3w==} + engines: {node: '>=18'} + dev: false + + /@discordjs/formatters@0.3.3: + resolution: {integrity: sha512-wTcI1Q5cps1eSGhl6+6AzzZkBBlVrBdc9IUhJbijRgVjCNIIIZPgqnUj3ntFODsHrdbGU8BEG9XmDQmgEEYn3w==} + engines: {node: '>=16.11.0'} + dependencies: + discord-api-types: 0.37.61 + dev: false + + /@discordjs/rest@2.2.0: + resolution: {integrity: sha512-nXm9wT8oqrYFRMEqTXQx9DUTeEtXUDMmnUKIhZn6O2EeDY9VCdwj23XCPq7fkqMPKdF7ldAfeVKyxxFdbZl59A==} + engines: {node: '>=16.11.0'} + dependencies: + '@discordjs/collection': 2.0.0 + '@discordjs/util': 1.0.2 + '@sapphire/async-queue': 1.5.0 + '@sapphire/snowflake': 3.5.1 + '@vladfrangu/async_event_emitter': 2.2.2 + discord-api-types: 0.37.61 + magic-bytes.js: 1.5.0 + tslib: 2.6.2 + undici: 5.27.2 + dev: false + + /@discordjs/util@1.0.2: + resolution: {integrity: sha512-IRNbimrmfb75GMNEjyznqM1tkI7HrZOf14njX7tCAAUetyZM1Pr8hX/EK2lxBCOgWDRmigbp24fD1hdMfQK5lw==} + engines: {node: '>=16.11.0'} + dev: false + + /@discordjs/voice@0.16.1(ffmpeg-static@5.2.0)(opusscript@0.0.8): + resolution: {integrity: sha512-uiWiW0Ta6K473yf8zs13RfKuPqm/xU4m4dAidMkIdwqgy1CztbbZBtPLfDkVSKzpW7s6m072C+uQcs4LwF3FhA==} + engines: {node: '>=16.11.0'} + dependencies: + '@types/ws': 8.5.10 + discord-api-types: 0.37.61 + prism-media: 1.3.5(ffmpeg-static@5.2.0)(opusscript@0.0.8) + tslib: 2.6.2 + ws: 8.14.2 + transitivePeerDependencies: + - '@discordjs/opus' + - bufferutil + - ffmpeg-static + - node-opus + - opusscript + - utf-8-validate + dev: false + + /@discordjs/ws@1.0.2: + resolution: {integrity: sha512-+XI82Rm2hKnFwAySXEep4A7Kfoowt6weO6381jgW+wVdTpMS/56qCvoXyFRY0slcv7c/U8My2PwIB2/wEaAh7Q==} + engines: {node: '>=16.11.0'} + dependencies: + '@discordjs/collection': 2.0.0 + '@discordjs/rest': 2.2.0 + '@discordjs/util': 1.0.2 + '@sapphire/async-queue': 1.5.0 + '@types/ws': 8.5.9 + '@vladfrangu/async_event_emitter': 2.2.2 + discord-api-types: 0.37.61 + tslib: 2.6.2 + ws: 8.14.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + + /@fastify/busboy@2.1.0: + resolution: {integrity: sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==} + engines: {node: '>=14'} + dev: false + + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + string-width-cjs: /string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: true + + /@jridgewell/resolve-uri@3.1.1: + resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + dev: true + + /@jridgewell/trace-mapping@0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + dev: true + optional: true + + /@sapphire/async-queue@1.5.0: + resolution: {integrity: sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + dev: false + + /@sapphire/shapeshift@3.9.3: + resolution: {integrity: sha512-WzKJSwDYloSkHoBbE8rkRW8UNKJiSRJ/P8NqJ5iVq7U2Yr/kriIBx2hW+wj2Z5e5EnXL1hgYomgaFsdK6b+zqQ==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + dependencies: + fast-deep-equal: 3.1.3 + lodash: 4.17.21 + dev: false + + /@sapphire/snowflake@3.5.1: + resolution: {integrity: sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + dev: false + + /@tsconfig/node10@1.0.9: + resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + dev: true + + /@tsconfig/node12@1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + dev: true + + /@tsconfig/node14@1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + dev: true + + /@tsconfig/node16@1.0.4: + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + dev: true + + /@types/node@10.17.60: + resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==} + dev: false + + /@types/node@20.10.0: + resolution: {integrity: sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==} + dependencies: + undici-types: 5.26.5 + + /@types/ws@8.5.10: + resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} + dependencies: + '@types/node': 20.10.0 + dev: false + + /@types/ws@8.5.9: + resolution: {integrity: sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==} + dependencies: + '@types/node': 20.10.0 + dev: false + + /@types/yt-search@2.10.3: + resolution: {integrity: sha512-kH4Yzs/edH9/s/Wj58WhV73qpiRedWeNy5V202BqpXPd+xUFM8M3ASLdUMYUbzzlUDoGorDm4JpeN6iNx3sOjw==} + dev: true + + /@vladfrangu/async_event_emitter@2.2.2: + resolution: {integrity: sha512-HIzRG7sy88UZjBJamssEczH5q7t5+axva19UbZLO6u0ySbYPrwzWiXBcC0WuHyhKKoeCyneH+FvYzKQq/zTtkQ==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + dev: false + + /acorn-walk@8.3.0: + resolution: {integrity: sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==} + engines: {node: '>=0.4.0'} + dev: true + + /acorn@8.11.2: + resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + + /ansi-regex@2.1.1: + resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} + engines: {node: '>=0.10.0'} + dev: false + + /ansi-regex@3.0.1: + resolution: {integrity: sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==} + engines: {node: '>=4'} + dev: false + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + dev: true + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + + /ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + dev: true + + /arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + dev: true + + /async.parallellimit@0.5.2: + resolution: {integrity: sha512-4Di2nFsb3jL7aUIICvRSvtw/oynpMIx0JrwYn5hqJI661Dd+mYBi2ElOukOQgRHihU1SCTapb86Vx/Snva5M1w==} + dependencies: + async.util.eachoflimit: 0.5.2 + async.util.parallel: 0.5.2 + dev: false + + /async.util.eachoflimit@0.5.2: + resolution: {integrity: sha512-oZksH0sBW0AEOJKgBCQ79io9DZruoRBLTAea/Ik36pejR7pDpByvtXeuJsoZdPwSVslsrQcsUfucbUaiXYBnAQ==} + dependencies: + async.util.keyiterator: 0.5.2 + async.util.noop: 0.5.2 + async.util.once: 0.5.2 + async.util.onlyonce: 0.5.2 + dev: false + + /async.util.isarray@0.5.2: + resolution: {integrity: sha512-wbUzlrwON8RUgi+v/rhF0U99Ce8Osjcn+JP/mFNg6ymvShcobAOvE6cvLajSY5dPqKCOE1xfdhefgBif11zZgw==} + dev: false + + /async.util.isarraylike@0.5.2: + resolution: {integrity: sha512-DbFpsz3ZFNkohAW8IpGTlm8gotU32zpqe3Y2XkEA/G3XNO6rmUTKPpo7XgXUruoI+AsGi8+0zWpJHe7t1sLiAg==} + dependencies: + async.util.isarray: 0.5.2 + dev: false + + /async.util.keyiterator@0.5.2: + resolution: {integrity: sha512-cktrETawCwgu13y3KZs2uMGFnNHc+IjKPZsavtRaoCjLELkePb2co4zrr+ghPvEqLXZIJPTKqC2HFZgJTssMVw==} + dependencies: + async.util.isarraylike: 0.5.2 + async.util.keys: 0.5.2 + dev: false + + /async.util.keys@0.5.2: + resolution: {integrity: sha512-umCOCRCRYwIC2Ho3fbuhKwIIe7OhQsVoVKGoF5GoQiGJUmjP4TG0Bmmcdpm7yW/znoIGKpnjKzVQz0niH4tfqw==} + dev: false + + /async.util.noop@0.5.2: + resolution: {integrity: sha512-AdwShXwE0KoskgqVJAck8zcM32nIHj3AC8ZN62ZaR5srhrY235Nw18vOJZWxcOfhxdVM0hRVKM8kMx7lcl7cCQ==} + dev: false + + /async.util.once@0.5.2: + resolution: {integrity: sha512-YQ5WPzDTt2jlblUDkq2I5RV/KiAJErJ4/0cEFhYPaZzqIuF/xDzdGvnEKe7UeuoMszsVPeajzcpKgkbwdb9MUA==} + dev: false + + /async.util.onlyonce@0.5.2: + resolution: {integrity: sha512-UgQvkU9JZ+I0Cm1f56XyGXcII+J3d/5XWUuHpcevlItuA3WFSJcqZrsyAUck2FkRSD8BwYQX1zUTDp3SJMVESg==} + dev: false + + /async.util.parallel@0.5.2: + resolution: {integrity: sha512-0bEvwmQ8fxsTYNwacw5iq0i3PvGryRkXxZ01Rvox21izdMdls9IH2rAZjfunbgI8j6nFRyIdCmMINQ9kka99ow==} + dependencies: + async.util.isarraylike: 0.5.2 + async.util.noop: 0.5.2 + async.util.restparam: 0.5.2 + dev: false + + /async.util.restparam@0.5.2: + resolution: {integrity: sha512-Q9Z+zgmtMxFX5i7CnBvNOkgrL5hptztCqwarQluyNudUUk4iCmyjmsQl8MuQEjNh3gGqP5ayvDaextL1VXXgIg==} + dev: false + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + /boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + dev: false + + /boolstring@1.0.2: + resolution: {integrity: sha512-0JLNSmZUv1m/O8sVayFm2t0naiOXwQ9O2Gq9u1eoIkhvu6U5NQER/e3k4BGpjZ33G775lWMT7TzJ7r5VtmEnbQ==} + dev: false + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: false + + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: true + + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: false + + /caseless@0.12.0: + resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} + dev: false + + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: false + + /cheerio-select@2.1.0: + resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + dependencies: + boolbase: 1.0.0 + css-select: 5.1.0 + css-what: 6.1.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.1.0 + dev: false + + /cheerio@1.0.0-rc.12: + resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} + engines: {node: '>= 6'} + dependencies: + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.1.0 + htmlparser2: 8.0.2 + parse5: 7.1.2 + parse5-htmlparser2-tree-adapter: 7.0.0 + dev: false + + /cli-color@1.2.0: + resolution: {integrity: sha512-AqfwItf/UqGif3FBErI3NHX04v5ywJtGYlL5z4OqWR50u7g+Fz3Xw2qcCIbKVPrqtJCBwSOkDgnSlHbcpwDKHw==} + dependencies: + ansi-regex: 2.1.1 + d: 1.0.1 + es5-ext: 0.10.62 + es6-iterator: 2.0.3 + memoizee: 0.4.15 + timers-ext: 0.1.7 + dev: false + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: false + + /concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 + dev: false + + /create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true + + /cross-env@7.0.3: + resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} + engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} + hasBin: true + dependencies: + cross-spawn: 7.0.3 + dev: true + + /cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: true + + /css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.1.0 + nth-check: 2.1.1 + dev: false + + /css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + dev: false + + /d@1.0.1: + resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==} + dependencies: + es5-ext: 0.10.62 + type: 1.2.0 + dev: false + + /dasu@0.4.3: + resolution: {integrity: sha512-AFwspl5k7V8MW8H7tyIGJ0gtOauUg7JC+DgiRFUIXvPNNDFXTMtvnCkZY0macN6JLGqBjNP38WVnQN7Iv3RSlg==} + dev: false + + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: false + + /diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dev: true + + /discord-api-types@0.37.61: + resolution: {integrity: sha512-o/dXNFfhBpYHpQFdT6FWzeO7pKc838QeeZ9d91CfVAtpr5XLK4B/zYxQbYgPdoMiTDvJfzcsLW5naXgmHGDNXw==} + dev: false + + /discord.js@14.14.1: + resolution: {integrity: sha512-/hUVzkIerxKHyRKopJy5xejp4MYKDPTszAnpYxzVVv4qJYf+Tkt+jnT2N29PIPschicaEEpXwF2ARrTYHYwQ5w==} + engines: {node: '>=16.11.0'} + dependencies: + '@discordjs/builders': 1.7.0 + '@discordjs/collection': 1.5.3 + '@discordjs/formatters': 0.3.3 + '@discordjs/rest': 2.2.0 + '@discordjs/util': 1.0.2 + '@discordjs/ws': 1.0.2 + '@sapphire/snowflake': 3.5.1 + '@types/ws': 8.5.9 + discord-api-types: 0.37.61 + fast-deep-equal: 3.1.3 + lodash.snakecase: 4.1.1 + tslib: 2.6.2 + undici: 5.27.2 + ws: 8.14.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + + /dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + dev: false + + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + dev: false + + /domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + dev: false + + /domutils@3.1.0: + resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dev: false + + /dotenv@16.3.1: + resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==} + engines: {node: '>=12'} + dev: false + + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: true + + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: true + + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: true + + /entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + dev: false + + /env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + dev: false + + /es5-ext@0.10.62: + resolution: {integrity: sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==} + engines: {node: '>=0.10'} + requiresBuild: true + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.3 + next-tick: 1.1.0 + dev: false + + /es6-iterator@2.0.3: + resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + es6-symbol: 3.1.3 + dev: false + + /es6-symbol@3.1.3: + resolution: {integrity: sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==} + dependencies: + d: 1.0.1 + ext: 1.7.0 + dev: false + + /es6-weak-map@2.0.3: + resolution: {integrity: sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + es6-iterator: 2.0.3 + es6-symbol: 3.1.3 + dev: false + + /event-emitter@0.3.5: + resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + dev: false + + /ext@1.7.0: + resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + dependencies: + type: 2.7.2 + dev: false + + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: false + + /ffmpeg-static@5.2.0: + resolution: {integrity: sha512-WrM7kLW+do9HLr+H6tk7LzQ7kPqbAgLjdzNE32+u3Ff11gXt9Kkkd2nusGFrlWMIe+XaA97t+I8JS7sZIrvRgA==} + engines: {node: '>=16'} + requiresBuild: true + dependencies: + '@derhuerst/http-basic': 8.2.4 + env-paths: 2.2.1 + https-proxy-agent: 5.0.1 + progress: 2.0.3 + transitivePeerDependencies: + - supports-color + dev: false + + /foreground-child@3.1.1: + resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + engines: {node: '>=14'} + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + dev: true + + /glob@10.3.10: + resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.3.6 + minimatch: 9.0.3 + minipass: 7.0.4 + path-scurry: 1.10.1 + dev: true + + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: false + + /htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.1.0 + entities: 4.5.0 + dev: false + + /http-response-object@3.0.2: + resolution: {integrity: sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==} + dependencies: + '@types/node': 10.17.60 + dev: false + + /https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + + /human-time@0.0.2: + resolution: {integrity: sha512-sbYI90YhYmstslPTb70BLGjy6mdESa0lxL7uDR4fIVAx9Iobz8fLEqi7FqF4Q/6vblrzZALg//MsYJlIPBU8SA==} + dev: false + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: false + + /is-fullwidth-code-point@2.0.0: + resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==} + engines: {node: '>=4'} + dev: false + + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: true + + /is-promise@2.2.2: + resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} + dev: false + + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true + + /jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + dev: true + + /jsonpath-plus@5.0.7: + resolution: {integrity: sha512-7TS6wsiw1s2UMK/A6nA4n0aUJuirCVhJ87nWX5je5MPOl0z5VTr2qs7nMP8NZ2ed3rlt6kePTqddgVPE9F0i0w==} + engines: {node: '>=10.0.0'} + dev: false + + /keypress@0.2.1: + resolution: {integrity: sha512-HjorDJFNhnM4SicvaUXac0X77NiskggxJdesG72+O5zBKpSqKFCrqmndKVqpu3pFqkla0St6uGk8Ju0sCurrmg==} + dev: false + + /lodash.snakecase@4.1.1: + resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} + dev: false + + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: false + + /lru-cache@10.1.0: + resolution: {integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==} + engines: {node: 14 || >=16.14} + dev: true + + /lru-queue@0.1.0: + resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} + dependencies: + es5-ext: 0.10.62 + dev: false + + /m3u8stream@0.8.6: + resolution: {integrity: sha512-LZj8kIVf9KCphiHmH7sbFQTVe4tOemb202fWwvJwR9W5ENW/1hxJN6ksAWGhQgSBSa3jyWhnjKU1Fw1GaOdbyA==} + engines: {node: '>=12'} + dependencies: + miniget: 4.2.3 + sax: 1.3.0 + dev: false + + /magic-bytes.js@1.5.0: + resolution: {integrity: sha512-wJkXvutRbNWcc37tt5j1HyOK1nosspdh3dj6LUYYAvF6JYNqs53IfRvK9oEpcwiDA1NdoIi64yAMfdivPeVAyw==} + dev: false + + /make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true + + /memoizee@0.4.15: + resolution: {integrity: sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + es6-weak-map: 2.0.3 + event-emitter: 0.3.5 + is-promise: 2.2.2 + lru-queue: 0.1.0 + next-tick: 1.1.0 + timers-ext: 0.1.7 + dev: false + + /miniget@4.2.3: + resolution: {integrity: sha512-SjbDPDICJ1zT+ZvQwK0hUcRY4wxlhhNpHL9nJOB2MEAXRGagTljsO8MEDzQMTFf0Q8g4QNi8P9lEm/g7e+qgzA==} + engines: {node: '>=12'} + dev: false + + /minimatch@3.0.8: + resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==} + dependencies: + brace-expansion: 1.1.11 + dev: false + + /minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: false + + /minipass@7.0.4: + resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} + engines: {node: '>=16 || 14 >=14.17'} + dev: true + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: false + + /next-tick@1.1.0: + resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + dev: false + + /node-fzf@0.5.3: + resolution: {integrity: sha512-crN8rRfApu/GUrtKq+zJ6LueUyNAOJpFHxoT2Ru1Q+OYRa/F/H7CXvzcMrFc7D964yakYZEZ9XR3YbdSHXgyCw==} + hasBin: true + dependencies: + cli-color: 1.2.0 + keypress: 0.2.1 + minimist: 1.2.8 + redstar: 0.0.2 + string-width: 2.1.1 + ttys: 0.0.3 + dev: false + + /nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + dependencies: + boolbase: 1.0.0 + dev: false + + /opusscript@0.0.8: + resolution: {integrity: sha512-VSTi1aWFuCkRCVq+tx/BQ5q9fMnQ9pVZ3JU4UHKqTkf0ED3fKEPdr+gKAAl3IA2hj9rrP6iyq3hlcJq3HELtNQ==} + dev: false + + /parse-cache-control@1.0.1: + resolution: {integrity: sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==} + dev: false + + /parse5-htmlparser2-tree-adapter@7.0.0: + resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} + dependencies: + domhandler: 5.0.3 + parse5: 7.1.2 + dev: false + + /parse5@7.1.2: + resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + dependencies: + entities: 4.5.0 + dev: false + + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: true + + /path-scurry@1.10.1: + resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + lru-cache: 10.1.0 + minipass: 7.0.4 + dev: true + + /prettier@3.1.0: + resolution: {integrity: sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==} + engines: {node: '>=14'} + hasBin: true + dev: true + + /prism-media@1.3.5(ffmpeg-static@5.2.0)(opusscript@0.0.8): + resolution: {integrity: sha512-IQdl0Q01m4LrkN1EGIE9lphov5Hy7WWlH6ulf5QdGePLlPas9p2mhgddTEHrlaXYjjFToM1/rWuwF37VF4taaA==} + peerDependencies: + '@discordjs/opus': '>=0.8.0 <1.0.0' + ffmpeg-static: ^5.0.2 || ^4.2.7 || ^3.0.0 || ^2.4.0 + node-opus: ^0.3.3 + opusscript: ^0.0.8 + peerDependenciesMeta: + '@discordjs/opus': + optional: true + ffmpeg-static: + optional: true + node-opus: + optional: true + opusscript: + optional: true + dependencies: + ffmpeg-static: 5.2.0 + opusscript: 0.0.8 + dev: false + + /progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + dev: false + + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + dev: false + + /redstar@0.0.2: + resolution: {integrity: sha512-VNvLaLxMJMYiAasJX5Q/GC+Os7FXL0yPWFDuTodhR7Na9wqzrXsePPWC+EtIv4t3q5DyAK00w423xi5mQN2fqg==} + dependencies: + minimatch: 3.0.8 + dev: false + + /rimraf@5.0.5: + resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==} + engines: {node: '>=14'} + hasBin: true + dependencies: + glob: 10.3.10 + dev: true + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: false + + /sax@1.3.0: + resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==} + dev: false + + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + dev: true + + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: true + + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: true + + /string-width@2.1.1: + resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==} + engines: {node: '>=4'} + dependencies: + is-fullwidth-code-point: 2.0.0 + strip-ansi: 4.0.0 + dev: false + + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: true + + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + dev: true + + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /strip-ansi@4.0.0: + resolution: {integrity: sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==} + engines: {node: '>=4'} + dependencies: + ansi-regex: 3.0.1 + dev: false + + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: true + + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 + dev: true + + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: false + + /timers-ext@0.1.7: + resolution: {integrity: sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==} + dependencies: + es5-ext: 0.10.62 + next-tick: 1.1.0 + dev: false + + /ts-mixer@6.0.3: + resolution: {integrity: sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==} + dev: false + + /ts-node@10.9.1(@types/node@20.10.0)(typescript@5.3.2): + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.10.0 + acorn: 8.11.2 + acorn-walk: 8.3.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.3.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + dev: false + + /ttys@0.0.3: + resolution: {integrity: sha512-UCqXRZS2S7U4aVB7Salj3ChPRSsb57ogJpJ1eMCvyowxFOBGsaHKcRU8bovcDwajX1mRbv0IpUnYkoG7Ieo5Zg==} + engines: {node: '>= 0.6.0'} + dev: false + + /tweetnacl@1.0.3: + resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} + dev: false + + /type@1.2.0: + resolution: {integrity: sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==} + dev: false + + /type@2.7.2: + resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==} + dev: false + + /typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + dev: false + + /typescript@5.3.2: + resolution: {integrity: sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + /undici@5.27.2: + resolution: {integrity: sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ==} + engines: {node: '>=14.0'} + dependencies: + '@fastify/busboy': 2.1.0 + dev: false + + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: false + + /v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + dev: true + + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + dev: true + + /ws@8.14.2: + resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: false + + /yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + dev: true + + /yt-search@2.10.4: + resolution: {integrity: sha512-cJeSdCxQYEV+gKxCeWbOcqgkHtyxLRF2Pt/57AJValzaf07JbJmRIoT9SR/XG2Ed6HIEEuD06WcgZS+6qRvajQ==} + hasBin: true + dependencies: + async.parallellimit: 0.5.2 + boolstring: 1.0.2 + cheerio: 1.0.0-rc.12 + dasu: 0.4.3 + human-time: 0.0.2 + jsonpath-plus: 5.0.7 + minimist: 1.2.8 + node-fzf: 0.5.3 + dev: false + + /ytdl-core@4.11.5: + resolution: {integrity: sha512-27LwsW4n4nyNviRCO1hmr8Wr5J1wLLMawHCQvH8Fk0hiRqrxuIu028WzbJetiYH28K8XDbeinYW4/wcHQD1EXA==} + engines: {node: '>=12'} + dependencies: + m3u8stream: 0.8.6 + miniget: 4.2.3 + sax: 1.3.0 + dev: false diff --git a/src/client.ts b/src/client.ts new file mode 100644 index 0000000..a044936 --- /dev/null +++ b/src/client.ts @@ -0,0 +1,29 @@ +import { Client, GatewayIntentBits, Options } from "discord.js"; +import { env } from "./env"; + +export class Bot extends Client { + constructor() { + super({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.GuildVoiceStates, + GatewayIntentBits.MessageContent, + ], + makeCache: Options.cacheWithLimits({ + MessageManager: 0, + GuildMessageManager: 0, + }), + shards: "auto", + allowedMentions: { + parse: [], + repliedUser: false, + }, + }); + } + async start() { + await super.login(env.DiscordToken); + } +} + +export const bot: Bot = new Bot(); diff --git a/src/command.ts b/src/command.ts new file mode 100644 index 0000000..580d7d8 --- /dev/null +++ b/src/command.ts @@ -0,0 +1,268 @@ +import { GuildTextBasedChannel, Message } from "discord.js"; +import { ys } from "./yt"; +import { connect, disconnect, get } from "./voice"; +import { Embed } from "./embed"; + +type Command = { + name: string; + alias: string[]; + run: (message: Message, args: string[]) => void | Promise; +}; + +export const commands: Command[] = [ + { + name: "ping", + alias: [], + run: ping, + }, + { + name: "play", + alias: [], + run: play, + }, + { + name: "join", + alias: [], + run: join, + }, + { + name: "skip", + alias: ["s"], + run: skip, + }, + { + name: "pause", + alias: [], + run: pause, + }, + { + name: "resume", + alias: [], + run: resume, + }, + { + name: "stop", + alias: [], + run: stop, + }, + { + name: "loop", + alias: [], + run: loop, + }, + { + name: "queueloop", + alias: [], + run: queueloop, + }, + { + name: "help", + alias: ["h"], + run: help, + }, + { + name: "stop", + alias: ["clear"], + run: stop, + }, + { + name: "dc", + alias: ["disconnect"], + run: dc, + }, + { + name: "queue", + alias: ["q"], + run: queue, + }, +]; + +async function queue(message: Message, args: string[]) { + if (!message.member?.voice.channelId) { + return; + } + const { player } = get(message.guildId!); + if (!player) return; + if (!player.queue.length) { + await message.reply("キューは空です。"); + return; + } + const desc = player.queue.map((q, i) => { + return { + name: `[${q.title}](https://youtube.com/watch?v=${q.id})`, + value: q.description, + }; + }); + await message.reply({ + embeds: [ + new Embed({ + title: "キュー", + fields: desc, + footer: { + text: `ループ: ${ + player.looping ? "有効" : "無効" + } キューループ: ${player.queuelooping ? "有効" : "無効"}`, + }, + }), + ], + }); +} + +async function join(message: Message, args: string[]) { + if (!message.member?.voice.channel) return; + const channel = message.member.voice.channel; + const _conn = connect(channel, message.channel as GuildTextBasedChannel); + await message.reply(`${channel.toString()}へ接続しました。`); +} + +async function dc(message: Message, args: string[]) { + if (!message.member?.voice.channelId) { + return; + } + disconnect(message.guildId!); +} + +async function ping(message: Message, args: string[]) { + let now = Date.now(); + const msg = await message.reply("計測中です..."); + now = Date.now() - now; + await msg.edit( + `Websocket:\`${message.client.ws.ping}\`ms\nRest:\`${now}\`ms`, + ); +} + +async function skip(message: Message, args: string[]) { + if (!message.member?.voice.channelId) { + return; + } + const { player } = get(message.guildId!); + if (!player) return; + player.skip(); +} + +async function pause(message: Message, args: string[]) { + if (!message.member?.voice.channelId) { + return; + } + const { player } = get(message.guildId!); + if (!player) return; + player.pause(); +} + +async function resume(message: Message, args: string[]) { + if (!message.member?.voice.channelId) { + return; + } + const { player } = get(message.guildId!); + if (!player) return; + player.resume(); +} + +async function stop(message: Message, args: string[]) { + if (!message.member?.voice.channelId) { + return; + } + const { player } = get(message.guildId!); + if (!player) return; + player.clear(); +} + +async function loop(message: Message, args: string[]) { + if (!message.member?.voice.channelId) { + return; + } + const { player } = get(message.guildId!); + if (!player) return; + player.looping = !player.looping; + await message.reply( + `ループを${player.looping ? "有効" : "無効"}にしました。`, + ); +} + +async function queueloop(message: Message, args: string[]) { + if (!message.member?.voice.channelId) { + return; + } + const { player } = get(message.guildId!); + if (!player) return; + player.queuelooping = !player.queuelooping; + await message.reply( + `キューループを${player.queuelooping ? "有効" : "無効"}にしました。`, + ); +} + +async function help(message: Message, args: string[]) { + await message.reply({ + embeds: [ + new Embed({ + title: "ヘルプ", + description: "コマンド一覧", + fields: [ + { + name: "ping", + value: "pingを送信します。", + }, + { + name: "play", + value: "音楽を再生します。", + }, + { + name: "join", + value: "ボイスチャンネルに接続します。", + }, + { + name: "skip", + value: "再生中の音楽をスキップします。", + }, + { + name: "pause", + value: "再生中の音楽を一時停止します。", + }, + { + name: "resume", + value: "再生中の音楽を再開します。", + }, + { + name: "stop", + value: "再生中の音楽を停止します。", + }, + { + name: "loop", + value: "再生中の音楽をループします。", + }, + { + name: "queueloop", + value: "キューをループします。", + }, + { + name: "help", + value: "ヘルプを表示します。", + }, + { + name: "dc", + value: "ボイスチャンネルから切断します。", + }, + ], + }), + ], + }); +} + +async function play(message: Message, args: string[]) { + if (!message.member?.voice.channelId) { + return; + } + const { player } = get(message.guildId!); + if (!player) return; + const arg = args.join(" "); + if (!arg) { + await message.reply("再生する内容が必須です。"); + return; + } + const yt = await ys(arg); + if (!yt) { + await message.reply("見つかりませんでした。"); + return; + } + await player.push(yt); +} diff --git a/src/commands/dc.ts b/src/commands/dc.ts deleted file mode 100644 index a3c701b..0000000 --- a/src/commands/dc.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Bot, CommandOptions } from "../lib"; -import { PlayerDestroyReason } from "../lib/manager/voice"; - -export default { - name: "dc", - description: "切断します", - async exec(i) { - const player = await (i.client as Bot).player.getPlayer(i); - if (!player) return; - return player.destroy(PlayerDestroyReason.Stopped, i); - }, -} as CommandOptions; diff --git a/src/commands/delete.ts b/src/commands/delete.ts deleted file mode 100644 index 8f6d603..0000000 --- a/src/commands/delete.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { SlashCommandIntegerOption } from "discord.js"; -import { Bot, CommandOptions, EmbedBuilder } from "../lib"; - -export default { - name: "delete", - description: "キューから指定した曲を削除します", - options: [ - new SlashCommandIntegerOption() - .setName("曲") - .setDescription("削除するトラックを指定します") - .setRequired(true), - ], - async exec(i) { - const player = await (i.client as Bot).player.getPlayer(i); - if (!player) return; - const index = i.options.getInteger("曲", true); - if (index > player.tracks.length || index < 1) { - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription("指定された曲は存在しません。"), - ], - ephemeral: true, - }); - } else { - player.tracks = player.tracks.filter((_, i) => i !== index); - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("情報") - .setDescription(`キューから${index}番目の曲を削除しました。`), - ], - }); - } - }, -} as CommandOptions; diff --git a/src/commands/index.ts b/src/commands/index.ts deleted file mode 100644 index e55f4d8..0000000 --- a/src/commands/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -import Disconnect from "./dc"; -import Loop from "./loop"; -import Pause from "./pause"; -import Play from "./play"; -import Queue from "./queue"; -import Resume from "./resume"; -import Skip from "./skip"; -import NowPlaying from "./np"; -import Volume from "./volume"; -import Search from "./search"; -import Status from "./status"; -import Delete from "./delete"; -import Shuffle from "./shuffle"; -import Update from "./update"; -import Stop from "./stop"; -import Restart from "./restart"; -import Playlist from "./playlist"; - -export default [ - Play, - Queue, - Skip, - NowPlaying, - Pause, - Resume, - Disconnect, - Loop, - Volume, - Search, - Status, - Delete, - Shuffle, - Update, - Stop, - Restart, - Playlist, -]; diff --git a/src/commands/loop.ts b/src/commands/loop.ts deleted file mode 100644 index c9a35fb..0000000 --- a/src/commands/loop.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { SlashCommandStringOption } from "discord.js"; -import { Bot, CommandOptions, EmbedBuilder } from "../lib"; -import { PlayerLoopState } from "../lib/manager/voice"; - -export default { - name: "loop", - description: "ループモードを切り替えます", - options: [ - new SlashCommandStringOption() - .setName("モード") - .setDescription("ループモードを指定します") - .setRequired(true) - .addChoices( - { name: "無効", value: "none" }, - { name: "現在の曲", value: "current" }, - { name: "キュー", value: "track" } - ), - ], - async exec(i) { - const player = await (i.client as Bot).player.getPlayer(i); - if (!player) return; - const mode = i.options.getString("モード", true); - const state = - mode === "none" - ? PlayerLoopState.None - : mode === "current" - ? PlayerLoopState.Current - : PlayerLoopState.Track; - if (player.loopstate === state) { - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription("すでにそのループモードは有効です。"), - ], - ephemeral: true, - }); - } else { - player.loopstate = state; - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("情報") - .setDescription( - `ループモードを${ - mode === "none" - ? "無効" - : mode === "current" - ? "現在の曲" - : "キュー" - }に変更しました。` - ), - ], - }); - } - }, -} as CommandOptions; diff --git a/src/commands/np.ts b/src/commands/np.ts deleted file mode 100644 index cc4b64a..0000000 --- a/src/commands/np.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Bot, CommandOptions, EmbedBuilder } from "../lib"; - -export default { - name: "np", - description: "現在再生中の曲を表示します", - async exec(i) { - const player = await (i.client as Bot).player.getPlayer(i); - if (!player) return; - const track = player.current; - if (!track) - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription("現在再生中の曲はありません。"), - ], - }); - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("情報 - " + track.title) - .setDescription(track.description) - .setAuthor({ - name: track.author.name, - url: track.author.url, - }) - .setThumbnail(track.thumbnail) - .setURL(track.url) - .setFooter({ - text: `再生中です | ${ - i.client.users.resolve(track.requester)?.tag - }`, - }), - ], - }); - }, -} as CommandOptions; diff --git a/src/commands/pause.ts b/src/commands/pause.ts deleted file mode 100644 index ac04965..0000000 --- a/src/commands/pause.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Bot, CommandOptions } from "../lib"; - -export default { - name: "pause", - description: "一時停止します", - async exec(i) { - const player = await (i.client as Bot).player.getPlayer(i); - if (!player) return; - player.pause(i); - }, -} as CommandOptions; diff --git a/src/commands/play.ts b/src/commands/play.ts deleted file mode 100644 index 8dbabe2..0000000 --- a/src/commands/play.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { - SlashCommandStringOption, - type TextChannel, - type VoiceChannel, -} from "discord.js"; -import { VideoSearchResult } from "yt-search"; -import { - type Bot, - type CommandOptions, - EmbedBuilder, - Voice, - search, - getVideoId, - getVideoInfo, -} from "../lib"; - -export default { - name: "play", - description: "音楽を再生します", - options: [ - new SlashCommandStringOption() - .setName("曲") - .setDescription("再生する曲のURLまたはキーワード") - .setRequired(true), - ], - async exec(i) { - let player = i.guildId - ? (i.client as Bot).player.get(i.guildId) - : undefined; - if (!i.guild?.voiceStates.cache.get(i.user.id)?.channelId) { - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription("ボイスチャンネルに接続してください。"), - ], - ephemeral: true, - }); - } - if ( - player && - player.vchannel.id !== - i.guild?.voiceStates.cache.get(i.user.id)?.channelId - ) { - await i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription("同じボイスチャンネルに接続してください。"), - ], - ephemeral: true, - }); - return null; - } - if (!player) { - player = new Voice( - i.client as Bot, - i.guild?.voiceStates.cache.get(i.user.id)?.channel as VoiceChannel, - i.channel as TextChannel - ); - } - const query = i.options.getString("曲", true); - const id = getVideoId(query); - let r: VideoSearchResult | null; - if (id) { - const v = await getVideoInfo(id); - if (v) { - r = Object.assign(v, { - type: "video" as const, - }); - } else { - r = null; - } - } else { - r = (await search(query))[0]; - } - if (!r) { - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription("曲が見つかりませんでした。"), - ], - ephemeral: true, - }); - } - const res = Object.assign(r, { - requester: i.user.id, - }); - if (!res) { - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription("曲が見つかりませんでした。"), - ], - ephemeral: true, - }); - } - player.add(res, i); - }, -} as CommandOptions; diff --git a/src/commands/playlist.ts b/src/commands/playlist.ts deleted file mode 100644 index ee52f65..0000000 --- a/src/commands/playlist.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { SlashCommandStringOption, TextChannel, VoiceChannel } from "discord.js"; -import { Bot, EmbedBuilder, searchPlaylist, search, Voice, type CommandOptions } from "../lib"; -import { search as yts } from "yt-search"; - -export default { - name: "playlist", - description: "playlistから再生します", - options: [ - new SlashCommandStringOption() - .setName("曲") - .setDescription("再生する曲のURLまたはキーワード") - .setRequired(true), - ], - async exec(i) { - let player = i.guildId - ? (i.client as Bot).player.get(i.guildId) - : undefined; - if (!i.guild?.voiceStates.cache.get(i.user.id)?.channelId) { - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription("ボイスチャンネルに接続してください。"), - ], - ephemeral: true, - }); - } - if ( - player && - player.vchannel.id !== - i.guild?.voiceStates.cache.get(i.user.id)?.channelId - ) { - await i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription("同じボイスチャンネルに接続してください。"), - ], - ephemeral: true, - }); - return null; - } - if (!player) { - player = new Voice( - i.client as Bot, - i.guild?.voiceStates.cache.get(i.user.id)?.channel as VoiceChannel, - i.channel as TextChannel - ); - } - const query = i.options.getString("曲", true); - const r = (await searchPlaylist(query))[0]; - if (!r) { - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription("曲が見つかりませんでした。"), - ], - ephemeral: true, - }); - } - const res = Object.assign(await yts({ - listId: r.listId, - }), { - requester: i.user.id, - }); - if (!res) { - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription("リストが見つかりませんでした。"), - ], - ephemeral: true, - }); - } - player.addList(res, i); - }, -} as CommandOptions; diff --git a/src/commands/queue.ts b/src/commands/queue.ts deleted file mode 100644 index 7a7f2d2..0000000 --- a/src/commands/queue.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { type Bot, type CommandOptions, EmbedBuilder } from "../lib"; -import { PlayerLoopState } from "../lib/manager/voice"; - -export default { - name: "q", - description: "キューを表示します", - async exec(i) { - const player = await (i.client as Bot).player.getPlayer(i); - if (!player) return; - if (!i.guild) return; - return i.reply({ - embeds: [ - new EmbedBuilder() - .setAuthor({ - name: i.guild.name, - iconURL: i.guild.iconURL() || undefined, - }) - .addFields({ - name: "現在再生中", - value: player.current - ? `[${player.current.title}](${player.current.url})\n> \`${ - i.client.users.resolve(player.current.requester)?.tag || "" - }\`` - : "現在再生中の曲はありません", - }) - .setThumbnail(player.current?.thumbnail || null) - .setFooter({ - text: `${ - player.loopstate === PlayerLoopState.Current - ? "ループは有効です" - : player.loopstate === PlayerLoopState.Track - ? "トラックループは有効です" - : "ループは無効です" - } | 音量は${player.volume}に設定されています`, - }) - .addFields( - ...player.tracks.slice(0, 24).map( - (v, index) => - [ - { - name: `${index + 1}番目`, - value: `[${v.title}](${v.url})\n> \`${ - i.client.users.resolve(v.requester)?.tag || "" - }\``, - }, - ][0] - ) - ), - ], - }); - }, -} as CommandOptions; diff --git a/src/commands/restart.ts b/src/commands/restart.ts deleted file mode 100644 index cff13eb..0000000 --- a/src/commands/restart.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { EmbedBuilder, env, type CommandOptions } from "../lib"; -import { Updater } from "../lib"; - -export default { - name: "restart", - description: "botを再起動します", - async exec(i) { - if (!env.Owners.includes(i.user.id)) - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription("このコマンドは開発者のみが実行できます"), - ], - }); - const updater = new Updater(); - await i.reply({ - embeds: [ - new EmbedBuilder().setTitle("情報").setDescription("再起動します"), - ], - }); - updater.restart(); - }, -} as CommandOptions; diff --git a/src/commands/resume.ts b/src/commands/resume.ts deleted file mode 100644 index 42cb4b9..0000000 --- a/src/commands/resume.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Bot, CommandOptions } from "../lib"; - -export default { - name: "resume", - description: "再開します", - async exec(i) { - const player = await (i.client as Bot).player.getPlayer(i); - if (!player) return; - player.resume(i); - }, -} as CommandOptions; diff --git a/src/commands/search.ts b/src/commands/search.ts deleted file mode 100644 index 1e9f983..0000000 --- a/src/commands/search.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { SlashCommandStringOption } from "discord.js"; -import { type CommandOptions, Embedlize } from "../lib"; - -export default { - name: "search", - description: "youtubeから音楽を検索します", - options: [ - new SlashCommandStringOption() - .setName("query") - .setDescription("検索する文字列を指定します") - .setRequired(true), - ], - async exec(i) { - await i.deferReply(); - return i.editReply(await Embedlize(i.options.getString("query", true))); - }, -} as CommandOptions; diff --git a/src/commands/shuffle.ts b/src/commands/shuffle.ts deleted file mode 100644 index 4f3def4..0000000 --- a/src/commands/shuffle.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Bot, CommandOptions, EmbedBuilder } from "../lib"; - -export default { - name: "shuffle", - description: "シャッフルします", - async exec(i) { - const player = await (i.client as Bot).player.getPlayer(i); - if (!player) return; - player.shuffle(); - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("情報") - .setDescription("シャッフルしました"), - ], - }); - }, -} as CommandOptions; diff --git a/src/commands/skip.ts b/src/commands/skip.ts deleted file mode 100644 index b58addf..0000000 --- a/src/commands/skip.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { type Bot, type CommandOptions, EmbedBuilder } from "../lib"; - -export default { - name: "skip", - description: "現在再生中の音楽をスキップします", - async exec(i) { - const player = await (i.client as Bot).player.getPlayer(i); - if (!player) return; - await player.skip(); - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("情報") - .setDescription("スキップしました。"), - ], - }); - }, -} as CommandOptions; diff --git a/src/commands/status.ts b/src/commands/status.ts deleted file mode 100644 index d7afbe6..0000000 --- a/src/commands/status.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { - type CommandOptions, - EmbedBuilder, - getCpu, - getMemory, - getUptime, - type Bot, - formatTime, -} from "../lib"; - -export default { - name: "status", - description: "botのステータスを表示します", - async exec(i) { - await i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("情報") - .setDescription("ステータスを取得しています。"), - ], - }); - const [cpu, memory, uptime] = await Promise.all([ - getCpu(), - getMemory(), - getUptime(), - ]); - return i.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle("情報") - .setDescription("ステータスを取得しました。") - .addFields( - { name: "cpu使用率", value: `${cpu}%`, inline: true }, - { - name: "プロセスid", - value: `${process.pid}`, - inline: true, - }, - { - name: "OS", - value: `${process.platform}-${process.arch}`, - inline: true, - }, - { - name: "WebSocket速度", - value: `${i.client.ws.ping}ms`, - inline: true, - }, - { - name: "接続vc数", - value: (i.client as Bot).player.size + "vc", - inline: true, - }, - { - name: "参加サーバー数", - value: i.client.guilds.cache.size + "サーバー", - inline: true, - }, - { - name: "メモリ使用量", - value: `プロセス: ${memory.process}MB,全体: ${memory.all}MB,合計メモリ: ${memory.total}MB`, - inline: false, - }, - { - name: "起動時間", - value: `プロセス: ${formatTime( - uptime.process - )},サーバー: ${formatTime(uptime.all)}`, - inline: false, - } - ), - ], - }); - }, -} as CommandOptions; diff --git a/src/commands/stop.ts b/src/commands/stop.ts deleted file mode 100644 index f8ba927..0000000 --- a/src/commands/stop.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { EmbedBuilder, env, type CommandOptions } from "../lib"; -import { Updater } from "../lib"; - -export default { - name: "stop", - description: "botを停止します", - async exec(i) { - if (!env.Owners.includes(i.user.id)) - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription("このコマンドは開発者のみが実行できます"), - ], - }); - const updater = new Updater(); - await i.reply({ - embeds: [ - new EmbedBuilder().setTitle("情報").setDescription("停止します"), - ], - }); - updater.exit(); - }, -} as CommandOptions; diff --git a/src/commands/update.ts b/src/commands/update.ts deleted file mode 100644 index 3d9d2b0..0000000 --- a/src/commands/update.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { type CommandOptions, env, EmbedBuilder, Updater } from "../lib"; - -export default { - name: "update", - description: "botのアップデートを行います", - async exec(i) { - if (!env.Owners.includes(i.user.id)) - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription("このコマンドは開発者のみが実行できます"), - ], - }); - const updater = new Updater(); - const status = updater.pull(); - if (!status) { - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription("すでに最新です"), - ], - }); - } else { - await i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("情報") - .setDescription( - "アップデートを開始します\n依存関係をインストールします" - ), - ], - }); - updater.install(); - await i.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle("情報") - .setDescription( - "アップデートを開始します\n依存関係をインストールします\n依存関係のインストールが完了しました\nスクリプトをビルドします" - ), - ], - }); - updater.build(); - await i.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle("情報") - .setDescription( - "アップデートを開始します\n依存関係をインストールします\n依存関係のインストールが完了しました\nスクリプトをビルドします\nスクリプトのビルドが完了しました\n再起動します" - ), - ], - }); - updater.restart(); - } - }, -} as CommandOptions; diff --git a/src/commands/volume.ts b/src/commands/volume.ts deleted file mode 100644 index d9654cf..0000000 --- a/src/commands/volume.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { SlashCommandIntegerOption } from "discord.js"; -import { type Bot, type CommandOptions, EmbedBuilder } from "../lib"; - -export default { - name: "volume", - description: "音量を変更します", - options: [ - new SlashCommandIntegerOption() - .setName("音量") - .setDescription("音量を指定します") - .setRequired(false) - .setMaxValue(200) - .setMinValue(0), - ], - async exec(i) { - const player = await (i.client as Bot).player.getPlayer(i); - if (!player) return; - const volume = i.options.getInteger("音量"); - if (!volume) { - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("情報") - .setDescription(`現在の音量は${player.volume * 500}です`), - ], - }); - } - player.setVolume(volume / 500); - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("情報") - .setDescription(`音量を${volume}に変更しました。`), - ], - }); - }, -} as CommandOptions; diff --git a/src/embed.ts b/src/embed.ts new file mode 100644 index 0000000..8b251d1 --- /dev/null +++ b/src/embed.ts @@ -0,0 +1,8 @@ +import { EmbedBuilder, type EmbedData, Colors } from "discord.js"; +import { env } from "./env"; + +export class Embed extends EmbedBuilder { + constructor(data: Omit) { + super({ ...data, color: Colors[env.Color] }); + } +} diff --git a/src/env.ts b/src/env.ts new file mode 100644 index 0000000..8cf4702 --- /dev/null +++ b/src/env.ts @@ -0,0 +1,27 @@ +import type { Colors } from "discord.js"; +import { panic } from "./logger"; +import { config } from "dotenv"; + +interface Env { + DiscordToken: string; + OwnerId: string[]; + Color: keyof typeof Colors; + Prefix: string; +} + +function loadEnv(): Env { + config(); + const DiscordToken = process.env.DISCORD_TOKEN!; + if (!DiscordToken) panic("Discord token not found"); + const OwnerId = process.env.OWNER_ID?.split(",") ?? []; + const Color = (process.env.COLOR ?? "Blue") as keyof typeof Colors; + const Prefix = process.env.PREFIX || "!"; + return { + DiscordToken, + OwnerId, + Color, + Prefix, + }; +} + +export const env = loadEnv(); diff --git a/src/event.ts b/src/event.ts new file mode 100644 index 0000000..811a820 --- /dev/null +++ b/src/event.ts @@ -0,0 +1,21 @@ +import { Message } from "discord.js"; +import { Bot } from "./client"; +import { info } from "./logger"; +import { commands } from "./command"; +import { env } from "./env"; + +export function onReady(client: Bot) { + info(`Logged in as ${client.user.tag}.`); +} + +export async function onMessage(message: Message) { + if (!message.content.startsWith(env.Prefix)) { + return; + } + const content = message.content.slice(env.Prefix.length); + const [name, ...args] = content.split(/[  ]+/g); + if (name === "") return; + const cmd = commands.find((c) => c.name == name || c.alias.includes(name)); + if (!cmd) return message.reply("コマンドが見つかりませんでした。"); + await cmd.run(message, args); +} diff --git a/src/index.ts b/src/index.ts index 0bb6ad0..db48738 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,60 +1,26 @@ -import { ActivityType } from "discord.js"; -import { Bot, env, logger, autoUpdate } from "./lib"; -import { createServer } from "node:http"; - -autoUpdate(logger); - -const bot = new Bot(); -bot.start(); - -bot.on("interactionCreate", async (i) => { - if (i.isChatInputCommand()) { - const cmd = bot.command.get(i.commandName); - if (!cmd) return; +import { Events } from "discord.js"; +import { bot } from "./client"; +import { onMessage, onReady } from "./event"; +import { error } from "./logger"; + +bot.once(Events.ClientReady, () => onReady(bot)); +bot.on(Events.MessageCreate, async (message) => { + if (!message.inGuild()) return; try { - await cmd.exec(i); - } catch (error) { - logger.error(error); - await i.reply( - "An error occured while executing this command.\nPlease try again later." - ); + await onMessage(message); + } catch (e) { + error(e); } - } }); -bot.on("error", logger.error); +(async () => { + await bot.start(); +})(); -bot.once("ready", () => { - setInterval(() => { - bot.user?.setActivity({ - name: "/help for help", - type: ActivityType.Streaming, - }); - setTimeout( - () => - bot.user?.setActivity({ - name: `${bot.player.size}vc / Version ${env.Version}`, - type: ActivityType.Streaming, - }), - 5000 - ); - }, 10000); - if (process.env.REPL_ID) { - logger.info("Seems to be running on repl.it"); - logger.info("Starting web server..."); - createServer((_, res) => { - res.writeHead(200); - res.end("ok"); - }).listen(Number(process.env.PORT) || 8080, () => { - logger.info("Web server started"); - }); - } +process.on("uncaughtException", (e) => { + error(e); }); -bot.once("ready", () => { - logger.info(`Logged in as ${bot.user?.tag}!`); +process.on("unhandledRejection", (e) => { + error(e); }); - -process.on("uncaughtException", logger.error); - -process.on("unhandledRejection", logger.error); diff --git a/src/lib/client.ts b/src/lib/client.ts deleted file mode 100644 index dd908e5..0000000 --- a/src/lib/client.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Client, User } from "discord.js"; -import { VoiceManager, CommandManager, env, logger } from "./"; - -export class Bot extends Client { - command: CommandManager; - player: VoiceManager; - owners: User[] = []; - constructor() { - super({ - intents: ["Guilds", "GuildIntegrations", "GuildVoiceStates"], - }); - this.command = new CommandManager(this); - this.player = new VoiceManager(this); - } - async start() { - this.command.loadAll(); - await this.login(env.DiscordToken); - this.owners = await new Promise((resolve) => - this.once("ready", () => - resolve(Promise.all(env.Owners.map((id) => this.users.fetch(id)))) - ) - ); - if (!this.application) return logger.panic("Application not found"); - await this.application.commands.fetch(); - await Promise.all( - this.command.map((c) => { - const m = this.application?.commands.cache.find( - (d) => d.name === c.raw.name && d.description === c.raw.description - ); - if (m) return m.edit(c.raw); - else return this.application?.commands.create(c.raw); - }) - ); - return null; - } -} diff --git a/src/lib/env.ts b/src/lib/env.ts deleted file mode 100644 index ad895cd..0000000 --- a/src/lib/env.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { config } from "dotenv"; -import { logger } from "./"; -import { Colors } from "discord.js"; -import { readFileSync } from "fs"; - -config(); - -if (!process.env.DISCORD_TOKEN) logger.panic("DISCORD_TOKEN is not defined"); -const Owners = (process.env.OWNER_ID || "") - .split(",") - .filter((x) => x.length > 0); - -export const env = { - Color: - (process.env.COLOR || "Green") in Colors - ? Colors[(process.env.COLOR || "Green") as keyof typeof Colors] - : (() => { - logger.warn("Invalid color, defaulting to Green"); - return Colors.Green; - })(), - DiscordToken: process.env.DISCORD_TOKEN as string, - Owners, - Version: JSON.parse(readFileSync("./package.json", "utf-8")) - .version as string, -}; diff --git a/src/lib/helper/embed.ts b/src/lib/helper/embed.ts deleted file mode 100644 index 0e5fbff..0000000 --- a/src/lib/helper/embed.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { EmbedBuilder as BaseEmbedBuiler, EmbedData } from "discord.js"; -import { env } from "../env"; - -export class EmbedBuilder extends BaseEmbedBuiler { - constructor(arg: Partial> = {}) { - super( - Object.assign(arg, { - color: env.Color, - footer: { text: "yt-bot by Googlefan" }, - }) - ); - } -} diff --git a/src/lib/helper/index.ts b/src/lib/helper/index.ts deleted file mode 100644 index 4a93e66..0000000 --- a/src/lib/helper/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { LinkButtonBuilder } from "./link"; -export { EmbedBuilder } from "./embed"; -export * from "./search"; -export * from "./status"; diff --git a/src/lib/helper/link.ts b/src/lib/helper/link.ts deleted file mode 100644 index ced10d1..0000000 --- a/src/lib/helper/link.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { - type LinkButtonComponentData, - ButtonBuilder, - ButtonStyle, -} from "discord.js"; - -export class LinkButtonBuilder extends ButtonBuilder { - constructor(arg: Partial> = {}) { - super(Object.assign(arg, { style: ButtonStyle.Link })); - } -} diff --git a/src/lib/helper/search.ts b/src/lib/helper/search.ts deleted file mode 100644 index 824c878..0000000 --- a/src/lib/helper/search.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { ActionRowBuilder, type ButtonBuilder } from "discord.js"; -import { LinkButtonBuilder, EmbedBuilder } from "./"; -import { search as yts } from "yt-search"; -import ytdl, { getVideoID } from "ytdl-core"; - -export const search = (q: string) => - yts(q) - .then((v) => v.videos.slice(0, 5)) - .catch(() => []); - -export const getVideoInfo = (q: string) => - yts({ videoId: q }) - .then((v) => v) - .catch(() => null); - -export const getVideoId = (q: string) => { - try { - return getVideoID(q); - } catch { - return null; - } -}; - -export const searchPlaylist = (q: string) => - yts(q) - .then((v) => v.playlists.slice(0, 5)) - .catch(() => []); - -export const Embedlize = (q: string) => - search(q).then((results) => { - if (!results.length) - return { - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription( - "検索結果が見つかりませんでした...\n検索語句を変えて再検索してみてください\n(検索の過度な実行による制限の可能性もあります)" - ), - ], - }; - return { - embeds: [ - new EmbedBuilder() - .setTitle("YouTubeの検索結果") - .setDescription( - results - .map( - (result) => - `[${result.title}](${ - result.url - })\n> ${result.description.replace(/\n/g, "\n> ")}\n[${ - result.author.name - }](${result.author.url})` - ) - .join("\n\n") - ) - .setThumbnail(results[0].thumbnail), - ], - components: [ - new ActionRowBuilder().addComponents( - new LinkButtonBuilder() - .setURL( - `https://www.youtube.com/results?search_query=${encodeURIComponent( - q - )}` - ) - .setLabel("もっと見る") - .setEmoji("🔎") - ), - ], - }; - }); - -export const resolveId = (q: string) => { - try { - return ytdl.getVideoID(q); - } catch { - return null; - } -}; diff --git a/src/lib/helper/status.ts b/src/lib/helper/status.ts deleted file mode 100644 index d0ba8b9..0000000 --- a/src/lib/helper/status.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { cpus, freemem, totalmem, uptime } from "node:os"; - -export const getCpu = () => { - const before = getCurrent(); - return new Promise((resolve) => - setTimeout(() => { - const after = getCurrent(); - resolve( - Math.floor( - (1 - (after.idle - before.idle) / (after.total - before.total)) * - 10000 - ) / 100 - ); - }, 1000) - ); -}; - -export const getMemory = () => ({ - process: Math.floor((process.memoryUsage().rss / 1024 / 1024) * 100) / 100, - all: Math.floor(((totalmem() - freemem()) / 1024 / 1024) * 100) / 100, - total: Math.floor((totalmem() / 1024 / 1024) * 100) / 100, -}); - -const getCurrent = () => { - const current = cpus() - .map((x) => x.times) - .reduce( - (x, y) => - [ - { - user: x.user + y.user, - nice: x.nice + y.nice, - sys: x.sys + y.sys, - irq: x.irq + y.irq, - idle: x.idle + y.idle, - }, - ][0] - ); - return { - idle: current.idle, - total: - current.user + current.nice + current.sys + current.idle + current.irq, - }; -}; - -export const getUptime = () => { - return { - process: process.uptime(), - all: uptime(), - }; -}; - -export const formatTime = (time: number) => { - time = Math.floor(time); - const sec = time % 60; - time = (time - sec) / 60; - const min = time % 60; - time = (time - min) / 60; - const hour = time % 24; - time = (time - hour) / 24; - return `${time ? time + "日" : ""}${hour ? hour + "時間" : ""}${ - min ? min + "分" : "" - }${sec ? sec + "秒" : ""}`; -}; diff --git a/src/lib/index.ts b/src/lib/index.ts deleted file mode 100644 index f0a4243..0000000 --- a/src/lib/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { logger } from "./logger"; -export { env } from "./env"; -export * from "./manager"; -export { Bot } from "./client"; -export * from "./helper"; -export { autoUpdate, Updater } from "./update"; diff --git a/src/lib/logger.ts b/src/lib/logger.ts deleted file mode 100644 index be5ae4c..0000000 --- a/src/lib/logger.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { inspect } from "node:util"; -import chalk from "chalk"; - -function format(msg: any) { - return typeof msg === "string" ? msg : inspect(msg); -} - -export class Logger { - constructor() {} - info(msg: any) { - console.log(chalk.greenBright("INFO: ") + format(msg)); - } - warn(msg: any) { - console.log(chalk.yellowBright("WARN: ") + format(msg)); - } - error(msg: any) { - console.log(chalk.redBright("ERROR: ") + format(msg)); - } - panic(msg: any) { - console.log(chalk.red("ERROR: ") + format(msg)); - process.exit(1); - } - debug(msg: any) { - if (process.env.NODE_ENV === "development") { - console.log(chalk.blueBright("DEBUG: ") + format(msg)); - } - } -} - -export const logger = new Logger(); diff --git a/src/lib/manager/command.ts b/src/lib/manager/command.ts deleted file mode 100644 index 6548840..0000000 --- a/src/lib/manager/command.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - type ApplicationCommandOptionData, - Collection, - type ChatInputCommandInteraction, -} from "discord.js"; -import type { Bot } from "../"; -import commands from "../../commands"; - -export interface CommandOptions { - exec(i: ChatInputCommandInteraction): Promise | any; - name: string; - description: string; - options?: ApplicationCommandOptionData[]; -} - -export class Command implements CommandOptions { - exec: (i: ChatInputCommandInteraction) => Promise | any; - name: string; - description: string; - options?: ApplicationCommandOptionData[]; - constructor(options: CommandOptions) { - this.exec = options.exec; - this.name = options.name; - this.description = options.description; - this.options = options.options; - } - get raw() { - return { - name: this.name, - description: this.description, - options: this.options, - }; - } -} - -export class CommandManager extends Collection { - constructor(public client: Bot) { - super(); - } - loadAll() { - return commands.map((cmd) => { - this.set(cmd.name, new Command(cmd)); - }); - } -} diff --git a/src/lib/manager/index.ts b/src/lib/manager/index.ts deleted file mode 100644 index 2579f25..0000000 --- a/src/lib/manager/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { CommandManager, Command, CommandOptions } from "./command"; -export { VoiceManager, Voice } from "./voice"; diff --git a/src/lib/manager/voice.ts b/src/lib/manager/voice.ts deleted file mode 100644 index f34f054..0000000 --- a/src/lib/manager/voice.ts +++ /dev/null @@ -1,365 +0,0 @@ -import { - Collection, - type Snowflake, - type VoiceChannel, - type TextChannel, - type ChatInputCommandInteraction, - ActionRowBuilder, - type ButtonBuilder, -} from "discord.js"; -import { Bot, EmbedBuilder, LinkButtonBuilder } from "../"; -import EventEmitter from "node:events"; -import { - type AudioPlayer, - createAudioPlayer, - joinVoiceChannel, - VoiceConnection, - VoiceConnectionStatus, - createAudioResource, - StreamType, - AudioResource, - AudioPlayerStatus, - entersState, -} from "@discordjs/voice"; -import ytdl from "ytdl-core"; -import { PlaylistMetadataResult, VideoSearchResult } from "yt-search"; - -export enum PlayerLoopState { - None, - Current, - Track, -} - -export enum PlayerDestroyReason { - Empty, - Disconnected, - Stopped, -} - -export interface Video extends VideoSearchResult { - requester: Snowflake; -} - -export interface Playlist extends PlaylistMetadataResult{ - requester: Snowflake; -} - -export class Voice extends EventEmitter { - loopstate = PlayerLoopState.None; - volume = 0.2; - player: AudioPlayer; - tracks: Video[]; - guildId: Snowflake; - connection: VoiceConnection; - current?: Video; - constructor( - public client: Bot, - public vchannel: VoiceChannel, - public tchannel: TextChannel - ) { - super(); - this.tracks = []; - this.player = createAudioPlayer(); - this.vchannel = vchannel; - this.guildId = vchannel.guildId; - this.vchannel = vchannel; - this.client.player.set(vchannel.guildId, this); - this.connection = joinVoiceChannel({ - channelId: vchannel.id, - guildId: vchannel.guildId, - adapterCreator: vchannel.guild.voiceAdapterCreator, - }); - this.connection.subscribe(this.player); - - this.connection.on(VoiceConnectionStatus.Disconnected, async () => { - try { - await Promise.race([ - entersState(this.connection, VoiceConnectionStatus.Signalling, 5_000), - entersState(this.connection, VoiceConnectionStatus.Connecting, 5_000), - ]); - } catch (error) { - this.destroy(PlayerDestroyReason.Disconnected); - } - }); - } - onPlaying(video: Video, i?: ChatInputCommandInteraction) { - if (this.loopstate !== PlayerLoopState.Current) { - const payload = { - embeds: [ - new EmbedBuilder() - .setTitle("再生中") - .setDescription( - `**[${video.title}](${video.url})**\n${video.author.name}` - ) - .setThumbnail(video.thumbnail) - .setFooter({ text: `再生時間: ${video.timestamp}` }), - ], - components: [ - new ActionRowBuilder().addComponents( - new LinkButtonBuilder() - .setLabel("YouTube") - .setURL(video.url) - .setEmoji("🔗") - ), - ], - }; - return i?.reply ? i.reply(payload) : this.tchannel.send(payload); - } - } - onQueueAdd(video: Video, i?: ChatInputCommandInteraction) { - const payload = { - embeds: [ - new EmbedBuilder() - .setTitle("キューに追加しました") - .setDescription( - `**[${video.title}](${video.url})**\n${video.author.name}` - ) - .setThumbnail(video.thumbnail) - .setFooter({ text: `再生時間: ${video.timestamp}` }), - ], - components: [ - new ActionRowBuilder().addComponents( - new LinkButtonBuilder() - .setLabel("YouTube") - .setURL(video.url) - .setEmoji("🔗") - ), - ], - }; - return i?.reply ? i.reply(payload) : this.tchannel.send(payload); - } - onQueueAddPlaylist(list: Playlist, i?: ChatInputCommandInteraction) { - const payload = { - embeds: [ - new EmbedBuilder() - .setTitle("プレイリストをキューに追加しました") - .setDescription(`**${list.videos.length}**件追加しました。`), - ], - components: [ - new ActionRowBuilder().addComponents( - new LinkButtonBuilder() - .setLabel("YouTube") - .setURL(list.url) - .setEmoji("🔗") - ), - ] - }; - return i?.reply ? i.reply(payload) : this.tchannel.send(payload); - } - onDisconnect(i?: ChatInputCommandInteraction) { - const payload = { - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription("切断されたため再生を終了します。"), - ], - }; - return i?.reply ? i.reply(payload) : this.tchannel.send(payload); - } - onEmpty(i?: ChatInputCommandInteraction) { - const payload = { - embeds: [ - new EmbedBuilder() - .setTitle("情報") - .setDescription("キューが空になったため再生を終了します。"), - ], - }; - return i?.reply ? i.reply(payload) : this.tchannel.send(payload); - } - onStop(i?: ChatInputCommandInteraction) { - const payload = { - embeds: [ - new EmbedBuilder() - .setTitle("情報") - .setDescription("正常に切断しました。"), - ], - }; - return i?.reply ? i.reply(payload) : this.tchannel.send(payload); - } - private stream(id: string) { - const stream = ytdl(id, { - filter: (format) => - format.audioCodec === "opus" && format.container === "webm", - highWaterMark: 32 * 32 * 32 * 32 * 32, - }); - const resource = createAudioResource(stream, { - inputType: StreamType.WebmOpus, - inlineVolume: true, - }); - resource.volume?.setVolume(this.volume); - return this.playResource(resource); - } - private async playResource(resource: AudioResource) { - try { - return this.player.play(resource); - } catch { - return new Promise((resolve) => { - setTimeout(() => resolve(this.playResource(resource)), 2000); - }); - } - } - async add(video: Video, i?: ChatInputCommandInteraction) { - if (this.player.state.status === AudioPlayerStatus.Idle) { - this.current = video; - this.play(video); - return this.onPlaying(video, i); - } else { - this.tracks.push(video); - return this.onQueueAdd(video, i); - } - } - async addList(list: Playlist, i?: ChatInputCommandInteraction) { - const videos = list.videos.map((v) => ({ - ...v, - requester: list.requester, - type: "video" as const, - url: `https://www.youtube.com/watch?v=${v.videoId}`, - views: 0, - ago: "null", - duration: { - seconds: 0, - timestamp: "null", - toString: () => "null", - }, - seconds: 0, - thumbnail: "https://test.org/image", - image: "https://test.org/image", - description: "null", - timestamp: "null", - })); - if (this.player.state.status === AudioPlayerStatus.Idle) { - this.current = videos[0]; - this.tracks = videos.slice(1); - this.play(videos[0]); - return this.onPlaying(videos[0], i); - } else { - this.tracks.push(...videos); - return this.onQueueAddPlaylist(list, i); - } - } - async play(video: Video) { - await this.stream(video.videoId); - this.player.once(AudioPlayerStatus.Idle, async () => { - this.current = undefined; - if (this.loopstate === PlayerLoopState.Current) - this.tracks.unshift(video); - else if (this.loopstate === PlayerLoopState.Track) - this.tracks.push(video); - const next = this.tracks.shift(); - if (!next) { - return this.destroy(PlayerDestroyReason.Empty); - } - this.current = next; - this.onPlaying(next); - await new Promise((resolve) => setTimeout(resolve, 1000)); - return this.play(next); - }); - } - async destroy(reason: PlayerDestroyReason, i?: ChatInputCommandInteraction) { - this.client.player.delete(this.guildId); - this.player.stop(); - this.connection.destroy(); - if (reason === PlayerDestroyReason.Disconnected) { - return this.onDisconnect(i); - } else if (reason === PlayerDestroyReason.Empty) { - return this.onEmpty(i); - } else { - return this.onStop(i); - } - } - skip() { - if (!this.current) return; - this.player.stop(); - if (this.loopstate === PlayerLoopState.Current) - this.tracks.unshift(this.current); - else if (this.loopstate === PlayerLoopState.Track) - this.tracks.push(this.current); - this.current = undefined; - const next = this.tracks.shift(); - if (!next) { - return this.destroy(PlayerDestroyReason.Empty); - } - this.current = next; - return this.play(next); - } - setVolume(volume: number) { - this.volume = volume; - if (this.player.state.status === AudioPlayerStatus.Playing) { - this.player.state.resource.volume?.setVolume(volume); - } - } - shuffle() { - this.tracks = this.tracks.sort(() => Math.random() - 0.5); - } - pause(i: ChatInputCommandInteraction) { - if (this.player.pause()) { - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("情報") - .setDescription("一時停止しました。"), - ], - }); - } else { - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription("一時停止に失敗しました。"), - ], - }); - } - } - resume(i: ChatInputCommandInteraction) { - if (this.player.unpause()) { - return i.reply({ - embeds: [ - new EmbedBuilder().setTitle("情報").setDescription("再開しました。"), - ], - }); - } else { - return i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription("再開に失敗しました。"), - ], - }); - } - } -} - -export class VoiceManager extends Collection { - constructor(public client: Bot) { - super(); - } - async getPlayer(i: ChatInputCommandInteraction) { - const player = i.guildId ? this.get(i.guildId) : undefined; - if (!player) { - await i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription("このサーバーでは音楽が再生されていません。"), - ], - ephemeral: true, - }); - return null; - } - if ( - player.vchannel.id !== - i.guild?.voiceStates.cache.get(i.user.id)?.channelId - ) { - await i.reply({ - embeds: [ - new EmbedBuilder() - .setTitle("エラー") - .setDescription("ボイスチャンネルに接続していません。"), - ], - ephemeral: true, - }); - return null; - } - return player; - } -} diff --git a/src/lib/update.ts b/src/lib/update.ts deleted file mode 100644 index 8cf90f0..0000000 --- a/src/lib/update.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { Logger } from "./logger"; -import { execSync, spawn } from "node:child_process"; -import { env } from "./env"; - -export class Updater { - HEAD = execSync("git rev-parse HEAD").toString().trim(); - Version = env.Version; - constructor() {} - pull() { - const r = execSync("git pull").toString().trim(); - if (r === "Already up to date.") return false; - return true; - } - install() { - execSync("npm install"); - } - build() { - execSync("npm run build"); - } - restart() { - process.once("exit", () => { - spawn(process.argv.shift() ?? "", process.argv, { - cwd: process.cwd(), - detached: true, - stdio: "inherit", - env: Object.assign(process.env, { - UPDATE_STATUS: "restarted", - }), - }); - }); - process.exit(0); - } - exit() { - process.exit(0); - } -} - -export function autoUpdate(logger: Logger) { - const updater = new Updater(); - if (process.env.UPDATE_STATUS === "restarted") { - logger.info("Updated!"); - return logger.info( - `Running on head ${updater.HEAD}, version ${updater.Version}...` - ); - } - logger.info("Checking for updates..."); - try { - const status = updater.pull(); - if (!status) { - logger.info("Already up to date."); - return logger.info( - `Running on head ${updater.HEAD}, version ${updater.Version}...` - ); - } else { - logger.info("Updating dependencies..."); - updater.install(); - logger.info("Updated!"); - logger.info("Building scripts..."); - updater.build(); - logger.info("Completed!"); - } - } catch (error) { - logger.error(error); - } - logger.info("Update finished"); - logger.info("Restarting..."); - updater.restart(); -} diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 0000000..1e89d98 --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,30 @@ +import { greenBright, yellowBright, redBright } from "chalk"; + +class Logger { + constructor(private name: string) {} + info(...args: any[]) { + console.log( + `${greenBright(`(info)`)}${yellowBright(`[${this.name}]`)}`, + ...args, + ); + } + error(...args: any[]) { + console.log( + `${redBright(`(error)`)}${yellowBright(`[${this.name}]`)}`, + ...args, + ); + } + panic(...args: any[]) { + console.log( + `${redBright(`(panic)`)}${yellowBright(`[${this.name}]`)}`, + ...args, + ); + process.exit(1); + } +} + +const logger = new Logger("music-bot"); + +export const info = logger.info.bind(logger); +export const error = logger.error.bind(logger); +export const panic = logger.panic.bind(logger); diff --git a/src/voice.ts b/src/voice.ts new file mode 100644 index 0000000..ba3c280 --- /dev/null +++ b/src/voice.ts @@ -0,0 +1,122 @@ +import { + joinVoiceChannel, + getVoiceConnection, + AudioPlayer, + AudioPlayerState, +} from "@discordjs/voice"; +import { Collection, GuildChannel, GuildTextBasedChannel } from "discord.js"; +import { Video, dl } from "./yt"; +import { info } from "./logger"; + +export class Player { + private playing = false; + public looping = false; + public queuelooping = false; + constructor( + public player: AudioPlayer, + public queue: Video[] = [], + public channel: GuildTextBasedChannel, + ) {} + async push(options: Video) { + this.queue.push(options); + if (!this.playing) { + this.playing = true; + await this.channel.send(`再生します。`); + this.play(); + } else { + await this.channel.send( + `キューに追加しました。(${this.queue.length}曲目)`, + ); + } + } + async skip() { + this.player.stop(); + await this.channel.send("スキップしました。"); + } + async pause() { + this.player.pause(); + await this.channel.send("一時停止しました。"); + } + async resume() { + this.player.unpause(); + await this.channel.send("再開しました。"); + } + async clear() { + this.queue = []; + this.player.stop(); + await this.channel.send("停止しました。"); + } + async play() { + if (!this.queue.length) { + this.playing = false; + return; + } + const options = + this.looping && !this.queuelooping + ? this.queue[0] + : this.queue.shift()!; + try { + await this.channel.send( + `再生します。(${ + this.looping && !this.queuelooping + ? this.queue.length + : this.queue.length + 1 + }曲残っています。)`, + ); + const stream = dl(options); + this.player.play(stream); + const listener = ( + state: AudioPlayerState, + newState: AudioPlayerState, + ) => { + if (state.status === "playing" && newState.status === "idle") { + info(`Finished playing ${options.title}.`); + this.player.off("stateChange", listener); + if (this.queuelooping) { + this.queue.push(options); + } + this.play(); + } + }; + this.player.on("stateChange", listener); + } catch (e) { + console.error(e); + this.play(); + } + } +} + +const players = new Collection(); + +export async function connect( + channel: GuildChannel, + log: GuildTextBasedChannel, +) { + const conn = joinVoiceChannel({ + channelId: channel.id, + guildId: channel.guild.id, + adapterCreator: channel.guild.voiceAdapterCreator, + selfDeaf: true, + }); + const player = new AudioPlayer(); + conn.subscribe(player); + players.set(channel.guild.id, new Player(player, [], log)); + return conn; +} + +export function get(guildId: string) { + const conn = getVoiceConnection(guildId); + if (!conn) { + if (players.has(guildId)) players.delete(guildId); + return {}; + } + return { + player: players.get(guildId), + conn, + }; +} + +export function disconnect(guildId: string) { + const conn = getVoiceConnection(guildId); + if (conn) conn.destroy(); +} diff --git a/src/yt.ts b/src/yt.ts new file mode 100644 index 0000000..f4c015f --- /dev/null +++ b/src/yt.ts @@ -0,0 +1,35 @@ +import { createAudioResource } from "@discordjs/voice"; +import { search } from "yt-search"; +import { validateID } from "ytdl-core"; +import ytdl from "ytdl-core"; + +export function dl(q: Video) { + const video = ytdl(q.id, { + quality: "highestaudio", + filter: "audioonly", + }); + return createAudioResource(video); +} + +export async function ys(query: string) { + let v = null; + if (validateID(query)) { + v = await search({ videoId: query }); + } else { + const { videos } = await search(query); + v = videos[0]; + } + if (!v) return null; + return { + title: v.title, + description: v.description, + id: v.videoId, + image: v.image, + footer: `${v.duration.timestamp},${v.views}回`, + }; +} + +export type AsyncReturnType any> = + ReturnType extends Promise ? S : never; + +export type Video = NonNullable>; diff --git a/tsconfig.json b/tsconfig.json index fe51d32..4b919f1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,12 +1,12 @@ { - "compilerOptions": { - "target": "es2022", - "module": "commonjs", - "rootDir": "./src", - "outDir": "./dist", - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "skipLibCheck": true - } + "compilerOptions": { + "target": "es2022", + "module": "Node16", + "rootDir": "./src", + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + } }