Merge branch 'master' into feature/select-dotenv-path

This commit is contained in:
João Costa 2024-10-28 16:16:27 +00:00 committed by GitHub
commit ce8edf4145
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 529 additions and 350 deletions

View file

@ -10,7 +10,7 @@ jobs:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- uses: actions/setup-node@v2 - uses: actions/setup-node@v2
with: with:
node-version: '16' node-version: '18'
cache: 'yarn' cache: 'yarn'
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install

108
.github/workflows/pr-release.yml vendored Normal file
View file

@ -0,0 +1,108 @@
name: Release snapshot of PR
on:
workflow_run:
workflows: ["Build snapshot of PR"]
types:
- completed
env:
REGISTRY_IMAGE: ghcr.io/museofficial/muse
jobs:
release-and-comment:
name: Release snapshot and comment in PR
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Buildx
uses: docker/setup-buildx-action@v1
- name: Download images
uses: actions/download-artifact@v4
with:
path: /tmp/images
pattern: image-linux-*
merge-multiple: true
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GH_PAT }}
- name: Load image
shell: bash
run: |
docker load -i /tmp/images/image-linux-amd64.tar
docker load -i /tmp/images/image-linux-arm64.tar
- name: Download SHA
uses: actions/download-artifact@v4
with:
path: /tmp/SHA
pattern: sha
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GH_PAT }}
- name: Read SHA
shell: bash
run: |
echo "SHA=$(cat /tmp/SHA/sha/sha.txt | tr -d '\n')" >> $GITHUB_ENV
- name: Push images
run: |
docker push ${{ env.REGISTRY_IMAGE }}:${{ env.SHA }}-linux-amd64
docker push ${{ env.REGISTRY_IMAGE }}:${{ env.SHA }}-linux-arm64
- name: Download Docker metadata
uses: actions/download-artifact@v4
with:
path: /tmp/metadata
pattern: metadata
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GH_PAT }}
- name: Read the metadata.json file
id: metadata_reader
uses: juliangruber/read-file-action@v1.0.0
with:
path: /tmp/metadata/metadata/metadata.json
- name: Download PR number
uses: actions/download-artifact@v4
with:
path: /tmp/pull_request_number
pattern: pull_request_number
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GH_PAT }}
- name: Read PR number
shell: bash
run: |
echo "PR_NUMBER=$(cat /tmp/pull_request_number/pull_request_number/pull_request_number.txt | tr -d '\n')" >> $GITHUB_ENV
- name: Create manifest list and push
run: |
docker buildx imagetools create $(cat /tmp/metadata/metadata/metadata.json | jq -cr '.tags | map("-t " + .) | join(" ")') ${{ env.REGISTRY_IMAGE }}:${{ env.SHA }}-linux-amd64 ${{ env.REGISTRY_IMAGE }}:${{ env.SHA }}-linux-arm64
- name: Create comment
uses: marocchino/sticky-pull-request-comment@v2
with:
header: "pr-release"
number: ${{ env.PR_NUMBER }}
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
message: |
#### :package: :robot: A new release has been made for this pull request.
To play around with this PR, pull `${{ env.REGISTRY_IMAGE }}:pr-${{ env.PR_NUMBER }}`.
Images are available for x86_64 and ARM64.
> Latest commit: ${{ env.SHA }}

101
.github/workflows/pr-snapshot.yml vendored Normal file
View file

@ -0,0 +1,101 @@
name: Build snapshot of PR
on: pull_request
env:
REGISTRY_IMAGE: ghcr.io/museofficial/muse
jobs:
build:
name: Build snapshot
strategy:
matrix:
runner-platform:
- ubuntu-latest
- namespace-profile-default-arm64
include:
- runner-platform: ubuntu-latest
build-arch: linux/amd64
- runner-platform: namespace-profile-default-arm64
build-arch: linux/arm64
runs-on: ${{ matrix.runner-platform }}
steps:
- name: Prepare
run: |
platform=${{ matrix.build-arch }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY_IMAGE }}
tags: type=ref,event=pr
- name: Set up Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get current time
uses: josStorer/get-current-time@v2
id: current-time
- name: Build
id: build
uses: docker/build-push-action@v6
with:
outputs: type=docker,dest=/tmp/image-${{ env.PLATFORM_PAIR }}.tar
platforms: ${{ matrix.build-arch }}
tags: |
${{ env.REGISTRY_IMAGE }}:${{ github.sha }}-${{ env.PLATFORM_PAIR }}
build-args: |
COMMIT_HASH=${{ github.sha }}
BUILD_DATE=${{ steps.current-time.outputs.time }}
- name: Export Docker meta output
shell: bash
run: echo $DOCKER_METADATA_OUTPUT_JSON > /tmp/metadata.json
- name: Upload metadata
uses: actions/upload-artifact@v4
with:
name: metadata
path: /tmp/metadata.json
overwrite: true
- name: Export SHA
run: |
echo "${{ github.sha }}" > /tmp/sha.txt
- name: Upload SHA
uses: actions/upload-artifact@v4
with:
name: sha
path: /tmp/sha.txt
overwrite: true
- name: Upload image
uses: actions/upload-artifact@v4
with:
name: image-${{ env.PLATFORM_PAIR }}
path: /tmp/image-${{ env.PLATFORM_PAIR }}.tar
if-no-files-found: error
retention-days: 1
- name: Save PR number in artifact
shell: bash
env:
PR_NUMBER: ${{ github.event.number }}
run: echo $PR_NUMBER > /tmp/pull_request_number.txt
- name: Upload PR number
uses: actions/upload-artifact@v4
with:
name: pull_request_number
path: /tmp/pull_request_number.txt
overwrite: true

View file

@ -1,91 +0,0 @@
name: PR Workflow
on: pull_request_target
jobs:
release-snapshot:
name: Release snapshot
strategy:
matrix:
runner-platform:
- ubuntu-latest
- buildjet-4vcpu-ubuntu-2204-arm
include:
- runner-platform: ubuntu-latest
build-arch: linux/amd64
tagged-platform: amd64
- runner-platform: buildjet-4vcpu-ubuntu-2204-arm
build-arch: linux/arm64
tagged-platform: arm64
runs-on: ${{ matrix.runner-platform }}
steps:
- name: Set up Buildx
uses: docker/setup-buildx-action@v1
- name: Cache Docker layers
# AWS data transfer is pricy
if: ${{ matrix.runner-platform != 'buildjet-4vcpu-ubuntu-2204-arm' }}
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-prs-${{ matrix.build-arch }}-${{ github.event.pull_request.head.sha }}
restore-keys: |
${{ runner.os }}-buildx-prs-${{ matrix.build-arch }}
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Get current time
uses: josStorer/get-current-time@v2
id: current-time
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: codetheweb/muse:${{ github.event.pull_request.head.sha }}-${{ matrix.tagged-platform }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache,mode=min
platforms: ${{ matrix.build-arch }}
build-args: |
COMMIT_HASH=${{ github.sha }}
BUILD_DATE=${{ steps.current-time.outputs.time }}
combine-and-comment:
name: Combine platform tags and leave comment
runs-on: ubuntu-latest
needs: release-snapshot
steps:
- name: Set up Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Combine tags
run: docker buildx imagetools create -t 'codetheweb/muse:pr-${{ github.event.number }}' -t 'codetheweb/muse:${{ github.event.pull_request.head.sha }}' 'codetheweb/muse:${{ github.event.pull_request.head.sha }}-arm64' 'codetheweb/muse:${{ github.event.pull_request.head.sha }}-amd64'
- name: Create comment
uses: marocchino/sticky-pull-request-comment@v2
with:
header: "pr-release"
message: |
#### :package: A new release has been made for this pull request.
To play around with this PR, pull `codetheweb/muse:pr-${{ github.event.number }}` or `codetheweb/muse:${{ github.event.pull_request.head.sha }}`.
Images are available for x86_64 and ARM64.
> Latest commit: ${{ github.event.pull_request.head.sha }}

View file

@ -5,34 +5,32 @@ on:
tags: tags:
- 'v*' - 'v*'
env:
REGISTRY_IMAGE: ghcr.io/museofficial/muse
jobs: jobs:
publish: publish:
strategy: strategy:
matrix: matrix:
runner-platform: runner-platform:
- ubuntu-latest - ubuntu-latest
- buildjet-4vcpu-ubuntu-2204-arm - namespace-profile-default-arm64
include: include:
- runner-platform: ubuntu-latest - runner-platform: ubuntu-latest
build-arch: linux/amd64 build-arch: linux/amd64
tagged-platform: amd64 tagged-platform: amd64
- runner-platform: buildjet-4vcpu-ubuntu-2204-arm - runner-platform: namespace-profile-default-arm64
build-arch: linux/arm64 build-arch: linux/arm64
tagged-platform: arm64 tagged-platform: arm64
runs-on: ${{ matrix.runner-platform }} runs-on: ${{ matrix.runner-platform }}
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps: steps:
- name: Set up Buildx - name: Set up Buildx
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v3
- name: Cache Docker layers
# AWS data transfer is pricy
if: ${{ matrix.runner-platform != 'buildjet-4vcpu-ubuntu-2204-arm' }}
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-prs-${{ matrix.build-arch }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-prs-${{ matrix.build-arch }}
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v1 uses: docker/login-action@v1
@ -40,19 +38,26 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get current time - name: Get current time
uses: josStorer/get-current-time@v2 uses: josStorer/get-current-time@v2
id: current-time id: current-time
- name: Build and push - name: Build and push
id: docker_build id: docker_build
uses: docker/build-push-action@v2 uses: docker/build-push-action@v6
with: with:
push: true push: true
tags: codetheweb/muse:${{ github.sha }}-${{ matrix.tagged-platform }} tags: |
codetheweb/muse:${{ github.sha }}-${{ matrix.tagged-platform }}
${{ env.REGISTRY_IMAGE }}:${{ github.sha }}-${{ matrix.tagged-platform }}
platforms: ${{ matrix.build-arch }} platforms: ${{ matrix.build-arch }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache,mode=min
build-args: | build-args: |
COMMIT_HASH=${{ github.sha }} COMMIT_HASH=${{ github.sha }}
BUILD_DATE=${{ steps.current-time.outputs.time }} BUILD_DATE=${{ steps.current-time.outputs.time }}
@ -61,6 +66,11 @@ jobs:
name: Combine platform tags name: Combine platform tags
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: publish needs: publish
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
@ -73,21 +83,37 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Get tags - name: Login to GitHub Container Registry
id: get-tags uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get tags (Docker Hub)
id: get-tags-dockerhub
uses: Surgo/docker-smart-tag-action@v1 uses: Surgo/docker-smart-tag-action@v1
with: with:
docker_image: codetheweb/muse docker_image: codetheweb/muse
- name: Combine tags - name: Get tags (ghcr.io)
run: docker buildx imagetools create $(echo '${{ steps.get-tags.outputs.tag }}' | tr "," "\0" | xargs -0 printf -- '-t %s ') 'codetheweb/muse:${{ github.sha }}-arm64' 'codetheweb/muse:${{ github.sha }}-amd64' id: get-tags-ghcr
uses: Surgo/docker-smart-tag-action@v1
with:
docker_image: ${{ env.REGISTRY_IMAGE }}
- name: Combine tags (Docker Hub)
run: docker buildx imagetools create $(echo '${{ steps.get-tags-dockerhub.outputs.tag }}' | tr "," "\0" | xargs -0 printf -- '-t %s ') 'codetheweb/muse:${{ github.sha }}-arm64' 'codetheweb/muse:${{ github.sha }}-amd64'
- name: Combine tags (GitHub Container Registry)
run: docker buildx imagetools create $(echo '${{ steps.get-tags-ghcr.outputs.tag }}' | tr "," "\0" | xargs -0 printf -- '-t %s ') '${{ env.REGISTRY_IMAGE }}:${{ github.sha }}-arm64' '${{ env.REGISTRY_IMAGE }}:${{ github.sha }}-amd64'
- name: Update Docker Hub description - name: Update Docker Hub description
uses: peter-evans/dockerhub-description@v2.4.3 uses: peter-evans/dockerhub-description@v2.4.3
env: with:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} password: ${{ secrets.DOCKERHUB_PASSWORD }}
DOCKERHUB_REPOSITORY: codetheweb/muse repository: codetheweb/muse
release: release:
name: Create GitHub release name: Create GitHub release

View file

@ -8,6 +8,6 @@ jobs:
steps: steps:
- uses: apexskier/github-release-commenter@v1 - uses: apexskier/github-release-commenter@v1
with: with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GH_PAT }}
comment-template: | comment-template: |
🚀 Released in {release_link}. 🚀 Released in {release_link}.

View file

@ -10,7 +10,7 @@ jobs:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- uses: actions/setup-node@v2 - uses: actions/setup-node@v2
with: with:
node-version: '16' node-version: '18'
cache: 'yarn' cache: 'yarn'
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install

View file

@ -6,7 +6,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [2.9.4] - 2024-08-28
### Added
- An optional `page-size` to `/queue` command
- Add `default-queue-page-size` setting
## [2.9.3] - 2024-08-19
### Fixed ### Fixed
- bumped @discordjs/voice
- bumped @distube/ytdl-core
## [2.9.2] - 2024-08-18
### Changed
- Muse has new maintainers! I ([@codetheweb](https://github.com/codetheweb)) am stepping aside as I haven't used Muse myself for a few years and haven't been able to spend as much time on Muse as I'd like. See [this issue](https://github.com/museofficial/muse/issues/1063) for details. Welcome @museofficial/maintainers!
- This repository has been moved to museofficial/muse.
- Docker images are now published to `ghcr.io/museofficial/muse`. **Please update your image source if you use Docker**.
## [2.9.1] - 2024-08-04
### Fixed
- bumped ytdl-core
## [2.9.0] - 2024-07-17
### Added
- A `skip` option to the `/play` command
### Fixed
- Fixed playback issue
- Audioplayer not stopping properly - Audioplayer not stopping properly
## [2.8.1] - 2024-04-28 ## [2.8.1] - 2024-04-28
@ -311,7 +344,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Initial release - Initial release
[unreleased]: https://github.com/codetheweb/muse/compare/v2.8.1...HEAD [unreleased]: https://github.com/codetheweb/muse/compare/v2.9.4...HEAD
[2.9.4]: https://github.com/codetheweb/muse/compare/v2.9.3...v2.9.4
[2.9.3]: https://github.com/codetheweb/muse/compare/v2.9.2...v2.9.3
[2.9.2]: https://github.com/codetheweb/muse/compare/v2.9.1...v2.9.2
[2.9.1]: https://github.com/codetheweb/muse/compare/v2.9.0...v2.9.1
[2.9.0]: https://github.com/codetheweb/muse/compare/v2.8.1...v2.9.0
[2.8.1]: https://github.com/codetheweb/muse/compare/v2.8.0...v2.8.1 [2.8.1]: https://github.com/codetheweb/muse/compare/v2.8.0...v2.8.1
[2.8.0]: https://github.com/codetheweb/muse/compare/v2.7.1...v2.8.0 [2.8.0]: https://github.com/codetheweb/muse/compare/v2.7.1...v2.8.0
[2.7.1]: https://github.com/codetheweb/muse/compare/v2.7.0...v2.7.1 [2.7.1]: https://github.com/codetheweb/muse/compare/v2.7.0...v2.7.1

View file

@ -1,4 +1,4 @@
FROM node:18.7.0-slim AS base FROM node:18-bullseye-slim AS base
# openssl will be a required package if base is updated to 18.16+ due to node:*-slim base distro change # openssl will be a required package if base is updated to 18.16+ due to node:*-slim base distro change
# https://github.com/prisma/prisma/issues/19729#issuecomment-1591270599 # https://github.com/prisma/prisma/issues/19729#issuecomment-1591270599
@ -19,7 +19,6 @@ FROM base AS dependencies
WORKDIR /usr/app WORKDIR /usr/app
COPY package.json . COPY package.json .
COPY patches ./patches
COPY yarn.lock . COPY yarn.lock .
RUN yarn install --prod RUN yarn install --prod
@ -49,10 +48,10 @@ COPY . .
ARG COMMIT_HASH=unknown ARG COMMIT_HASH=unknown
ARG BUILD_DATE=unknown ARG BUILD_DATE=unknown
ENV DATA_DIR /data ENV DATA_DIR=/data
ENV NODE_ENV production ENV NODE_ENV=production
ENV COMMIT_HASH $COMMIT_HASH ENV COMMIT_HASH=$COMMIT_HASH
ENV BUILD_DATE $BUILD_DATE ENV BUILD_DATE=$BUILD_DATE
ENV ENV_FILE /config ENV ENV_FILE /config
CMD ["tini", "--", "node", "--enable-source-maps", "dist/scripts/migrate-and-start.js"] CMD ["tini", "--", "node", "--enable-source-maps", "dist/scripts/migrate-and-start.js"]

View file

@ -1,8 +1,10 @@
<p align="center"> <p align="center">
<img width="250" height="250" src="https://raw.githubusercontent.com/codetheweb/muse/master/.github/logo.png"> <img width="250" height="250" src="https://raw.githubusercontent.com/museofficial/muse/master/.github/logo.png">
</p> </p>
🚨: v1.0.0 was a breaking change. Please take a look at the [release notes](https://github.com/codetheweb/muse/releases/tag/v1.0.0) for upgrade instructions > [!WARNING]
> I ([@codetheweb](https://github.com/codetheweb)) am no longer the primary maintainer of Muse. **If you use the Docker image, update your image source to `ghcr.io/museofficial/muse`.** We are currently publishing new releases to both `ghcr.io/museofficial/muse` and `codetheweb/muse`, but this may change in the future.
> Thank you to all the people who stepped up to help maintain Muse!
------ ------
@ -42,7 +44,7 @@ A 64-bit OS is required to run Muse.
The `master` branch acts as the developing / bleeding edge branch and is not guaranteed to be stable. The `master` branch acts as the developing / bleeding edge branch and is not guaranteed to be stable.
When running a production instance, I recommend that you use the [latest release](https://github.com/codetheweb/muse/releases/). When running a production instance, I recommend that you use the [latest release](https://github.com/museofficial/muse/releases/).
### 🐳 Docker ### 🐳 Docker
@ -56,7 +58,7 @@ There are a variety of image tags available:
(Replace empty config strings with correct values.) (Replace empty config strings with correct values.)
```bash ```bash
docker run -it -v "$(pwd)/data":/data -e DISCORD_TOKEN='' -e SPOTIFY_CLIENT_ID='' -e SPOTIFY_CLIENT_SECRET='' -e YOUTUBE_API_KEY='' codetheweb/muse:latest docker run -it -v "$(pwd)/data":/data -e DISCORD_TOKEN='' -e SPOTIFY_CLIENT_ID='' -e SPOTIFY_CLIENT_SECRET='' -e YOUTUBE_API_KEY='' ghcr.io/museofficial/muse:latest
``` ```
This starts Muse and creates a data directory in your current directory. This starts Muse and creates a data directory in your current directory.
@ -70,7 +72,7 @@ version: '3.4'
services: services:
muse: muse:
image: codetheweb/muse:latest image: ghcr.io/museofficial/muse:latest
restart: always restart: always
volumes: volumes:
- ./muse:/data - ./muse:/data
@ -84,16 +86,16 @@ services:
### Node.js ### Node.js
**Prerequisites**: **Prerequisites**:
* Node.js (16.x is recommended because it's the current LTS version) * Node.js (18.17.0 or later is required and latest 18.x.x LTS is recommended)
* ffmpeg (4.1 or later) * ffmpeg (4.1 or later)
1. `git clone https://github.com/codetheweb/muse.git && cd muse` 1. `git clone https://github.com/museofficial/muse.git && cd muse`
2. Copy `.env.example` to `.env` and populate with values 2. Copy `.env.example` to `.env` and populate with values
3. I recommend checking out a tagged release with `git checkout v[latest release]` 3. I recommend checking out a tagged release with `git checkout v[latest release]`
4. `yarn install` (or `npm i`) 4. `yarn install` (or `npm i`)
5. `yarn start` (or `npm run start`) 5. `yarn start` (or `npm run start`)
**Note**: if you're on Windows, you may need to manually set the ffmpeg path. See [#345](https://github.com/codetheweb/muse/issues/345) for details. **Note**: if you're on Windows, you may need to manually set the ffmpeg path. See [#345](https://github.com/museofficial/muse/issues/345) for details.
## ⚙️ Additional configuration (advanced) ## ⚙️ Additional configuration (advanced)

5
RELEASING.md Normal file
View file

@ -0,0 +1,5 @@
# Releasing
1. Confirm that CHANGELOG.md is updated (any new changes should go under "Unreleased" at the top).
2. On the master branch, run `yarn release` and follow the prompts.
3. After a new tag is pushed from the above step, the [publish workflow](./.github/workflows/publish.yml) will automatically build & push Docker images and create a GitHub release for the tag.

View file

@ -0,0 +1,19 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Setting" (
"guildId" TEXT NOT NULL PRIMARY KEY,
"playlistLimit" INTEGER NOT NULL DEFAULT 50,
"secondsToWaitAfterQueueEmpties" INTEGER NOT NULL DEFAULT 30,
"leaveIfNoListeners" BOOLEAN NOT NULL DEFAULT true,
"queueAddResponseEphemeral" BOOLEAN NOT NULL DEFAULT false,
"autoAnnounceNextSong" BOOLEAN NOT NULL DEFAULT false,
"defaultVolume" INTEGER NOT NULL DEFAULT 100,
"defaultQueuePageSize" INTEGER NOT NULL DEFAULT 10,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_Setting" ("autoAnnounceNextSong", "createdAt", "defaultVolume", "guildId", "leaveIfNoListeners", "playlistLimit", "queueAddResponseEphemeral", "secondsToWaitAfterQueueEmpties", "updatedAt") SELECT "autoAnnounceNextSong", "createdAt", "defaultVolume", "guildId", "leaveIfNoListeners", "playlistLimit", "queueAddResponseEphemeral", "secondsToWaitAfterQueueEmpties", "updatedAt" FROM "Setting";
DROP TABLE "Setting";
ALTER TABLE "new_Setting" RENAME TO "Setting";
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View file

@ -1,15 +1,15 @@
{ {
"name": "muse", "name": "muse",
"version": "2.8.1", "version": "2.9.4",
"description": "🎧 a self-hosted Discord music bot that doesn't suck ", "description": "🎧 a self-hosted Discord music bot that doesn't suck ",
"repository": "git@github.com:codetheweb/muse.git", "repository": "git@github.com:museofficial/muse.git",
"author": "Max Isom <hi@maxisom.me>", "author": "Max Isom <hi@maxisom.me>",
"license": "MIT", "license": "MIT",
"private": true, "private": true,
"types": "dts/types", "types": "dts/types",
"type": "module", "type": "module",
"engines": { "engines": {
"node": ">=16.0.0" "node": ">=18.17.0"
}, },
"files": [ "files": [
"src" "src"
@ -88,7 +88,9 @@
"@discordjs/builders": "1.1.0", "@discordjs/builders": "1.1.0",
"@discordjs/opus": "^0.8.0", "@discordjs/opus": "^0.8.0",
"@discordjs/rest": "1.0.1", "@discordjs/rest": "1.0.1",
"@discordjs/voice": "0.11.0", "@discordjs/voice": "0.17.0",
"@distube/ytdl-core": "^4.14.4",
"@distube/ytsr": "^2.0.0",
"@prisma/client": "4.16.0", "@prisma/client": "4.16.0",
"@types/libsodium-wrappers": "^0.7.9", "@types/libsodium-wrappers": "^0.7.9",
"array-shuffle": "^3.0.0", "array-shuffle": "^3.0.0",
@ -126,7 +128,6 @@
"sync-fetch": "^0.3.1", "sync-fetch": "^0.3.1",
"tsx": "3.8.2", "tsx": "3.8.2",
"xbytes": "^1.7.0", "xbytes": "^1.7.0",
"ytdl-core": "^4.11.5",
"ytsr": "^3.8.4" "ytsr": "^3.8.4"
}, },
"resolutions": { "resolutions": {

View file

@ -1,172 +0,0 @@
diff --git a/node_modules/ytdl-core/lib/sig.js b/node_modules/ytdl-core/lib/sig.js
index eb7bfaa..b2eee87 100644
--- a/node_modules/ytdl-core/lib/sig.js
+++ b/node_modules/ytdl-core/lib/sig.js
@@ -3,6 +3,9 @@ const Cache = require('./cache');
const utils = require('./utils');
const vm = require('vm');
+
+let nTransformWarning = false;
+
// A shared cache to keep track of html5player js functions.
exports.cache = new Cache();
@@ -23,6 +26,49 @@ exports.getFunctions = (html5playerfile, options) => exports.cache.getOrSet(html
return functions;
});
+// eslint-disable-next-line max-len
+// https://github.com/TeamNewPipe/NewPipeExtractor/blob/41c8dce452aad278420715c00810b1fed0109adf/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java#L816
+const DECIPHER_REGEXPS = [
+ '(?:\\b|[^a-zA-Z0-9$])([a-zA-Z0-9$]{2,})\\s*=\\s*function\\(\\s*a\\s*\\)' +
+ '\\s*\\{\\s*a\\s*=\\s*a\\.split\\(\\s*""\\s*\\)',
+ '\\bm=([a-zA-Z0-9$]{2,})\\(decodeURIComponent\\(h\\.s\\)\\)',
+ '\\bc&&\\(c=([a-zA-Z0-9$]{2,})\\(decodeURIComponent\\(c\\)\\)',
+ '([\\w$]+)\\s*=\\s*function\\((\\w+)\\)\\{\\s*\\2=\\s*\\2\\.split\\(""\\)\\s*;',
+ '\\b([\\w$]{2,})\\s*=\\s*function\\((\\w+)\\)\\{\\s*\\2=\\s*\\2\\.split\\(""\\)\\s*;',
+ '\\bc\\s*&&\\s*d\\.set\\([^,]+\\s*,\\s*(:encodeURIComponent\\s*\\()([a-zA-Z0-9$]+)\\(',
+];
+
+const DECIPHER_ARGUMENT = 'sig';
+const N_ARGUMENT = 'ncode';
+
+const matchGroup1 = (regex, str) => {
+ const match = str.match(new RegExp(regex));
+ if (!match) throw new Error(`Could not match ${regex}`);
+ return match[1];
+};
+
+const getFuncName = (body, regexps) => {
+ try {
+ let fn;
+ for (const regex of regexps) {
+ try {
+ fn = matchGroup1(regex, body);
+ const idx = fn.indexOf('[0]');
+ if (idx > -1) fn = matchGroup1(`${fn.slice(0, 3)}=\\[([a-zA-Z0-9$\\[\\]]{2,})\\]`, body);
+ } catch (err) {
+ continue;
+ }
+ }
+ if (!fn || fn.includes('[')) throw Error("Couldn't find fn name");
+ return fn;
+ } catch (e) {
+ throw Error(`Please open an issue on ytdl-core GitHub: ${e.message}`);
+ }
+};
+
+const getDecipherFuncName = body => getFuncName(body, DECIPHER_REGEXPS);
+
+
/**
* Extracts the actions that should be taken to decipher a signature
* and tranform the n parameter
@@ -31,44 +77,45 @@ exports.getFunctions = (html5playerfile, options) => exports.cache.getOrSet(html
* @returns {Array.<string>}
*/
exports.extractFunctions = body => {
+ body = body.replace(/\n|\r/g, '');
const functions = [];
- const extractManipulations = caller => {
- const functionName = utils.between(caller, `a=a.split("");`, `.`);
- if (!functionName) return '';
- const functionStart = `var ${functionName}={`;
- const ndx = body.indexOf(functionStart);
- if (ndx < 0) return '';
- const subBody = body.slice(ndx + functionStart.length - 1);
- return `var ${functionName}=${utils.cutAfterJS(subBody)}`;
- };
+ // This is required function, so we can't continue if it's not found.
const extractDecipher = () => {
- const functionName = utils.between(body, `a.set("alr","yes");c&&(c=`, `(decodeURIC`);
- if (functionName && functionName.length) {
- const functionStart = `${functionName}=function(a)`;
- const ndx = body.indexOf(functionStart);
- if (ndx >= 0) {
- const subBody = body.slice(ndx + functionStart.length);
- let functionBody = `var ${functionStart}${utils.cutAfterJS(subBody)}`;
- functionBody = `${extractManipulations(functionBody)};${functionBody};${functionName}(sig);`;
- functions.push(functionBody);
- }
+ const decipherFuncName = getDecipherFuncName(body);
+ try {
+ const functionPattern = `(${decipherFuncName.replace(/\$/g, '\\$')}=function\\([a-zA-Z0-9_]+\\)\\{.+?\\})`;
+ const decipherFunction = `var ${matchGroup1(functionPattern, body)};`;
+ const helperObjectName = matchGroup1(';([A-Za-z0-9_\\$]{2,})\\.\\w+\\(', decipherFunction)
+ .replace(/\$/g, '\\$');
+ const helperPattern = `(var ${helperObjectName}=\\{[\\s\\S]+?\\}\\};)`;
+ const helperObject = matchGroup1(helperPattern, body);
+ const callerFunction = `${decipherFuncName}(${DECIPHER_ARGUMENT});`;
+ const resultFunction = helperObject + decipherFunction + callerFunction;
+ functions.push(resultFunction);
+ } catch (err) {
+ throw Error(`Could not parse decipher function: ${err}`);
}
};
- const extractNCode = () => {
- let functionName = utils.between(body, `&&(b=a.get("n"))&&(b=`, `(b)`);
- if (functionName.includes('[')) functionName = utils.between(body, `var ${functionName.split('[')[0]}=[`, `]`);
- if (functionName && functionName.length) {
- const functionStart = `${functionName}=function(a)`;
- const ndx = body.indexOf(functionStart);
- if (ndx >= 0) {
- const subBody = body.slice(ndx + functionStart.length);
- const functionBody = `var ${functionStart}${utils.cutAfterJS(subBody)};${functionName}(ncode);`;
- functions.push(functionBody);
+ // This is optional, so we can continue if it's not found, but it will bottleneck the download.
+ const extractNTransform = () => {
+ let nFuncName = utils.between(body, `(b=a.get("n"))&&(b=`, `(b)`);
+ if (nFuncName.includes('[')) nFuncName = utils.between(body, `${nFuncName.split('[')[0]}=[`, `]`);
+ if (nFuncName && nFuncName.length) {
+ const nBegin = `${nFuncName}=function(a)`;
+ const nEnd = '.join("")};';
+ const nFunction = utils.between(body, nBegin, nEnd);
+ if (nFunction) {
+ const callerFunction = `${nFuncName}(${N_ARGUMENT});`;
+ const resultFunction = nBegin + nFunction + nEnd + callerFunction;
+ functions.push(resultFunction);
+ } else if (!nTransformWarning) {
+ console.warn('Could not parse n transform function, please report it on @distube/ytdl-core GitHub.');
+ nTransformWarning = true;
}
}
};
extractDecipher();
- extractNCode();
+ extractNTransform();
return functions;
};
@@ -82,22 +129,25 @@ exports.extractFunctions = body => {
exports.setDownloadURL = (format, decipherScript, nTransformScript) => {
const decipher = url => {
const args = querystring.parse(url);
- if (!args.s || !decipherScript) return args.url;
+ if (!args.s) return args.url;
const components = new URL(decodeURIComponent(args.url));
- components.searchParams.set(args.sp ? args.sp : 'signature',
- decipherScript.runInNewContext({ sig: decodeURIComponent(args.s) }));
+ const context = {};
+ context[DECIPHER_ARGUMENT] = decodeURIComponent(args.s);
+ components.searchParams.set(args.sp || 'sig', decipherScript.runInNewContext(context));
return components.toString();
};
- const ncode = url => {
+ const nTransform = url => {
const components = new URL(decodeURIComponent(url));
const n = components.searchParams.get('n');
if (!n || !nTransformScript) return url;
- components.searchParams.set('n', nTransformScript.runInNewContext({ ncode: n }));
+ const context = {};
+ context[N_ARGUMENT] = n;
+ components.searchParams.set('n', nTransformScript.runInNewContext(context));
return components.toString();
};
const cipher = !format.url;
const url = format.url || format.signatureCipher || format.cipher;
- format.url = cipher ? ncode(decipher(url)) : ncode(url);
+ format.url = cipher ? nTransform(decipher(url)) : nTransform(url);
delete format.signatureCipher;
delete format.cipher;
};

View file

@ -31,6 +31,7 @@ model Setting {
queueAddResponseEphemeral Boolean @default(false) queueAddResponseEphemeral Boolean @default(false)
autoAnnounceNextSong Boolean @default(false) autoAnnounceNextSong Boolean @default(false)
defaultVolume Int @default(100) defaultVolume Int @default(100)
defaultQueuePageSize Int @default(10)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }

View file

@ -56,6 +56,15 @@ export default class implements Command {
.setMinValue(0) .setMinValue(0)
.setMaxValue(100) .setMaxValue(100)
.setRequired(true))) .setRequired(true)))
.addSubcommand(subcommand => subcommand
.setName('set-default-queue-page-size')
.setDescription('set the default page size of the /queue command')
.addIntegerOption(option => option
.setName('page-size')
.setDescription('page size of the /queue command')
.setMinValue(1)
.setMaxValue(30)
.setRequired(true)))
.addSubcommand(subcommand => subcommand .addSubcommand(subcommand => subcommand
.setName('get') .setName('get')
.setDescription('show all settings')); .setDescription('show all settings'));
@ -171,6 +180,23 @@ export default class implements Command {
break; break;
} }
case 'set-default-queue-page-size': {
const value = interaction.options.getInteger('page-size')!;
await prisma.setting.update({
where: {
guildId: interaction.guild!.id,
},
data: {
defaultQueuePageSize: value,
},
});
await interaction.reply('👍 default queue page size updated');
break;
}
case 'get': { case 'get': {
const embed = new EmbedBuilder().setTitle('Config'); const embed = new EmbedBuilder().setTitle('Config');
@ -185,6 +211,7 @@ export default class implements Command {
'Auto announce next song in queue': config.autoAnnounceNextSong ? 'yes' : 'no', 'Auto announce next song in queue': config.autoAnnounceNextSong ? 'yes' : 'no',
'Add to queue reponses show for requester only': config.autoAnnounceNextSong ? 'yes' : 'no', 'Add to queue reponses show for requester only': config.autoAnnounceNextSong ? 'yes' : 'no',
'Default Volume': config.defaultVolume, 'Default Volume': config.defaultVolume,
'Default queue page size': config.defaultQueuePageSize,
}; };
let description = ''; let description = '';

View file

@ -28,7 +28,10 @@ export default class implements Command {
.setDescription('shuffle the input if you\'re adding multiple tracks')) .setDescription('shuffle the input if you\'re adding multiple tracks'))
.addBooleanOption(option => option .addBooleanOption(option => option
.setName('split') .setName('split')
.setDescription('if a track has chapters, split it'))) .setDescription('if a track has chapters, split it'))
.addBooleanOption(option => option
.setName('skip')
.setDescription('skip the currently playing track')))
.addSubcommand(subcommand => subcommand .addSubcommand(subcommand => subcommand
.setName('list') .setName('list')
.setDescription('list all favorites')) .setDescription('list all favorites'))
@ -124,6 +127,7 @@ export default class implements Command {
shuffleAdditions: interaction.options.getBoolean('shuffle') ?? false, shuffleAdditions: interaction.options.getBoolean('shuffle') ?? false,
addToFrontOfQueue: interaction.options.getBoolean('immediate') ?? false, addToFrontOfQueue: interaction.options.getBoolean('immediate') ?? false,
shouldSplitChapters: interaction.options.getBoolean('split') ?? false, shouldSplitChapters: interaction.options.getBoolean('split') ?? false,
skipCurrentTrack: interaction.options.getBoolean('skip') ?? false,
}); });
} }

View file

@ -29,7 +29,10 @@ export default class implements Command {
.setDescription('shuffle the input if you\'re adding multiple tracks')) .setDescription('shuffle the input if you\'re adding multiple tracks'))
.addBooleanOption(option => option .addBooleanOption(option => option
.setName('split') .setName('split')
.setDescription('if a track has chapters, split it')); .setDescription('if a track has chapters, split it'))
.addBooleanOption(option => option
.setName('skip')
.setDescription('skip the currently playing track'));
public requiresVC = true; public requiresVC = true;
@ -52,6 +55,7 @@ export default class implements Command {
addToFrontOfQueue: interaction.options.getBoolean('immediate') ?? false, addToFrontOfQueue: interaction.options.getBoolean('immediate') ?? false,
shuffleAdditions: interaction.options.getBoolean('shuffle') ?? false, shuffleAdditions: interaction.options.getBoolean('shuffle') ?? false,
shouldSplitChapters: interaction.options.getBoolean('split') ?? false, shouldSplitChapters: interaction.options.getBoolean('split') ?? false,
skipCurrentTrack: interaction.options.getBoolean('skip') ?? false,
}); });
} }

View file

@ -5,6 +5,7 @@ import {TYPES} from '../types.js';
import PlayerManager from '../managers/player.js'; import PlayerManager from '../managers/player.js';
import Command from './index.js'; import Command from './index.js';
import {buildQueueEmbed} from '../utils/build-embed.js'; import {buildQueueEmbed} from '../utils/build-embed.js';
import {getGuildSettings} from '../utils/get-guild-settings.js';
@injectable() @injectable()
export default class implements Command { export default class implements Command {
@ -14,6 +15,12 @@ export default class implements Command {
.addIntegerOption(option => option .addIntegerOption(option => option
.setName('page') .setName('page')
.setDescription('page of queue to show [default: 1]') .setDescription('page of queue to show [default: 1]')
.setRequired(false))
.addIntegerOption(option => option
.setName('page-size')
.setDescription('how many items to display per page [default: 10, max: 30]')
.setMinValue(1)
.setMaxValue(30)
.setRequired(false)); .setRequired(false));
private readonly playerManager: PlayerManager; private readonly playerManager: PlayerManager;
@ -23,9 +30,17 @@ export default class implements Command {
} }
public async execute(interaction: ChatInputCommandInteraction) { public async execute(interaction: ChatInputCommandInteraction) {
const player = this.playerManager.get(interaction.guild!.id); const guildId = interaction.guild!.id;
const player = this.playerManager.get(guildId);
const embed = buildQueueEmbed(player, interaction.options.getInteger('page') ?? 1); const pageSizeFromOptions = interaction.options.getInteger('page-size');
const pageSize = pageSizeFromOptions ?? (await getGuildSettings(guildId)).defaultQueuePageSize;
const embed = buildQueueEmbed(
player,
interaction.options.getInteger('page') ?? 1,
pageSize,
);
await interaction.reply({embeds: [embed]}); await interaction.reply({embeds: [embed]});
} }

View file

@ -40,5 +40,5 @@ export default async (guild: Guild): Promise<void> => {
} }
const owner = await guild.fetchOwner(); const owner = await guild.fetchOwner();
await owner.send('👋 Hi! Someone (probably you) just invited me to a server you own. By default, I\'m usable by all guild member in all guild channels. To change this, check out the wiki page on permissions: https://github.com/codetheweb/muse/wiki/Configuring-Bot-Permissions.'); await owner.send('👋 Hi! Someone (probably you) just invited me to a server you own. By default, I\'m usable by all guild member in all guild channels. To change this, check out the wiki page on permissions: https://github.com/museofficial/muse/wiki/Configuring-Bot-Permissions.');
}; };

View file

@ -38,12 +38,14 @@ export default class AddQueryToQueue {
addToFrontOfQueue, addToFrontOfQueue,
shuffleAdditions, shuffleAdditions,
shouldSplitChapters, shouldSplitChapters,
skipCurrentTrack,
interaction, interaction,
}: { }: {
query: string; query: string;
addToFrontOfQueue: boolean; addToFrontOfQueue: boolean;
shuffleAdditions: boolean; shuffleAdditions: boolean;
shouldSplitChapters: boolean; shouldSplitChapters: boolean;
skipCurrentTrack: boolean;
interaction: ChatInputCommandInteraction; interaction: ChatInputCommandInteraction;
}): Promise<void> { }): Promise<void> {
const guildId = interaction.guild!.id; const guildId = interaction.guild!.id;
@ -169,6 +171,14 @@ export default class AddQueryToQueue {
await player.play(); await player.play();
} }
if (skipCurrentTrack) {
try {
await player.forward(1);
} catch (_: unknown) {
throw new Error('no song to skip to');
}
}
// Build response message // Build response message
if (statusMsg !== '') { if (statusMsg !== '') {
if (extraMsg === '') { if (extraMsg === '') {
@ -183,9 +193,9 @@ export default class AddQueryToQueue {
} }
if (newSongs.length === 1) { if (newSongs.length === 1) {
await interaction.editReply(`u betcha, **${firstSong.title}** added to the${addToFrontOfQueue ? ' front of the' : ''} queue${extraMsg}`); await interaction.editReply(`u betcha, **${firstSong.title}** added to the${addToFrontOfQueue ? ' front of the' : ''} queue${skipCurrentTrack ? 'and current track skipped' : ''}${extraMsg}`);
} else { } else {
await interaction.editReply(`u betcha, **${firstSong.title}** and ${newSongs.length - 1} other songs were added to the queue${extraMsg}`); await interaction.editReply(`u betcha, **${firstSong.title}** and ${newSongs.length - 1} other songs were added to the queue${skipCurrentTrack ? 'and current track skipped' : ''}${extraMsg}`);
} }
} }

View file

@ -1,7 +1,7 @@
import {VoiceChannel, Snowflake} from 'discord.js'; import {VoiceChannel, Snowflake} from 'discord.js';
import {Readable} from 'stream'; import {Readable} from 'stream';
import hasha from 'hasha'; import hasha from 'hasha';
import ytdl, {videoFormat} from 'ytdl-core'; import ytdl, {videoFormat} from '@distube/ytdl-core';
import {WriteStream} from 'fs-capacitor'; import {WriteStream} from 'fs-capacitor';
import ffmpeg from 'fluent-ffmpeg'; import ffmpeg from 'fluent-ffmpeg';
import shuffle from 'array-shuffle'; import shuffle from 'array-shuffle';
@ -280,8 +280,8 @@ export default class {
if (this.getCurrent() && this.status !== STATUS.PAUSED) { if (this.getCurrent() && this.status !== STATUS.PAUSED) {
await this.play(); await this.play();
} else { } else {
this.audioPlayer?.stop(true);
this.status = STATUS.IDLE; this.status = STATUS.IDLE;
this.audioPlayer?.stop(true);
const settings = await getGuildSettings(this.guildId); const settings = await getGuildSettings(this.guildId);

View file

@ -1,7 +1,7 @@
import {inject, injectable} from 'inversify'; import {inject, injectable} from 'inversify';
import {toSeconds, parse} from 'iso8601-duration'; import {toSeconds, parse} from 'iso8601-duration';
import got, {Got} from 'got'; import got, {Got} from 'got';
import ytsr, {Video} from 'ytsr'; import ytsr, {Video} from '@distube/ytsr';
import PQueue from 'p-queue'; import PQueue from 'p-queue';
import {SongMetadata, QueuedPlaylist, MediaSource} from './player.js'; import {SongMetadata, QueuedPlaylist, MediaSource} from './player.js';
import {TYPES} from '../types.js'; import {TYPES} from '../types.js';

View file

@ -5,8 +5,6 @@ import getProgressBar from './get-progress-bar.js';
import {prettyTime} from './time.js'; import {prettyTime} from './time.js';
import {truncate} from './string.js'; import {truncate} from './string.js';
const PAGE_SIZE = 10;
const getMaxSongTitleLength = (title: string) => { const getMaxSongTitleLength = (title: string) => {
// eslint-disable-next-line no-control-regex // eslint-disable-next-line no-control-regex
const nonASCII = /[^\x00-\x7F]+/; const nonASCII = /[^\x00-\x7F]+/;
@ -77,7 +75,7 @@ export const buildPlayingMessageEmbed = (player: Player): EmbedBuilder => {
return message; return message;
}; };
export const buildQueueEmbed = (player: Player, page: number): EmbedBuilder => { export const buildQueueEmbed = (player: Player, page: number, pageSize: number): EmbedBuilder => {
const currentlyPlaying = player.getCurrent(); const currentlyPlaying = player.getCurrent();
if (!currentlyPlaying) { if (!currentlyPlaying) {
@ -85,14 +83,14 @@ export const buildQueueEmbed = (player: Player, page: number): EmbedBuilder => {
} }
const queueSize = player.queueSize(); const queueSize = player.queueSize();
const maxQueuePage = Math.ceil((queueSize + 1) / PAGE_SIZE); const maxQueuePage = Math.ceil((queueSize + 1) / pageSize);
if (page > maxQueuePage) { if (page > maxQueuePage) {
throw new Error('the queue isn\'t that big'); throw new Error('the queue isn\'t that big');
} }
const queuePageBegin = (page - 1) * PAGE_SIZE; const queuePageBegin = (page - 1) * pageSize;
const queuePageEnd = queuePageBegin + PAGE_SIZE; const queuePageEnd = queuePageBegin + pageSize;
const queuedSongs = player const queuedSongs = player
.getQueue() .getQueue()
.slice(queuePageBegin, queuePageEnd) .slice(queuePageBegin, queuePageEnd)

130
yarn.lock
View file

@ -126,16 +126,16 @@
resolved "https://registry.npmjs.org/@discordjs/util/-/util-0.3.1.tgz" resolved "https://registry.npmjs.org/@discordjs/util/-/util-0.3.1.tgz"
integrity sha512-HxXKYKg7vohx2/OupUN/4Sd02Ev3PBJ5q0gtjdcvXb0ErCva8jNHWfe/v5sU3UKjIB/uxOhc+TDOnhqffj9pRA== integrity sha512-HxXKYKg7vohx2/OupUN/4Sd02Ev3PBJ5q0gtjdcvXb0ErCva8jNHWfe/v5sU3UKjIB/uxOhc+TDOnhqffj9pRA==
"@discordjs/voice@0.11.0": "@discordjs/voice@^0.17.0":
version "0.11.0" version "0.17.0"
resolved "https://registry.npmjs.org/@discordjs/voice/-/voice-0.11.0.tgz" resolved "https://registry.npmjs.org/@discordjs/voice/-/voice-0.17.0.tgz"
integrity sha512-6+9cj1dxzBJm7WJ9qyG2XZZQ8rcLl6x2caW0C0OxuTtMLAaEDntpb6lqMTFiBg/rDc4Rd59g1w0gJmib33CuHw== integrity sha512-hArn9FF5ZYi1IkxdJEVnJi+OxlwLV0NJYWpKXsmNOojtGtAZHxmsELA+MZlu2KW1F/K1/nt7lFOfcMXNYweq9w==
dependencies: dependencies:
"@types/ws" "^8.5.3" "@types/ws" "^8.5.10"
discord-api-types "^0.36.2" discord-api-types "0.37.83"
prism-media "^1.3.4" prism-media "^1.3.5"
tslib "^2.4.0" tslib "^2.6.2"
ws "^8.8.1" ws "^8.16.0"
"@discordjs/ws@^0.8.3": "@discordjs/ws@^0.8.3":
version "0.8.3" version "0.8.3"
@ -152,6 +152,25 @@
tslib "^2.5.0" tslib "^2.5.0"
ws "^8.13.0" ws "^8.13.0"
"@distube/ytdl-core@^4.14.4":
version "4.14.4"
resolved "https://registry.npmjs.org/@distube/ytdl-core/-/ytdl-core-4.14.4.tgz"
integrity sha512-dHb4GW3qATIjRsS6VIhm3Pop7FdUcDFhsnyQlsPeXW7UhTPuNS0BmraKiTpFbpp0Ky+rxBQjJBfPRFsM+dT1fg==
dependencies:
http-cookie-agent "^6.0.5"
m3u8stream "^0.8.6"
miniget "^4.2.3"
sax "^1.4.1"
tough-cookie "^4.1.4"
undici "^6.19.2"
"@distube/ytsr@^2.0.0":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@distube/ytsr/-/ytsr-2.0.4.tgz#873b7ca767b5ff362bc0e136ae0d9ca5cbde8f7b"
integrity sha512-OiSWgARQ9LTj+dXt3jmMFzUH4l86VVCD4dVC4hEHNXdqp+DyU4QEzc+W6YY6//kWkvzTaUxOo7JUY7lBzwIF0A==
dependencies:
undici "^6.18.2"
"@esbuild-kit/cjs-loader@^2.3.3": "@esbuild-kit/cjs-loader@^2.3.3":
version "2.3.3" version "2.3.3"
resolved "https://registry.npmjs.org/@esbuild-kit/cjs-loader/-/cjs-loader-2.3.3.tgz" resolved "https://registry.npmjs.org/@esbuild-kit/cjs-loader/-/cjs-loader-2.3.3.tgz"
@ -673,6 +692,13 @@ agent-base@6, agent-base@^6.0.0, agent-base@^6.0.2:
dependencies: dependencies:
debug "4" debug "4"
agent-base@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317"
integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==
dependencies:
debug "^4.3.4"
ajv@^6.10.0, ajv@^6.12.4: ajv@^6.10.0, ajv@^6.12.4:
version "6.12.6" version "6.12.6"
resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz"
@ -1290,6 +1316,13 @@ debug@^3.2.7:
dependencies: dependencies:
ms "^2.1.1" ms "^2.1.1"
debug@^4.3.4:
version "4.3.5"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e"
integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==
dependencies:
ms "2.1.2"
decode-uri-component@^0.2.0: decode-uri-component@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz" resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz"
@ -2584,6 +2617,13 @@ http-cache-semantics@^4.0.0:
resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz" resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz"
integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
http-cookie-agent@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/http-cookie-agent/-/http-cookie-agent-6.0.5.tgz#23b490439464424a689d80ea7f3a560a4a893ab8"
integrity sha512-sfZ8fDgDP3B1YB+teqSnAK1aPgBu8reUUGxSsndP2XnYN6cM29EURXWXZqQQiaRdor3B4QjpkUNfv21syaO4DA==
dependencies:
agent-base "^7.1.1"
http-errors@2.0.0: http-errors@2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz"
@ -3310,7 +3350,7 @@ lru-cache@^6.0.0:
m3u8stream@^0.8.6: m3u8stream@^0.8.6:
version "0.8.6" version "0.8.6"
resolved "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.8.6.tgz" resolved "https://registry.yarnpkg.com/m3u8stream/-/m3u8stream-0.8.6.tgz#0d6de4ce8ee69731734e6b616e7b05dd9d9a55b1"
integrity sha512-LZj8kIVf9KCphiHmH7sbFQTVe4tOemb202fWwvJwR9W5ENW/1hxJN6ksAWGhQgSBSa3jyWhnjKU1Fw1GaOdbyA== integrity sha512-LZj8kIVf9KCphiHmH7sbFQTVe4tOemb202fWwvJwR9W5ENW/1hxJN6ksAWGhQgSBSa3jyWhnjKU1Fw1GaOdbyA==
dependencies: dependencies:
miniget "^4.2.2" miniget "^4.2.2"
@ -3393,6 +3433,11 @@ miniget@^4.2.2:
resolved "https://registry.npmjs.org/miniget/-/miniget-4.2.2.tgz" resolved "https://registry.npmjs.org/miniget/-/miniget-4.2.2.tgz"
integrity sha512-a7voNL1N5lDMxvTMExOkg+Fq89jM2vY8pAi9ZEWzZtfNmdfP6RXkvUtFnCAXoCv2T9k1v/fUJVaAEuepGcvLYA== integrity sha512-a7voNL1N5lDMxvTMExOkg+Fq89jM2vY8pAi9ZEWzZtfNmdfP6RXkvUtFnCAXoCv2T9k1v/fUJVaAEuepGcvLYA==
miniget@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/miniget/-/miniget-4.2.3.tgz#3707a24c7c11c25d359473291638ab28aab349bd"
integrity sha512-SjbDPDICJ1zT+ZvQwK0hUcRY4wxlhhNpHL9nJOB2MEAXRGagTljsO8MEDzQMTFf0Q8g4QNi8P9lEm/g7e+qgzA==
minimatch@^3.0.4, minimatch@^3.1.2: minimatch@^3.0.4, minimatch@^3.1.2:
version "3.1.2" version "3.1.2"
resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
@ -3996,6 +4041,11 @@ proxy-from-env@^1.0.0, proxy-from-env@^1.1.0:
resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
psl@^1.1.33:
version "1.9.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==
pump@^3.0.0: pump@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz" resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz"
@ -4009,6 +4059,11 @@ punycode@^2.1.0:
resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
punycode@^2.1.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
pupa@^2.1.1: pupa@^2.1.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz" resolved "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz"
@ -4033,6 +4088,11 @@ query-string@^6.13.8:
split-on-first "^1.0.0" split-on-first "^1.0.0"
strict-uri-encode "^2.0.0" strict-uri-encode "^2.0.0"
querystringify@^2.1.1:
version "2.2.0"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
queue-microtask@^1.2.2: queue-microtask@^1.2.2:
version "1.2.3" version "1.2.3"
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
@ -4182,6 +4242,11 @@ require-from-string@^2.0.2:
resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz" resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
resolve-alpn@^1.2.0: resolve-alpn@^1.2.0:
version "1.2.1" version "1.2.1"
resolved "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz" resolved "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz"
@ -4317,10 +4382,10 @@ safe-regex-test@^1.0.3:
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sax@^1.1.3, sax@^1.2.4: sax@^1.2.4, sax@^1.4.1:
version "1.2.4" version "1.4.1"
resolved "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz" resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==
semver-compare@^1.0.0: semver-compare@^1.0.0:
version "1.0.0" version "1.0.0"
@ -4780,6 +4845,16 @@ token-types@^5.0.0-alpha.2, token-types@^5.0.1:
"@tokenizer/token" "^0.3.0" "@tokenizer/token" "^0.3.0"
ieee754 "^1.2.1" ieee754 "^1.2.1"
tough-cookie@^4.1.4:
version "4.1.4"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36"
integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==
dependencies:
psl "^1.1.33"
punycode "^2.1.1"
universalify "^0.2.0"
url-parse "^1.5.3"
tr46@~0.0.3: tr46@~0.0.3:
version "0.0.3" version "0.0.3"
resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"
@ -4955,6 +5030,11 @@ undici@^5.8.0:
resolved "https://registry.npmjs.org/undici/-/undici-5.8.1.tgz" resolved "https://registry.npmjs.org/undici/-/undici-5.8.1.tgz"
integrity sha512-iDRmWX4Zar/4A/t+1LrKQRm102zw2l9Wgat3LtTlTn8ykvMZmAmpq9tjyHEigx18FsY7IfATvyN3xSw9BDz0eA== integrity sha512-iDRmWX4Zar/4A/t+1LrKQRm102zw2l9Wgat3LtTlTn8ykvMZmAmpq9tjyHEigx18FsY7IfATvyN3xSw9BDz0eA==
undici@^6.18.2, undici@^6.19.2:
version "6.19.2"
resolved "https://registry.yarnpkg.com/undici/-/undici-6.19.2.tgz#231bc5de78d0dafb6260cf454b294576c2f3cd31"
integrity sha512-JfjKqIauur3Q6biAtHJ564e3bWa8VvT+7cSiOJHFbX4Erv6CLGDpg8z+Fmg/1OI/47RA+GI2QZaF48SSaLvyBA==
unique-string@^2.0.0: unique-string@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz" resolved "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz"
@ -4972,6 +5052,11 @@ universalify@^0.1.0:
resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz" resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz"
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
universalify@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
universalify@^2.0.0: universalify@^2.0.0:
version "2.0.1" version "2.0.1"
resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz" resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz"
@ -5021,6 +5106,14 @@ url-parse-lax@^3.0.0:
dependencies: dependencies:
prepend-http "^2.0.0" prepend-http "^2.0.0"
url-parse@^1.5.3:
version "1.5.10"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
dependencies:
querystringify "^2.1.1"
requires-port "^1.0.0"
util-deprecate@^1.0.1: util-deprecate@^1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
@ -5228,15 +5321,6 @@ yocto-queue@^1.0.0:
resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz"
integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==
ytdl-core@^4.11.5:
version "4.11.5"
resolved "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.11.5.tgz"
integrity sha512-27LwsW4n4nyNviRCO1hmr8Wr5J1wLLMawHCQvH8Fk0hiRqrxuIu028WzbJetiYH28K8XDbeinYW4/wcHQD1EXA==
dependencies:
m3u8stream "^0.8.6"
miniget "^4.2.2"
sax "^1.1.3"
ytsr@^3.8.4: ytsr@^3.8.4:
version "3.8.4" version "3.8.4"
resolved "https://registry.npmjs.org/ytsr/-/ytsr-3.8.4.tgz" resolved "https://registry.npmjs.org/ytsr/-/ytsr-3.8.4.tgz"