From 86348bd06b217528fd30df2251d971b2861d7b76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?To=CF=80?= Date: Sat, 2 Dec 2023 13:14:59 +0100 Subject: [PATCH] docs (#944) --- .github/workflows/build.yml | 6 +- .github/workflows/docs-pr.yml | 49 + .github/workflows/docs.yml | 49 + .gitignore | 2 + CHANGELOG.md | 7 + README.md | 243 +-- branding/lavalink-400.png | Bin 14450 -> 16484 bytes branding/lavalink.svg | 2 + docs/CNAME | 1 + .../api/Insomnia.json | 0 docs/api/index.md | 74 + PLUGINS.md => docs/api/plugins.md | 87 +- IMPLEMENTATION.md => docs/api/rest.md | 1438 +++++------------ docs/api/websocket.md | 454 ++++++ docs/assets/favicon.png | Bin 0 -> 2445 bytes docs/assets/images/lavalink.svg | 50 + docs/assets/logo.svg | 18 + docs/changelog/index.md | 119 ++ docs/changelog/v2.md | 36 + docs/changelog/v3.md | 315 ++++ docs/changelog/v4.md | 40 + docs/clients.md | 49 + docs/configuration/binary.md | 12 + docs/configuration/docker.md | 54 + docs/configuration/index.md | 115 ++ docs/configuration/systemd.md | 52 + docs/docker/Dockerfile | 9 + docs/docker/docker-compose.yml | 15 + docs/getting-started/faq.md | 46 + docs/getting-started/index.md | 33 + docs/getting-started/troubleshooting.md | 55 + docs/index.md | 60 + docs/mkdocs.yml | 127 ++ docs/overrides/home.html | 32 + docs/overrides/main.html | 5 + docs/plugins.md | 24 + docs/requirements.txt | 9 + docs/stylesheets/neoteroi-cards.css | 76 + docs/stylesheets/style.css | 51 + 39 files changed, 2503 insertions(+), 1311 deletions(-) create mode 100644 .github/workflows/docs-pr.yml create mode 100644 .github/workflows/docs.yml create mode 100644 docs/CNAME rename Lavalink-Insomnia.json => docs/api/Insomnia.json (100%) create mode 100644 docs/api/index.md rename PLUGINS.md => docs/api/plugins.md (68%) rename IMPLEMENTATION.md => docs/api/rest.md (54%) create mode 100644 docs/api/websocket.md create mode 100644 docs/assets/favicon.png create mode 100644 docs/assets/images/lavalink.svg create mode 100644 docs/assets/logo.svg create mode 100644 docs/changelog/index.md create mode 100644 docs/changelog/v2.md create mode 100644 docs/changelog/v3.md create mode 100644 docs/changelog/v4.md create mode 100644 docs/clients.md create mode 100644 docs/configuration/binary.md create mode 100644 docs/configuration/docker.md create mode 100644 docs/configuration/index.md create mode 100644 docs/configuration/systemd.md create mode 100644 docs/docker/Dockerfile create mode 100644 docs/docker/docker-compose.yml create mode 100644 docs/getting-started/faq.md create mode 100644 docs/getting-started/index.md create mode 100644 docs/getting-started/troubleshooting.md create mode 100644 docs/index.md create mode 100644 docs/mkdocs.yml create mode 100644 docs/overrides/home.html create mode 100644 docs/overrides/main.html create mode 100644 docs/plugins.md create mode 100644 docs/requirements.txt create mode 100644 docs/stylesheets/neoteroi-cards.css create mode 100644 docs/stylesheets/style.css diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 347e05b67..1db033e67 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,11 @@ name: Build on: push: branches: [ '**' ] - paths-ignore: [ '**.md' ] + paths-ignore: + - '**.md' + - 'docs/**' + - '.github/workflows/docs.yml' + - '.github/workflows/docs-pr.yml' workflow_call: secrets: DOCKER_USERNAME: diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml new file mode 100644 index 000000000..0703f9e4a --- /dev/null +++ b/.github/workflows/docs-pr.yml @@ -0,0 +1,49 @@ +name: Docs PR + +on: + pull_request_target: + branches: [ '**' ] + paths: + - 'docs/**' + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: read + deployments: write + steps: + - uses: actions/checkout@v3 + with: + ref: "${{ github.event.pull_request.merge_commit_sha }}" + fetch-depth: 0 + - uses: actions/setup-python@v4 + with: + python-version: 3.x + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + - uses: actions/cache@v3 + with: + key: mkdocs-material-${{ env.cache_id }} + path: .cache + restore-keys: | + mkdocs-material- + - run: pip install -r requirements.txt + working-directory: docs +# - run: mkdocs build --verbose --strict + - run: mkdocs build --verbose + working-directory: docs + - uses: actions/upload-pages-artifact@v1 + with: + path: 'site' + - uses: cloudflare/pages-action@v1 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + projectName: ${{ vars.CLOUDFLARE_PROJECT_NAME }} + directory: site + branch: pr-${{ github.event.pull_request.number }} + gitHubToken: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 000000000..8cbbbf354 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,49 @@ +name: Docs Push + +on: + push: + branches: [ '**' ] + paths: + - 'docs/**' + - '.github/workflows/docs.yml' + +concurrency: + group: pages-${{ github.ref }} + cancel-in-progress: true + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: read + deployments: write + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-python@v4 + with: + python-version: 3.x + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + - uses: actions/cache@v3 + with: + key: mkdocs-material-${{ env.cache_id }} + path: .cache + restore-keys: | + mkdocs-material- + - run: pip install -r requirements.txt + working-directory: docs +# - run: mkdocs build --verbose --strict + - run: mkdocs build --verbose + working-directory: docs + - uses: actions/upload-pages-artifact@v1 + with: + path: 'site' + - uses: cloudflare/pages-action@v1 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + projectName: ${{ vars.CLOUDFLARE_PROJECT_NAME }} + directory: site + gitHubToken: ${{ secrets.GITHUB_TOKEN }} + wranglerVersion: '3' diff --git a/.gitignore b/.gitignore index a26490138..d9ac554cd 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ build/* gradle.properties application.yml LavalinkServer/plugins +.cache/ +site/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 103055a5a..78fa3df62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,13 @@ The most noteworthy of these, as well as any features and breaking changes, are Contributors: [@topi314](https://github.com/topi314), [@freyacodes](https://github.com/freyacodes), [@DRSchlaubi](https://github.com/DRSchlaubi) and [@melike2d](https://github.com/melike2d) +## 3.7.8 +* Fix YouTube 403 errors +* Fix YouTube access token errors + +## 3.7.7 +* Add JDA-NAS support for musl (`x86-64`, `aarch64`) based systems (most notably `alpine`) + ## 3.7.6 * Update Lavaplayer to [`1.4.1`](https://github.com/Walkyst/lavaplayer-fork/releases/tag/1.4.1) & [`1.4.2`](https://github.com/Walkyst/lavaplayer-fork/releases/tag/1.4.2) * New support for `MUSL` based systems (most notably `alpine`) diff --git a/README.md b/README.md index 4b9767a89..1288be1cd 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,12 @@ A [basic example bot](Testbot) is available. > [!Warning] > Lavalink v4 is now in beta! See [the changelog](CHANGELOG.md) for more information. +## Getting started +* Pick one of the [up-to-date clients](https://lavalink.dev/clients). Advanced users can create their own using the [API documentation +](https://lavalink.dev/api/) +* See the [server configuration documentation](https://lavalink.dev/configuration/) for configuring your Lavalink server +* Explore [available plugins](https://lavalink.dev/plugins) for extra features +* See also our [FAQ](https://lavalink.dev/getting-started/faq)
Table of Contents @@ -22,12 +28,6 @@ A [basic example bot](Testbot) is available. - [Hardware Support](#hardware-support) - [Changelog](#changelog) - [Versioning policy](#versioning-policy) -- [Client libraries](#client-libraries) -- [Server configuration](#server-configuration) - - [Config](#config) - - [Binary](#binary) - - [Systemd Serivce](#systemd-service) - - [Docker](#docker)
@@ -95,236 +95,5 @@ Version numbers can come in different combinations, depending on the release typ `MAJOR.MINOR.PATCH-PRERELEASE` - Pre-release `MAJOR.MINOR.PATCH-PRERELEASE+BUILD` - Pre-release additional build metadata ---- -## Client libraries: -| Client | Platform | Compatible With | Additional Information | -|---------------------------------------------------------------------------|-----------------|--------------------------------------------|---------------------------------| -| [Lavalink-Client](https://github.com/lavalink-devs/Lavalink-Client) | Java/Kotlin/JVM | JDA/Discord4J/**Any** | Uses reactor | -| [Lavalink.kt](https://github.com/DRSchlaubi/Lavalink.kt) | Kotlin | Kord/JDA/**Any** | Kotlin Coroutines | -| [DisGoLink](https://github.com/disgoorg/disgolink) | Go | **Any** | | -| [Mafic](https://github.com/ooliver1/mafic) | Python | discord.py **V2**/nextcord/disnake/py-cord | | -| [Wavelink](https://github.com/PythonistaGuild/Wavelink/tree/feature/v3) | Python | discord.py **V2** | Pre-Release (Version 3+) | -| [Moonlink.js](https://github.com/1Lucas1apk/moonlink.js) | Node.js | **Any** | | -| [Magmastream](https://github.com/Blackfort-Hosting/magmastream) | Node.js | **Any** | | -| [Lavacord](https://github.com/lavacord/Lavacord) | Node.js | **Any** | | -| [Shoukaku](https://github.com/Deivu/Shoukaku) | Node.js | **Any** | | -| [Lavalink-Client](https://github.com/tomato6966/Lavalink-Client) | Node.js | **Any** | | -| [FastLink](https://github.com/PerformanC/FastLink) | Node.js | **Any** | | -| [Riffy](https://github.com/riffy-team/riffy) | Node.js | **Any** | | -| [DisCatSharp](https://github.com/Aiko-IT-Systems/DisCatSharp) | .NET | DisCatSharp | v10.4.2+ | -| [Lavalink4NET](https://github.com/angelobreuer/Lavalink4NET) | .NET | Discord.Net/DSharpPlus/Remora | v4+ | -| [Coglink](https://github.com/PerformanC/Coglink) | C | Concord | | -| [lavalink-rs](https://gitlab.com/vicky5124/lavalink-rs) | Rust, Python | **Any** | `tokio`-based, `asyncio`-based | -
-v3.7 supporting Client Libraries - -| Client | Platform | Compatible With | Additional Information | -|---------------------------------------------------------------|----------|--------------------------------------------|---------------------------------| -| [Lavalink.kt](https://github.com/DRSchlaubi/lavalink.kt) | Kotlin | JDA/Kord/**Any** | Kotlin Coroutines | -| [lavaplay.py](https://github.com/HazemMeqdad/lavaplay.py) | Python | **Any\*** | *`asyncio`-based libraries only | -| [Mafic](https://github.com/ooliver1/mafic) | Python | discord.py **V2**/nextcord/disnake/py-cord | | -| [Wavelink](https://github.com/PythonistaGuild/Wavelink) | Python | discord.py **V2** | Version >=2, <3 | -| [Pomice](https://github.com/cloudwithax/pomice) | Python | discord.py **V2** | | -| [Lavacord](https://github.com/lavacord/lavacord) | Node.js | **Any** | | -| [Poru](https://github.com/parasop/poru) | Node.js | **Any** | | -| [Shoukaku](https://github.com/Deivu/Shoukaku) | Node.js | **Any** | | -| [Cosmicord.js](https://github.com/SudhanPlayz/Cosmicord.js) | Node.js | **Any** | | -| [DisCatSharp](https://github.com/Aiko-IT-Systems/DisCatSharp) | .NET | DisCatSharp | Only prior v10.4.1 | -| [Nomia](https://github.com/DHCPCD9/Nomia) | .NET | DSharpPlus | | -| [Lavalink4NET](https://github.com/angelobreuer/Lavalink4NET) | .NET | Discord.Net/DSharpPlus | < v4 | -| [DisGoLink](https://github.com/disgoorg/disgolink) | Go | **Any** | | - -
- -Or alternatively, you can create your own client library, following the [implementation documentation](IMPLEMENTATION.md). -Any client libraries marked with `Unmaintained` have been marked as such as their repositories have not received any commits for at least 1 year since time of checking, -however they are listed as they may still support Lavalink, and/or have not needed maintenance, however keep in mind that compatibility and full feature support is not guaranteed. - -## Server configuration - -### Config - -The server configuration is done in `application.yml`. You can find an example configuration [here](LavalinkServer/application.yml.example). - -Alternatively, you can also use environment variables to configure the server. The environment variables are named the same as the keys in the `application.yml` file, but in uppercase and with `.` replaced with `_`. For example, `server.port` becomes `SERVER_PORT`. -For arrays, the index is appended to the key, starting at 0. For example, `LAVALINK_PLUGINS_0_DEPENDENCY` refers to the `dependency` key of the first plugin. - -
-List of all env keys - -```env -SERVER_PORT -SERVER_ADDRESS - -LAVALINK_PLUGINS_0_DEPENDENCY -LAVALINK_PLUGINS_0_REPOSITORY - -LAVALINK_PLUGINS_1_DEPENDENCY -LAVALINK_PLUGINS_1_REPOSITORY - -LAVALINK_PLUGINS_DIR - -LAVALINK_SERVER_PASSWORD -LAVALINK_SERVER_SOURCES_YOUTUBE -LAVALINK_SERVER_SOURCES_BANDCAMP -LAVALINK_SERVER_SOURCES_SOUNDCLOUD -LAVALINK_SERVER_SOURCES_TWITCH -LAVALINK_SERVER_SOURCES_VIMEO -LAVALINK_SERVER_SOURCES_HTTP -LAVALINK_SERVER_SOURCES_LOCAL - -LAVALINK_SERVER_FILTERS_VOLUME -LAVALINK_SERVER_FILTERS_EQUALIZER -LAVALINK_SERVER_FILTERS_KARAOKE -LAVALINK_SERVER_FILTERS_TIMESCALE -LAVALINK_SERVER_FILTERS_TREMOLO -LAVALINK_SERVER_FILTERS_VIBRATO -LAVALINK_SERVER_FILTERS_DISTORTION -LAVALINK_SERVER_FILTERS_ROTATION -LAVALINK_SERVER_FILTERS_CHANNEL_MIX -LAVALINK_SERVER_FILTERS_LOW_PASS - -LAVALINK_SERVER_BUFFER_DURATION_MS -LAVALINK_SERVER_FRAME_BUFFER_DURATION_MS -LAVALINK_SERVER_OPUS_ENCODING_QUALITY -LAVALINK_SERVER_RESAMPLING_QUALITY -LAVALINK_SERVER_TRACK_STUCK_THRESHOLD_MS -LAVALINK_SERVER_USE_SEEK_GHOSTING - -LAVALINK_SERVER_PLAYER_UPDATE_INTERVAL -LAVALINK_SERVER_YOUTUBE_SEARCH_ENABLED -LAVALINK_SERVER_SOUNDCLOUD_SEARCH_ENABLED - -LAVALINK_SERVER_GC_WARNINGS - -LAVALINK_SERVER_RATELIMIT_IP_BLOCKS -LAVALINK_SERVER_RATELIMIT_EXCLUDE_IPS -LAVALINK_SERVER_RATELIMIT_STRATEGY -LAVALINK_SERVER_RATELIMIT_SEARCH_TRIGGERS_FAIK -LAVALINK_SERVER_RATELIMIT_RETRY_LIMIT - -LAVALINK_SERVER_YOUTUBE_CONFIG_EMAIL -LAVALINK_SERVER_YOUTUBE_CONFIG_PASSWORD - -LAVALINK_SERVER_HTTP_CONFIG_PROXY_HOST -LAVALINK_SERVER_HTTP_CONFIG_PROXY_PORT -LAVALINK_SERVER_HTTP_CONFIG_PROXY_USER -LAVALINK_SERVER_HTTP_CONFIG_PROXY_PASSWORD - -METRICS_PROMETHEUS_ENABLED -METRICS_PROMETHEUS_ENDPOINT - -SENTRY_DSN -SENTRY_ENVIRONMENT -SENTRY_TAGS_SOME_KEY -SENTRY_TAGS_ANOTHER_KEY - -LOGGING_FILE_PATH -LOGGING_LEVEL_ROOT -LOGGING_LEVEL_LAVALINK - -LOGGING_REQUEST_ENABLED -LOGGING_REQUEST_INCLUDE_CLIENT_INFO -LOGGING_REQUEST_INCLUDE_HEADERS -LOGGING_REQUEST_INCLUDE_QUERY_STRING -LOGGING_REQUEST_INCLUDE_PAYLOAD -LOGGING_REQUEST_MAX_PAYLOAD_LENGTH - -LOGGING_LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE -LOGGING_LOGBACK_ROLLINGPOLICY_MAX_HISTORY -``` -
- - -### Binary -Download binaries from the [Download Server](https://repo.arbjerg.dev/artifacts/lavalink/), [GitHub releases](https://github.com/lavalink-devs/Lavalink/releases) (specific versions prior to `v3.5` can be found in the [CI Server](https://ci.fredboat.com/viewLog.html?buildId=lastSuccessful&buildTypeId=Lavalink_Build&tab=artifacts&guest=1)) or [GitHub actions](https://github.com/lavalink-devs/Lavalink/actions). - -Put an `application.yml` file in your working directory. ([Example here](LavalinkServer/application.yml.example)) - -Run with `java -jar Lavalink.jar` from the same directory - -### Systemd Service - -If you're using a Systemd-based Linux distribution you may want to install Lavalink as a background service. You will need to create a `lavalink.service` file inside `/usr/lib/systemd/system`. Create the file with the following template (replacing the values inside the `<>` brackets): - ```ini -[Unit] -# Describe the service -Description=Lavalink Service - -# Configure service order -After=syslog.target network.target - -[Service] -# The user which will run Lavalink -User= - -# The group which will run Lavalink -Group= - -# Where the program should start -WorkingDirectory= - -# The command to start Lavalink -ExecStart=java -Xmx4G -jar /Lavalink.jar - -# Restart the service if it crashes -Restart=on-failure - -# Delay each restart by 5s -RestartSec=5s - -[Install] -# Start this service as part of normal system start-up -WantedBy=multi-user.target -``` - -To initiate the service, run - -```shell -sudo systemctl daemon-reload -sudo systemctl enable lavalink -sudo systemctl start lavalink -``` - -In addition to the usual log files, you can also view the log with `sudo journalctl -u lavalink`. - -### Docker - -Docker images can be found under [packages](https://github.com/lavalink-devs/Lavalink/pkgs/container/lavalink) with old builds prior to `v3.7.4` being available on [Docker Hub](https://hub.docker.com/r/fredboat/lavalink/). -There are 2 image variants `Ubuntu` and `Alpine`, the `Alpine` variant is smaller and can be used with the `-alpine` suffix, for example `ghcr.io/lavalink-devs/lavalink:3-alpine`. - -Install [Docker](https://docs.docker.com/engine/install/) & [Docker Compose](https://docs.docker.com/compose/install/) - -Create a `docker-compose.yml` with the following content: -```yaml -version: "3.8" - -services: - lavalink: - image: ghcr.io/lavalink-devs/lavalink:4 # pin the image version to Lavalink v4 - container_name: lavalink - restart: unless-stopped - environment: - - _JAVA_OPTIONS=-Xmx6G # set Java options here - - SERVER_PORT=2333 # set lavalink server port - - LAVALINK_SERVER_PASSWORD=youshallnotpass # set password for lavalink - volumes: - - ./application.yml:/opt/Lavalink/application.yml # mount application.yml from the same directory or use environment variables - - ./plugins/:/opt/Lavalink/plugins/ # persist plugins between restarts, make sure to set the correct permissions (user: 322, group: 322) - networks: - - lavalink - expose: - - 2333 # lavalink exposes port 2333 to connect to for other containers (this is for documentation purposes only) - ports: - - 2333:2333 # you only need this if you want to make your lavalink accessible from outside of containers -networks: - lavalink: # create a lavalink network you can add other containers to, to give them access to Lavalink - name: lavalink -``` - -Run `docker compose up -d`. See [Docker Compose Up](https://docs.docker.com/engine/reference/commandline/compose_up/) - -If your bot also runs in a docker container you can make that container join the lavalink network and use `lavalink` (service name) as the hostname to connect. -See [Docker Networking](https://docs.docker.com/network/) & [Docker Compose Networking](https://docs.docker.com/compose/networking/) diff --git a/branding/lavalink-400.png b/branding/lavalink-400.png index 5e9e7ca24ff88a29a67406103714b501db0423b4..57cdf8ecbe1e1b19507f8bd49e77f00d8865187f 100644 GIT binary patch literal 16484 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 literal 14450 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 mcBcT8l^9rMySzd`wmcv?b?>$p **Note:** > If your plugin is developed in Kotlin make sure you are using **Kotlin v1.8.22** -Follow [these steps](https://github.com/lavalink-devs/lavalink-plugin-template#how-to-use-this-template) to setup a new Lavalink plugin +Follow [these steps](https://github.com/lavalink-devs/lavalink-plugin-template#how-to-use-this-template) to set up a new Lavalink plugin Now you can start writing your plugin. You can test your plugin against Lavalink by running Gradle with the `:runLavalink` Gradle task. The [Gradle plugin documentation](https://github.com/lavalink-devs/lavalink-gradle-plugin#running-the-plugin) might be helpful. @@ -115,3 +68,33 @@ class TestAudioPluginInfoModifier implements AudioPluginInfoModifier { // ... } ``` + +# Distributing your plugin + +The official plugin repository is hosted on https://maven.lavalink.dev. If you want to publish your plugin there, please reach out to us via [Discord](https://discord.gg/ZW4s47Ppw4) for credentials. +The Lavalink team has release (https://maven.lavalink.dev/releases) and snapshot (https://maven.lavalink.dev/snapshots) repositories which you can use to publish your plugin. +By default, Lavalink will look for the plugin in the Lavalink repository, but you can also specify a custom repository for each plugin in your `application.yml` file. + +```yaml + +lavalink: + plugins: + - dependency: "com.github.example:example-plugin:x.y.z" # required, the dependency to your plugin + repository: "https://maven.example.com/releases" # optional, defaults to https://maven.lavalink.dev/releases for releases + snapshot: false # optional, defaults to false, used to tell Lavalink to use the snapshot repository instead of the release repository +``` + +The default repositories can also be overridden in your `application.yml` file. + +```yaml +lavalink: + defaultPluginRepository: "https://maven.example.com/releases" # optional, defaults to https://maven.lavalink.dev/releases + defaultPluginSnapshotRepository: "https://maven.example.com/snapshots" # optional, defaults to https://maven.lavalink.dev/snapshots +``` + +Additionally, you can override the default plugin path where Lavalink saves and loads the downloaded plugins. + +```yaml +lavalink: + pluginsDir: "./lavalink-plugins" # optional, defaults to "./plugins" +``` \ No newline at end of file diff --git a/IMPLEMENTATION.md b/docs/api/rest.md similarity index 54% rename from IMPLEMENTATION.md rename to docs/api/rest.md index 3fab96b91..e2c5ecb2d 100644 --- a/IMPLEMENTATION.md +++ b/docs/api/rest.md @@ -1,767 +1,265 @@ -# Implementation guidelines - -How to write your own client. - -## Requirements - -* You must be able to send messages via a shard's gateway connection. -* You must be able to intercept voice server & voice state updates from the gateway on your shard connection. - -## Significant changes v3.7.0 -> v4.0.0 - -* removed all non version `/v3` or `/v4` endpoints (except `/version`). -* `/v4/websocket` does not accept any messages anymore. -* `v4` uses the `sessionId` instead of the `resumeKey` for resuming. -* `v4` now returns the tracks `artworkUrl` and `isrc` if the source supports it. -* removal of deprecated json fields like `track`. -* addition of `artworkUrl` and `isrc` fields to the [Track Info](#track-info) object. -* addition of the full [Track](#track) object in [TrackStartEvent](#trackstartevent), [TrackEndEvent](#trackendevent), [TrackExceptionEvent](#trackexceptionevent) and [TrackStuckEvent](#trackstuckevent). -* updated capitalization of [Track End Reason](#track-end-reason) and [Severity](#severity) -* reworked [Load Result](#track-loading-result) object - -
-v4.0.0 Migration Guide - -All websocket ops are removed as of `v4.0.0` and replaced with the following endpoints and json fields: -* `play` -> [Update Player Endpoint](#update-player) `encodedTrack` or `identifier` field -* `stop` -> [Update Player Endpoint](#update-player) `encodedTrack` field with `null` -* `pause` -> [Update Player Endpoint](#update-player) `pause` field -* `seek` -> [Update Player Endpoint](#update-player) `position` field -* `volume` -> [Update Player Endpoint](#update-player) `volume` field -* `filters` -> [Update Player Endpoint](#update-player) `filters` field -* `destroy` -> [Destroy Player Endpoint](#destroy-player) -* `voiceUpdate` -> [Update Player Endpoint](#update-player) `voice` field -* `configureResuming` -> [Update Session Endpoint](#update-session) - -
- -
-Older versions - -## Significant changes v3.6.0 -> v3.7.0 - -* Moved HTTP endpoints under the new `/v3` path with `/version` as the only exception. -* Deprecation of the old HTTP paths. -* WebSocket handshakes should be done with `/v3/websocket`. Handshakes on `/` are now deprecated. -* Deprecation of all client-to-server messages (play, stop, pause, seek, volume, filters, destroy, voiceUpdate & configureResuming). -* Addition of REST endpoints intended to replace client requests. -* Addition of new WebSocket dispatch [Ready OP](#ready-op) to get `sessionId` and `resume` status. -* Addition of new [Session](#update-session)/[Player](#get-player) REST API. -* Addition of `/v3/info`, replaces `/plugins`. -* Deprecation of `Track.track` in existing endpoints. Use `Track.encoded` instead. -* Deprecation of `TrackXEvent.track` in WebSocket dispatches. Use `TrackXEvent.encodedTrack` instead. -* Player now has a `state` field which contains the same structure as returned by the `playerUpdate` OP. - -
-v3.7.0 Migration Guide - -All websocket ops are deprecated as of `v3.7.0` and replaced with the following endpoints and json fields: - -* `play` -> [Update Player Endpoint](#update-player) `track` or `identifier` field -* `stop` -> [Update Player Endpoint](#update-player) `track` field with `null` -* `pause` -> [Update Player Endpoint](#update-player) `pause` field -* `seek` -> [Update Player Endpoint](#update-player) `position` field -* `volume` -> [Update Player Endpoint](#update-player) `volume` field -* `filters` -> [Update Player Endpoint](#update-player) `filters` field -* `destroy` -> [Destroy Player Endpoint](#destroy-player) -* `voiceUpdate` -> [Update Player Endpoint](#update-player) `voice` field -* `configureResuming` -> [Update Session Endpoint](#update-session) - -
- -## Significant changes v3.3 -> v3.4 - -* Added filters -* The `error` string on the `TrackExceptionEvent` has been deprecated and replaced by - the [Exception](#exception-object) object following the same structure as the `LOAD_FAILED` error on [`/loadtracks`](#track-loading). -* Added the `connected` boolean to player updates. -* Added source name to REST api track objects -* Clients are now requested to make their name known during handshake - -## Significant changes v2.0 -> v3.0 - -* The response of `/loadtracks` has been completely changed (again since the initial v3.0 pre-release). -* Lavalink v3.0 now reports its version as a handshake response header. - `Lavalink-Major-Version` has a value of `3` for v3.0 only. It's missing for any older version. - -## Significant changes v1.3 -> v2.0 - -With the release of v2.0 many unnecessary ops were removed: - -* `connect` -* `disconnect` -* `validationRes` -* `isConnectedRes` -* `validationReq` -* `isConnectedReq` -* `sendWS` - -With Lavalink 1.x the server had the responsibility of handling Discord `VOICE_SERVER_UPDATE`s as well as its own internal ratelimiting. -This remote handling makes things unnecessarily complicated and adds a lot og points where things could go wrong. -One problem we noticed is that since JDA is unaware of ratelimits on the bot's gateway connection, it would keep adding -to the ratelimit queue to the gateway. With this update this is now the responsibility of the Lavalink client or the -Discord client. - -A voice connection is now initiated by forwarding a `voiceUpdate` (VOICE_SERVER_UPDATE) to the server. When you want to -disconnect or move to a different voice channel you must send Discord a new VOICE_STATE_UPDATE. If you want to move your -connection to a new Lavalink server you can simply send the VOICE_SERVER_UPDATE to the new node, and the other node -will be disconnected by Discord. - -Depending on your Discord library, it may be possible to take advantage of the library's OP 4 handling. For instance, -the JDA client takes advantage of JDA's websocket write thread to send OP 4s for connects, disconnects and reconnects. - -
- -## Protocol - -### Reference - -Fields marked with `?` are optional and types marked with `?` are nullable. - -### Opening a connection - -You can establish a WebSocket connection against the path `/v4/websocket`. - -When opening a websocket connection, you must supply 3 required headers: - -| Header Name | Description | -|-----------------|-------------------------------------------------| -| `Authorization` | The password you set in your Lavalink config | -| `User-Id` | The user id of the bot | -| `Client-Name` | The name of the client in `NAME/VERSION` format | -| `Session-Id`? * | The id of the previous session to resume | +--- +description: Lavalink REST API documentation. +--- -**\*For more information on resuming see [Resuming](#resuming-lavalink-sessions)** +# REST API -
-Example Headers +Lavalink exposes a REST API to allow for easy control of the players. +Most routes require the `Authorization` header with the configured password. ``` Authorization: youshallnotpass -User-Id: 170939974227541168 -Client-Name: lavalink-client/2.0.0 ``` -
- -### Websocket Messages - -Websocket messages all follow the following standard format: - -| Field | Type | Description | -|-------|----------------------|---------------------------------------| -| op | [OP Type](#op-types) | The op type | -| ... | ... | Extra fields depending on the op type | - -
-Example Payload - -```yaml -{ - "op": "...", - ... -} -``` - -
+Routes are prefixed with `/v3` as of `v3.7.0` and `/v4` as of `v4.0.0`. Routes without an API prefix were removed in v4 (except `/version`). -#### OP Types +## Insomnia Collection -| OP Type | Description | -|-----------------------------------|---------------------------------------------------------------| -| [ready](#ready-op) | Dispatched when you successfully connect to the Lavalink node | -| [playerUpdate](#player-update-op) | Dispatched every x seconds with the latest player state | -| [stats](#stats-op) | Dispatched when the node sends stats once per minute | -| [event](#event-op) | Dispatched when player or voice events occur | +You can find an [Insomnia](https://insomnia.rest/) collection in the [here](Insomnia.json) which contains all the endpoints and their respective payloads. -#### Ready OP +## Error Responses -Dispatched by Lavalink upon successful connection and authorization. Contains fields determining if resuming was successful, as well as the session id. +When Lavalink encounters an error, it will respond with a JSON object containing more information about the error. Include the `trace=true` query param to also receive the full stack trace. -| Field | Type | Description | -|-----------|--------|------------------------------------------------------------------------------------------------| -| resumed | bool | Whether this session was resumed | -| sessionId | string | The Lavalink session id of this connection. Not to be confused with a Discord voice session id | +| Field | Type | Description | +|-----------|--------|-----------------------------------------------------------------------------| +| timestamp | int | The timestamp of the error in milliseconds since the Unix epoch | +| status | int | The HTTP status code | +| error | string | The HTTP status code message | +| trace? | string | The stack trace of the error when `trace=true` as query param has been sent | +| message | string | The error message | +| path | string | The request path |
Example Payload ```json { - "op": "ready", - "resumed": false, - "sessionId": "..." + "timestamp": 1667857581613, + "status": 404, + "error": "Not Found", + "trace": "...", + "message": "Session not found", + "path": "/v4/sessions/xtaug914v9k5032f/players/817327181659111454" } ```
---- -#### Player Update OP +## Track API -Dispatched every x seconds (configurable in `application.yml`) with the current state of the player. +### Common Types ### {: #track-api-types } -| Field | Type | Description | -|---------|--------------------------------------|----------------------------| -| guildId | string | The guild id of the player | -| state | [Player State](#player-state) object | The player state | +#### Track -##### Player State +| Field | Type | Description | +|------------|----------------------------------|-----------------------------------------| +| encoded | string | The base64 encoded track data | +| info | [Track Info](#track-info) object | Info about the track | +| pluginInfo | object | Addition track info provided by plugins | -| Field | Type | Description | -|-----------|------|------------------------------------------------------------------------------------------| -| time | int | Unix timestamp in milliseconds | -| position | int | The position of the track in milliseconds | -| connected | bool | Whether Lavalink is connected to the voice gateway | -| ping | int | The ping of the node to the Discord voice server in milliseconds (`-1` if not connected) | +#### Track Info -
-Example Payload +| Field | Type | Description | +|------------|---------|---------------------------------------------------------------------------------------| +| identifier | string | The track identifier | +| isSeekable | bool | Whether the track is seekable | +| author | string | The track author | +| length | int | The track length in milliseconds | +| isStream | bool | Whether the track is a stream | +| position | int | The track position in milliseconds | +| title | string | The track title | +| uri | ?string | The track uri | +| artworkUrl | ?string | The track artwork url | +| isrc | ?string | The track [ISRC](https://en.wikipedia.org/wiki/International_Standard_Recording_Code) | +| sourceName | string | The track source name | -```json -{ - "op": "playerUpdate", - "guildId": "...", - "state": { - "time": 1500467109, - "position": 60000, - "connected": true, - "ping": 50 - } -} -``` +#### Playlist Info -
+| Field | Type | Description | +|---------------|--------|-----------------------------------------------------------------| +| name | string | The name of the playlist | +| selectedTrack | int | The selected track of the playlist (-1 if no track is selected) | --- -#### Stats OP +### Track Loading -A collection of statistics sent every minute. - -##### Stats Object - -| Field | Type | Description | -|----------------|-------------------------------------|--------------------------------------------------------------------------------------------------| -| players | int | The amount of players connected to the node | -| playingPlayers | int | The amount of players playing a track | -| uptime | int | The uptime of the node in milliseconds | -| memory | [Memory](#memory) object | The memory stats of the node | -| cpu | [CPU](#cpu) object | The cpu stats of the node | -| frameStats | ?[Frame Stats](#frame-stats) object | The frame stats of the node. `null` if the node has no players or when retrieved via `/v4/stats` | - -##### Memory - -| Field | Type | Description | -|------------|------|------------------------------------------| -| free | int | The amount of free memory in bytes | -| used | int | The amount of used memory in bytes | -| allocated | int | The amount of allocated memory in bytes | -| reservable | int | The amount of reservable memory in bytes | - -##### CPU +This endpoint is used to resolve audio tracks for use with the [Update Player](#update-player) endpoint. -| Field | Type | Description | -|--------------|-------|----------------------------------| -| cores | int | The amount of cores the node has | -| systemLoad | float | The system load of the node | -| lavalinkLoad | float | The load of Lavalink on the node | -##### Frame Stats +!!! note + + Lavalink supports searching via YouTube, YouTube Music, and Soundcloud. To search, you must prefix your identifier with `ytsearch:`, `ytmsearch:` or `scsearch:` respectively. + + When a search prefix is used, the returned `loadType` will be `search`. Note that disabling the respective source managers renders these search prefixes useless. -| Field | Type | Description | -|-----------|------|----------------------------------------------------------------------| -| sent | int | The amount of frames sent to Discord | -| nulled | int | The amount of frames that were nulled | -| deficit * | int | The difference between sent frames and the expected amount of frames | + Plugins may also implement prefixes to allow for more search engines to be utilised. -\* The expected amount of frames is 3000 (1 every 20 ms) per player. If the `deficit` is negative, too many frames were sent, and if it's positive, not enough frames got sent. -
-Example Payload - -```json -{ - "op": "stats", - "players": 1, - "playingPlayers": 1, - "uptime": 123456789, - "memory": { - "free": 123456789, - "used": 123456789, - "allocated": 123456789, - "reservable": 123456789 - }, - "cpu": { - "cores": 4, - "systemLoad": 0.5, - "lavalinkLoad": 0.5 - }, - "frameStats": { - "sent": 6000, - "nulled": 10, - "deficit": -3010 - } -} ``` - -
- ---- - -#### Event OP - -Server dispatched an event. See the [Event Types](#event-types) section for more information. - -| Field | Type | Description | -|---------|---------------------------|-------------------------------------| -| type | [EventType](#event-types) | The type of event | -| guildId | string | The guild id | -| ... | ... | Extra fields depending on the event | - -
-Example Payload - -```yaml -{ - "op": "event", - "type": "...", - "guildId": "...", - ... -} +GET /v4/loadtracks?identifier=dQw4w9WgXcQ ``` -
- -##### Event Types - -| Event Type | Description | -|-----------------------------------------------|-----------------------------------------------------------------------------| -| [TrackStartEvent](#trackstartevent) | Dispatched when a track starts playing | -| [TrackEndEvent](#trackendevent) | Dispatched when a track ends | -| [TrackExceptionEvent](#trackexceptionevent) | Dispatched when a track throws an exception | -| [TrackStuckEvent](#trackstuckevent) | Dispatched when a track gets stuck while playing | -| [WebSocketClosedEvent](#websocketclosedevent) | Dispatched when the websocket connection to Discord voice servers is closed | - -##### TrackStartEvent - -Dispatched when a track starts playing. - -| Field | Type | Description | -|-------|------------------------|--------------------------------| -| track | [Track](#track) object | The track that started playing | +Response: -
-Example Payload +#### Track Loading Result -```json -{ - "op": "event", - "type": "TrackStartEvent", - "guildId": "...", - "track": { - "encoded": "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==", - "info": { - "identifier": "dQw4w9WgXcQ", - "isSeekable": true, - "author": "RickAstleyVEVO", - "length": 212000, - "isStream": false, - "position": 0, - "title": "Rick Astley - Never Gonna Give You Up", - "uri": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", - "artworkUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", - "isrc": null, - "sourceName": "youtube" - }, - "pluginInfo": {} - } -} -``` - -
- ---- +| Field | Type | Description | +|----------|-------------------------------------|------------------------| +| loadType | [LoadResultType](#load-result-type) | The type of the result | +| data | [LoadResultData](#load-result-data) | The data of the result | -##### TrackEndEvent +#### Load Result Type -Dispatched when a track ends. +| Load Result Type | Description | +|------------------|-----------------------------------------------| +| `track` | A track has been loaded | +| `playlist` | A playlist has been loaded | +| `search` | A search result has been loaded | +| `empty` | There has been no matches for your identifier | +| `error` | Loading has failed with an error | -| Field | Type | Description | -|--------|-------------------------------------|------------------------------| -| track | [Track](#track) object | The track that ended playing | -| reason | [TrackEndReason](#track-end-reason) | The reason the track ended | +#### Load Result Data -##### Track End Reason +##### Track Result Data -| Reason | Description | May Start Next | -|--------------|----------------------------|----------------| -| `finished` | The track finished playing | true | -| `loadFailed` | The track failed to load | true | -| `stopped` | The track was stopped | false | -| `replaced` | The track was replaced | false | -| `cleanup` | The track was cleaned up | false | +[Track](#track) object with the loaded track.
Example Payload -```json +```yaml { - "op": "event", - "type": "TrackEndEvent", - "guildId": "...", - "track": { - "encoded": "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==", - "info": { - "identifier": "dQw4w9WgXcQ", - "isSeekable": true, - "author": "RickAstleyVEVO", - "length": 212000, - "isStream": false, - "position": 0, - "title": "Rick Astley - Never Gonna Give You Up", - "uri": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", - "artworkUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", - "isrc": null, - "sourceName": "youtube" - }, - "pluginInfo": {} - }, - "reason": "finished" + "loadType": "track", + "data": { + "encoded": "...", + "info": { ... }, + "pluginInfo": { ... } + } } ```
---- - -##### TrackExceptionEvent - -Dispatched when a track throws an exception. - -| Field | Type | Description | -|-----------|---------------------------------------|------------------------------------| -| track | [Track](#track) object | The track that threw the exception | -| exception | [Exception](#exception-object) object | The occurred exception | - -##### Exception Object - -| Field | Type | Description | -|----------|-----------------------|-------------------------------| -| message | ?string | The message of the exception | -| severity | [Severity](#severity) | The severity of the exception | -| cause | string | The cause of the exception | +##### Playlist Result Data -##### Severity - -| Severity | Description | -|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `common` | The cause is known and expected, indicates that there is nothing wrong with the library itself | -| `suspicious` | The cause might not be exactly known, but is possibly caused by outside factors. For example when an outside service responds in a format that we do not expect | -| `fault` | The probable cause is an issue with the library or there is no way to tell what the cause might be. This is the default level and other levels are used in cases where the thrower has more in-depth knowledge about the error | +| Field | Type | Description | +|------------|---------------------------------------|--------------------------------------------| +| info | [PlaylistInfo](#playlist-info) object | The info of the playlist | +| pluginInfo | Object | Addition playlist info provided by plugins | +| tracks | array of [Track](#track) objects | The tracks of the playlist |
Example Payload -```json +```yaml { - "op": "event", - "type": "TrackExceptionEvent", - "guildId": "...", - "track": { - "encoded": "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==", - "info": { - "identifier": "dQw4w9WgXcQ", - "isSeekable": true, - "author": "RickAstleyVEVO", - "length": 212000, - "isStream": false, - "position": 0, - "title": "Rick Astley - Never Gonna Give You Up", - "uri": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", - "artworkUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", - "isrc": null, - "sourceName": "youtube" - }, - "pluginInfo": {} - }, - "exception": { - "message": "...", - "severity": "common", - "cause": "..." + "loadType": "playlist", + "data": { + "info": { ... }, + "pluginInfo": { ... }, + "tracks": [ ... ] } } ```
---- - -##### TrackStuckEvent +##### Search Result Data -Dispatched when a track gets stuck while playing. - -| Field | Type | Description | -|-------------|------------------------|-------------------------------------------------| -| track | [Track](#track) object | The track that got stuck | -| thresholdMs | int | The threshold in milliseconds that was exceeded | +Array of [Track](#track) objects from the search result.
Example Payload -```json +```yaml { - "op": "event", - "type": "TrackStuckEvent", - "guildId": "...", - "track": { - "encoded": "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==", - "info": { - "identifier": "dQw4w9WgXcQ", - "isSeekable": true, - "author": "RickAstleyVEVO", - "length": 212000, - "isStream": false, - "position": 0, - "title": "Rick Astley - Never Gonna Give You Up", - "uri": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", - "artworkUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", - "isrc": null, - "sourceName": "youtube" + "loadType": "search", + "data": [ + { + "encoded": "...", + "info": { ... }, + "pluginInfo": { ... } }, - "pluginInfo": {} - }, - "thresholdMs": 123456789 + ... + ] } ```
---- - -##### WebSocketClosedEvent +##### Empty Result Data -Dispatched when an audio WebSocket (to Discord) is closed. -This can happen for various reasons (normal and abnormal), e.g. when using an expired voice server update. -4xxx codes are usually bad. -See the [Discord Docs](https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice-voice-close-event-codes). - -| Field | Type | Description | -|----------|--------|-----------------------------------------------------------------------------------------------------------------------------------| -| code | int | The [Discord close event code](https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice-voice-close-event-codes) | -| reason | string | The close reason | -| byRemote | bool | Whether the connection was closed by Discord | +Empty object.
Example Payload -```json +```yaml { - "op": "event", - "type": "WebSocketClosedEvent", - "guildId": "...", - "code": 4006, - "reason": "Your session is no longer valid.", - "byRemote": true + "loadType": "empty", + "data": {} } ```
---- - -### REST API +##### Error Result Data -Lavalink exposes a REST API to allow for easy control of the players. -Most routes require the `Authorization` header with the configured password. - -``` -Authorization: youshallnotpass -``` - -Routes are prefixed with `/v3` as of `v3.7.0` and `/v4` as of `v4.0.0`. Routes without an API prefix were removed in v4 (except `/version`). - -#### Error Responses - -When Lavalink encounters an error, it will respond with a JSON object containing more information about the error. Include the `trace=true` query param to also receive the full stack trace. - -| Field | Type | Description | -|-----------|--------|-----------------------------------------------------------------------------| -| timestamp | int | The timestamp of the error in milliseconds since the Unix epoch | -| status | int | The HTTP status code | -| error | string | The HTTP status code message | -| trace? | string | The stack trace of the error when `trace=true` as query param has been sent | -| message | string | The error message | -| path | string | The request path | +[Exception](websocket.md#exception-object) object with the error.
Example Payload -```json +```yaml { - "timestamp": 1667857581613, - "status": 404, - "error": "Not Found", - "trace": "...", - "message": "Session not found", - "path": "/v4/sessions/xtaug914v9k5032f/players/817327181659111454" + "loadType": "error", + "data": { + "message": "Something went wrong", + "severity": "fault", + "cause": "..." + } } ```
-#### Get Players - -Returns a list of players in this specific session. - -``` -GET /v4/sessions/{sessionId}/players -``` - -##### Player - -| Field | Type | Description | -|---------|--------------------------------------|-------------------------------------------------------| -| guildId | string | The guild id of the player | -| track | ?[Track](#track) object | The currently playing track | -| volume | int | The volume of the player, range 0-1000, in percentage | -| paused | bool | Whether the player is paused | -| state | [Player State](#player-state) object | The state of the player | -| voice | [Voice State](#voice-state) object | The voice state of the player | -| filters | [Filters](#filters) object | The filters used by the player | - -##### Track - -| Field | Type | Description | -|------------|----------------------------------|-----------------------------------------| -| encoded | string | The base64 encoded track data | -| info | [Track Info](#track-info) object | Info about the track | -| pluginInfo | object | Addition track info provided by plugins | - -##### Track Info - -| Field | Type | Description | -|------------|---------|---------------------------------------------------------------------------------------| -| identifier | string | The track identifier | -| isSeekable | bool | Whether the track is seekable | -| author | string | The track author | -| length | int | The track length in milliseconds | -| isStream | bool | Whether the track is a stream | -| position | int | The track position in milliseconds | -| title | string | The track title | -| uri | ?string | The track uri | -| artworkUrl | ?string | The track artwork url | -| isrc | ?string | The track [ISRC](https://en.wikipedia.org/wiki/International_Standard_Recording_Code) | -| sourceName | string | The track source name | - -##### Voice State - -| Field | Type | Description | -|------------|--------|---------------------------------------------------------------------------------------------| -| token | string | The Discord voice token to authenticate with | -| endpoint | string | The Discord voice endpoint to connect to | -| sessionId | string | The Discord voice session id to authenticate with | - -`token`, `endpoint`, and `sessionId` are the 3 required values for connecting to one of Discord's voice servers. -`sessionId` is provided by the Voice State Update event sent by Discord, whereas the `endpoint` and `token` are provided -with the Voice Server Update. Please refer to https://discord.com/developers/docs/topics/gateway-events#voice - -
-Example Payload - -```yaml -[ - { - "guildId": "...", - "track": { - "encoded": "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==", - "info": { - "identifier": "dQw4w9WgXcQ", - "isSeekable": true, - "author": "RickAstleyVEVO", - "length": 212000, - "isStream": false, - "position": 60000, - "title": "Rick Astley - Never Gonna Give You Up", - "uri": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", - "artworkUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", - "isrc": null, - "sourceName": "youtube" - }, - "pluginInfo": {} - }, - "volume": 100, - "paused": false, - "state": { - "time": 1500467109, - "position": 60000, - "connected": true, - "ping": 50 - }, - "voice": { - "token": "...", - "endpoint": "...", - "sessionId": "..." - }, - "filters": { ... } - }, - ... -] -``` - -
- --- -#### Get Player +### Track Decoding -Returns the player for this guild in this session. +Decode a single track into its info, where `BASE64` is the encoded base64 data. ``` -GET /v4/sessions/{sessionId}/players/{guildId} +GET /v4/decodetrack?encodedTrack=BASE64 ``` Response: -[Player](#Player) object +[Track](#track) object
Example Payload ```yaml { - "guildId": "...", - "track": { - "encoded": "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==", - "info": { - "identifier": "dQw4w9WgXcQ", - "isSeekable": true, - "author": "RickAstleyVEVO", - "length": 212000, - "isStream": false, - "position": 60000, - "title": "Rick Astley - Never Gonna Give You Up", - "uri": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", - "artworkUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", - "isrc": null, - "sourceName": "youtube" - } - }, - "volume": 100, - "paused": false, - "state": { - "time": 1500467109, - "position": 60000, - "connected": true, - "ping": 50 - }, - "voice": { - "token": "...", - "endpoint": "...", - "sessionId": "..." + "encoded": "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==", + "info": { + "identifier": "dQw4w9WgXcQ", + "isSeekable": true, + "author": "RickAstleyVEVO", + "length": 212000, + "isStream": false, + "position": 0, + "title": "Rick Astley - Never Gonna Give You Up", + "uri": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", + "artworkUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", + "isrc": null, + "sourceName": "youtube" }, - "filters": { ... } + "pluginInfo": {} } ``` @@ -769,72 +267,38 @@ Response: --- -#### Update Player - -Updates or creates the player for this guild if it doesn't already exist. - -``` -PATCH /v4/sessions/{sessionId}/players/{guildId}?noReplace=true -``` - -Query Params: - -| Field | Type | Description | -|------------|------|------------------------------------------------------------------------------| -| noReplace? | bool | Whether to replace the current track with the new track. Defaults to `false` | - -Request: - -| Field | Type | Description | -|-----------------|------------------------------------|-----------------------------------------------------------------------------------------------| -| encodedTrack? * | ?string | The base64 encoded track to play. `null` stops the current track | -| identifier? * | string | The identifier of the track to play | -| position? | int | The track position in milliseconds | -| endTime? | ?int | The track end time in milliseconds (must be > 0). `null` resets this if it was set previously | -| volume? | int | The player volume, in percentage, from 0 to 1000 | -| paused? | bool | Whether the player is paused | -| filters? | [Filters](#filters) object | The new filters to apply. This will override all previously applied filters | -| voice? | [Voice State](#voice-state) object | Information required for connecting to Discord | +Decodes multiple tracks into their info -> **Note** -> - \* `encodedTrack` and `identifier` are mutually exclusive. -> - `sessionId` in the path should be the value from the [ready op](#ready-op). +``` +POST /v4/decodetracks +``` -When `identifier` is used, Lavalink will try to resolve the identifier as a single track. An HTTP `400` error is returned when resolving a playlist, search result, or no tracks. +Request: + +Array of track data strings
Example Payload ```yaml -{ - "encodedTrack": "...", - "identifier": "...", - "endTime": 0, - "volume": 100, - "position": 32400, - "paused": false, - "filters": { ... }, - "voice": { - "token": "...", - "endpoint": "...", - "sessionId": "..." - } -} +[ + "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==", + ... +] ```
Response: -[Player](#Player) object +Array of [Track](#track) objects
Example Payload ```yaml -{ - "guildId": "...", - "track": { +[ + { "encoded": "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==", "info": { "identifier": "dQw4w9WgXcQ", @@ -842,35 +306,52 @@ Response: "author": "RickAstleyVEVO", "length": 212000, "isStream": false, - "position": 60000, + "position": 0, "title": "Rick Astley - Never Gonna Give You Up", "uri": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "artworkUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", "isrc": null, "sourceName": "youtube" - } - }, - "volume": 100, - "paused": false, - "state": { - "time": 1500467109, - "position": 60000, - "connected": true, - "ping": 50 - }, - "voice": { - "token": "...", - "endpoint": "...", - "sessionId": "..." + }, + "pluginInfo": {} }, - "filters": { ... } -} + ... +] ```
--- +## Player API + +### Common Types ### {: #player-api-types } + +#### Player + +| Field | Type | Description | +|---------|--------------------------------------------------|-------------------------------------------------------| +| guildId | string | The guild id of the player | +| track | ?[Track](#track) object | The currently playing track | +| volume | int | The volume of the player, range 0-1000, in percentage | +| paused | bool | Whether the player is paused | +| state | [Player State](websocket.md#player-state) object | The state of the player | +| voice | [Voice State](#voice-state) object | The voice state of the player | +| filters | [Filters](#filters) object | The filters used by the player | + +#### Voice State + +| Field | Type | Description | +|-----------|--------|---------------------------------------------------| +| token | string | The Discord voice token to authenticate with | +| endpoint | string | The Discord voice endpoint to connect to | +| sessionId | string | The Discord voice session id to authenticate with | + +`token`, `endpoint`, and `sessionId` are the 3 required values for connecting to one of Discord's voice servers. +`sessionId` is provided by the Voice State Update event sent by Discord, whereas the `endpoint` and `token` are provided +with the Voice Server Update. Please refer to https://discord.com/developers/docs/topics/gateway-events#voice + + #### Filters Filters are used in above requests and look like this @@ -1008,9 +489,9 @@ Any smoothing values equal to or less than 1.0 will disable the filter. |------------|-------|--------------------------------| | smoothing? | float | The smoothing factor (1.0 < x) | -##### Plugin Filter +##### Plugin Filters -Plugins can add their own filters. The key is the name of the plugin, and the value is the configuration for that plugin. The configuration is plugin specific. See [Plugins](PLUGINS.md) for more plugin information. +Plugins can add their own filters. The key is the name of the plugin, and the value is the configuration for that plugin. The configuration is plugin specific. See [Plugins](plugins.md) for more plugin information.
Example Payload @@ -1077,202 +558,211 @@ Plugins can add their own filters. The key is the name of the plugin, and the va --- -#### Destroy Player - -Destroys the player for this guild in this session. - -``` -DELETE /v4/sessions/{sessionId}/players/{guildId} -``` - -Response: - -204 - No Content - ---- - -#### Update Session +### Get Players -Updates the session with the resuming state and timeout. +Returns a list of players in this specific session. ``` -PATCH /v4/sessions/{sessionId} -``` - -Request: - -| Field | Type | Description | -|-----------|------|-----------------------------------------------------| -| resuming? | bool | Whether resuming is enabled for this session or not | -| timeout? | int | The timeout in seconds (default is 60s) | - -
-Example Payload - -```json -{ - "resuming": false, - "timeout": 0 -} +GET /v4/sessions/{sessionId}/players ``` -
- -Response: - -| Field | Type | Description | -|----------|------|-----------------------------------------------------| -| resuming | bool | Whether resuming is enabled for this session or not | -| timeout | int | The timeout in seconds (default is 60s) | -
Example Payload -```json -{ - "resuming": true, - "timeout": 60 -} +```yaml +[ + { + "guildId": "...", + "track": { + "encoded": "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==", + "info": { + "identifier": "dQw4w9WgXcQ", + "isSeekable": true, + "author": "RickAstleyVEVO", + "length": 212000, + "isStream": false, + "position": 60000, + "title": "Rick Astley - Never Gonna Give You Up", + "uri": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", + "artworkUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", + "isrc": null, + "sourceName": "youtube" + }, + "pluginInfo": {} + }, + "volume": 100, + "paused": false, + "state": { + "time": 1500467109, + "position": 60000, + "connected": true, + "ping": 50 + }, + "voice": { + "token": "...", + "endpoint": "...", + "sessionId": "..." + }, + "filters": { ... } + }, + ... +] ```
--- -#### Track Loading +### Get Player -This endpoint is used to resolve audio tracks for use with the [Update Player](#update-player) endpoint. +Returns the player for this guild in this session. ``` -GET /v4/loadtracks?identifier=dQw4w9WgXcQ +GET /v4/sessions/{sessionId}/players/{guildId} ``` Response: -##### Track Loading Result - -| Field | Type | Description | -|----------|-------------------------------------|------------------------| -| loadType | [LoadResultType](#load-result-type) | The type of the result | -| data | [LoadResultData](#load-result-data) | The data of the result | - -##### Load Result Type - -| Load Result Type | Description | -|------------------|-----------------------------------------------| -| `track` | A track has been loaded | -| `playlist` | A playlist has been loaded | -| `search` | A search result has been loaded | -| `empty` | There has been no matches for your identifier | -| `error` | Loading has failed with an error | - -##### Load Result Data - -###### Load Result Data - Track - -[Track](#track) object with the loaded track. - -
-Example Payload - -```yaml -{ - "loadType": "track", - "data": { - "encoded": "...", - "info": { ... }, - "pluginInfo": { ... } - } -} -``` - -
- -###### Load Result Data - Playlist - -| Field | Type | Description | -|------------|---------------------------------------|---------------------------------------------| -| info | [PlaylistInfo](#playlist-info) object | The info of the playlist | -| pluginInfo | Object | Addition playlist info provided by plugins | -| tracks | array of [Track](#track) objects | The tracks of the playlist | - -###### Playlist Info - -| Field | Type | Description | -|---------------|--------|-----------------------------------------------------------------| -| name | string | The name of the playlist | -| selectedTrack | int | The selected track of the playlist (-1 if no track is selected) | +[Player](#Player) object
Example Payload ```yaml { - "loadType": "playlist", - "data": { - "info": { ... }, - "pluginInfo": { ... }, - "tracks": [ ... ] - } + "guildId": "...", + "track": { + "encoded": "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==", + "info": { + "identifier": "dQw4w9WgXcQ", + "isSeekable": true, + "author": "RickAstleyVEVO", + "length": 212000, + "isStream": false, + "position": 60000, + "title": "Rick Astley - Never Gonna Give You Up", + "uri": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", + "artworkUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", + "isrc": null, + "sourceName": "youtube" + } + }, + "volume": 100, + "paused": false, + "state": { + "time": 1500467109, + "position": 60000, + "connected": true, + "ping": 50 + }, + "voice": { + "token": "...", + "endpoint": "...", + "sessionId": "..." + }, + "filters": { ... } } ```
- -###### Load Result Data - Search - -Array of [Track](#track) objects from the search result. - -
-Example Payload - -```yaml -{ - "loadType": "search", - "data": [ - { - "encoded": "...", - "info": { ... }, - "pluginInfo": { ... } - }, - ... - ] -} + +--- + +### Update Player + +Updates or creates the player for this guild if it doesn't already exist. + +``` +PATCH /v4/sessions/{sessionId}/players/{guildId}?noReplace=true ``` -
+Query Params: + +| Field | Type | Description | +|------------|------|------------------------------------------------------------------------------| +| noReplace? | bool | Whether to replace the current track with the new track. Defaults to `false` | -###### Load Result Data - Empty +Request: -Empty object. +| Field | Type | Description | +|-----------------|------------------------------------|-----------------------------------------------------------------------------------------------| +| encodedTrack? * | ?string | The base64 encoded track to play. `null` stops the current track | +| identifier? * | string | The identifier of the track to play | +| position? | int | The track position in milliseconds | +| endTime? | ?int | The track end time in milliseconds (must be > 0). `null` resets this if it was set previously | +| volume? | int | The player volume, in percentage, from 0 to 1000 | +| paused? | bool | Whether the player is paused | +| filters? | [Filters](#filters) object | The new filters to apply. This will override all previously applied filters | +| voice? | [Voice State](#voice-state) object | Information required for connecting to Discord | + +> **Note** +> - \* `encodedTrack` and `identifier` are mutually exclusive. +> - `sessionId` in the path should be the value from the [ready op](websocket.md#ready-op). + +When `identifier` is used, Lavalink will try to resolve the identifier as a single track. An HTTP `400` error is returned when resolving a playlist, search result, or no tracks.
Example Payload ```yaml { - "loadType": "empty", - "data": {} + "encodedTrack": "...", + "identifier": "...", + "endTime": 0, + "volume": 100, + "position": 32400, + "paused": false, + "filters": { ... }, + "voice": { + "token": "...", + "endpoint": "...", + "sessionId": "..." + } } ```
-###### Load Result Data - Error +Response: -[Exception](#exception-object) object with the error. +[Player](#Player) object
Example Payload ```yaml { - "loadType": "error", - "data": { - "message": "Something went wrong", - "severity": "fault", - "cause": "..." - } + "guildId": "...", + "track": { + "encoded": "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==", + "info": { + "identifier": "dQw4w9WgXcQ", + "isSeekable": true, + "author": "RickAstleyVEVO", + "length": 212000, + "isStream": false, + "position": 60000, + "title": "Rick Astley - Never Gonna Give You Up", + "uri": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", + "artworkUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", + "isrc": null, + "sourceName": "youtube" + } + }, + "volume": 100, + "paused": false, + "state": { + "time": 1500467109, + "position": 60000, + "connected": true, + "ping": 50 + }, + "voice": { + "token": "...", + "endpoint": "...", + "sessionId": "..." + }, + "filters": { ... } } ``` @@ -1280,110 +770,71 @@ Empty object. --- -#### Track Searching - -Lavalink supports searching via YouTube, YouTube Music, and Soundcloud. To search, you must prefix your identifier with `ytsearch:`, `ytmsearch:` or `scsearch:` respectively. - -When a search prefix is used, the returned `loadType` will be `search`. Note that disabling the respective source managers renders these search prefixes useless. Plugins may also implement prefixes to allow for more search engines to be utilised. - ---- - -#### Track Decoding +### Destroy Player -Decode a single track into its info, where `BASE64` is the encoded base64 data. +Destroys the player for this guild in this session. ``` -GET /v4/decodetrack?encodedTrack=BASE64 +DELETE /v4/sessions/{sessionId}/players/{guildId} ``` Response: -[Track](#track) object - -
-Example Payload +204 - No Content -```yaml -{ - "encoded": "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==", - "info": { - "identifier": "dQw4w9WgXcQ", - "isSeekable": true, - "author": "RickAstleyVEVO", - "length": 212000, - "isStream": false, - "position": 0, - "title": "Rick Astley - Never Gonna Give You Up", - "uri": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", - "artworkUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", - "isrc": null, - "sourceName": "youtube" - }, - "pluginInfo": {} -} -``` +--- -
+## Session API ---- +### Update Session -Decodes multiple tracks into their info +Updates the session with the resuming state and timeout. ``` -POST /v4/decodetracks +PATCH /v4/sessions/{sessionId} ``` Request: -Array of track data strings +| Field | Type | Description | +|-----------|------|-----------------------------------------------------| +| resuming? | bool | Whether resuming is enabled for this session or not | +| timeout? | int | The timeout in seconds (default is 60s) |
Example Payload -```yaml -[ - "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==", - ... -] +```json +{ + "resuming": false, + "timeout": 0 +} ```
Response: -Array of [Track](#track) objects +| Field | Type | Description | +|----------|------|-----------------------------------------------------| +| resuming | bool | Whether resuming is enabled for this session or not | +| timeout | int | The timeout in seconds (default is 60s) |
Example Payload -```yaml -[ - { - "encoded": "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==", - "info": { - "identifier": "dQw4w9WgXcQ", - "isSeekable": true, - "author": "RickAstleyVEVO", - "length": 212000, - "isStream": false, - "position": 0, - "title": "Rick Astley - Never Gonna Give You Up", - "uri": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", - "artworkUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", - "isrc": null, - "sourceName": "youtube" - }, - "pluginInfo": {} - }, - ... -] +```json +{ + "resuming": true, + "timeout": 60 +} ```
--- -#### Get Lavalink info +## Get Lavalink info Request Lavalink information. @@ -1393,7 +844,7 @@ GET /v4/info Response: -##### Info Response +### Info Response | Field | Type | Description | |----------------|-------------------------------------------|-----------------------------------------------------------------| @@ -1406,9 +857,9 @@ Response: | filters | array of strings | The enabled filters for this server | | plugins | array of [Plugin](#plugin-object) objects | The enabled plugins for this server | -##### Version Object +#### Version Object -Parsed Semantic Versioning 2.0.0. See https://semver.org/ for more info +Parsed [Semantic Versioning 2.0.0](https://semver.org/) | Field | Type | Description | |------------|---------|------------------------------------------------------------------------------------| @@ -1419,7 +870,7 @@ Parsed Semantic Versioning 2.0.0. See https://semver.org/ for more info | preRelease | ?string | The pre-release version according to semver as a `.` separated list of identifiers | | build | ?string | The build metadata according to semver as a `.` separated list of identifiers | -##### Git Object +#### Git Object | Field | Type | Description | |------------|--------|----------------------------------------------------------------| @@ -1427,7 +878,7 @@ Parsed Semantic Versioning 2.0.0. See https://semver.org/ for more info | commit | string | The commit this Lavalink server was built on | | commitTime | int | The millisecond unix timestamp for when the commit was created | -##### Plugin Object +#### Plugin Object | Field | Type | Description | |---------|--------|---------------------------| @@ -1482,7 +933,23 @@ Parsed Semantic Versioning 2.0.0. See https://semver.org/ for more info --- -#### Get Lavalink stats +## Get Lavalink version + +Request Lavalink version. + +``` +GET /version +``` + +Response: + +``` +4.0.0 +``` + +--- + +## Get Lavalink stats Request Lavalink statistics. @@ -1493,7 +960,7 @@ GET /v4/stats Response: `frameStats` is always missing for this endpoint. -[Stats](#stats-object) object +[Stats](websocket.md#stats-object) object
Example Payload @@ -1521,40 +988,14 @@ Response: --- -#### Get Lavalink version - -Request Lavalink version. - -``` -GET /version -``` - -Response: - -``` -4.0.0 -``` - ---- - -### RoutePlanner API +## RoutePlanner API Additionally, there are a few REST endpoints for the ip rotation extension. -#### Get RoutePlanner status -``` -GET /v4/routeplanner/status -``` - -Response: - -| Field | Type | Description | -|---------|---------------------------------------------|-----------------------------------------------------------------------| -| class | ?[Route Planner Type](#route-planner-types) | The name of the RoutePlanner implementation being used by this server | -| details | ?[Details](#details-object) object | The status details of the RoutePlanner | +### Common Types ### {: #route-planner-api-types } -##### Route Planner Types +#### Route Planner Types | Route Planner Type | Description | |------------------------------|-----------------------------------------------------------------------------------------------------------------------------| @@ -1563,7 +1004,7 @@ Response: | `RotatingNanoIpRoutePlanner` | IP address used is switched on clock update, rotates to a different /64 block on ban. Use with at least 2x /64 IPv6 blocks. | | `BalancingIpRoutePlanner` | IP address used is selected at random per request. Recommended for larger IP blocks. | -##### Details Object +#### Details Object | Field | Type | Description | Valid Types | |---------------------|-------------------------------------------------------|---------------------------------------------------------------------------------------|----------------------------------------------------| @@ -1575,21 +1016,21 @@ Response: | currentAddressIndex | string | The current offset in the ip block | `NanoIpRoutePlanner`, `RotatingNanoIpRoutePlanner` | | blockIndex | string | The information in which /64 block ips are chosen. This number increases on each ban. | `RotatingNanoIpRoutePlanner` | -##### IP Block Object +#### IP Block Object | Field | Type | Description | |-------|---------------------------------|--------------------------| | type | [IP Block Type](#ip-block-type) | The type of the ip block | | size | string | The size of the ip block | -##### IP Block Type +#### IP Block Type | IP Block Type | Description | |----------------|---------------------| | `Inet4Address` | The ipv4 block type | | `Inet6Address` | The ipv6 block type | -##### Failing Address Object +#### Failing Address Object | Field | Type | Description | |------------------|--------|----------------------------------------------------------| @@ -1597,6 +1038,21 @@ Response: | failingTimestamp | int | The timestamp when the address failed | | failingTime | string | The timestamp when the address failed as a pretty string | +--- + +### Get RoutePlanner status + +``` +GET /v4/routeplanner/status +``` + +Response: + +| Field | Type | Description | +|---------|---------------------------------------------|-----------------------------------------------------------------------| +| class | ?[Route Planner Type](#route-planner-types) | The name of the RoutePlanner implementation being used by this server | +| details | ?[Details](#details-object) object | The status details of the RoutePlanner | +
Example Payload @@ -1625,7 +1081,7 @@ Response: --- -#### Unmark a failed address +### Unmark a failed address ``` POST /v4/routeplanner/free/address @@ -1654,7 +1110,7 @@ Response: --- -#### Unmark all failed address +### Unmark all failed address ``` POST /v4/routeplanner/free/all @@ -1663,63 +1119,3 @@ POST /v4/routeplanner/free/all Response: 204 - No Content - ---- - -### Resuming Lavalink Sessions - -What happens after your client disconnects is dependent on whether the session has been configured for resuming. - -* If resuming is disabled all voice connections are closed immediately. -* If resuming is enabled all music will continue playing. You will then be able to resume your session, allowing you to control the players again. - -To enable resuming, you must call the [Update Session](#update-session) endpoint with the `resuming` and `timeout`. - -To resume a session, specify the session id in your WebSocket handshake request headers: - -``` -Session-Id: The id of the session you want to resume. -``` - -You can tell if your session was resumed by looking at the handshake response header `Session-Resumed` which is either `true` or `false`. - -``` -Session-Resumed: true -``` - -In case your websocket library doesn't support reading headers you can listen for the [ready op](#ready-op) and check the `resumed` field. - -When a session is paused, any events that would normally have been sent are queued up. When the session is resumed, this -queue is then emptied and the events are replayed. - ---- - -### Special notes - -* When your shard's main WS connection dies, so does all your Lavalink audio connections - * This also includes resumes - -* If Lavalink-Server suddenly dies (think SIGKILL) the client will have to terminate any audio connections by sending this event to Discord: - -```json -{ - "op": 4, - "d": { - "self_deaf": false, - "guild_id": "GUILD_ID_HERE", - "channel_id": null, - "self_mute": false - } -} -``` - ---- - -# Common pitfalls - -Admittedly Lavalink isn't inherently the most intuitive thing ever, and people tend to run into the same mistakes over again. Please double-check the following if you run into problems developing your client, and you can't connect to a voice channel or play audio: - -1. Check that you are intercepting **VOICE_SERVER_UPDATE**s and **VOICE_STATE_UPDATE**s to **Lavalink**. You only need the `endpoint`, `token`, and `session_id`. -2. Check that you aren't expecting to hear audio when you have forgotten to queue something up OR forgotten to join a voice channel. -3. Check that you are not trying to create a voice connection with your Discord library. -4. When in doubt, check the debug logfile at `/logs/debug.log`. diff --git a/docs/api/websocket.md b/docs/api/websocket.md new file mode 100644 index 000000000..487c5aa36 --- /dev/null +++ b/docs/api/websocket.md @@ -0,0 +1,454 @@ +--- +description: Lavalink WebSocket API documentation. +--- + +# WebSocket API + +## Opening a connection + +You can establish a WebSocket connection against the path `/v4/websocket`. + +When opening a websocket connection, you must supply 3 required headers: + +| Header Name | Description | +|-----------------|-------------------------------------------------| +| `Authorization` | The password you set in your Lavalink config | +| `User-Id` | The user id of the bot | +| `Client-Name` | The name of the client in `NAME/VERSION` format | +| `Session-Id`? * | The id of the previous session to resume | + +**\*For more information on resuming see [Resuming](index.md#resuming)** + +
+Example Headers + +``` +Authorization: youshallnotpass +User-Id: 170939974227541168 +Client-Name: lavalink-client/2.0.0 +``` + +
+ +Websocket messages all follow the following standard format: + +| Field | Type | Description | +|-------|----------------------|---------------------------------------| +| op | [OP Type](#op-types) | The op type | +| ... | ... | Extra fields depending on the op type | + +
+Example Payload + +```yaml +{ + "op": "...", + ... +} +``` + +
+ +## OP Types + +| OP Type | Description | +|-----------------------------------|---------------------------------------------------------------| +| [ready](#ready-op) | Dispatched when you successfully connect to the Lavalink node | +| [playerUpdate](#player-update-op) | Dispatched every x seconds with the latest player state | +| [stats](#stats-op) | Dispatched when the node sends stats once per minute | +| [event](#event-op) | Dispatched when player or voice events occur | + +### Ready OP + +Dispatched by Lavalink upon successful connection and authorization. Contains fields determining if resuming was successful, as well as the session id. + +| Field | Type | Description | +|-----------|--------|------------------------------------------------------------------------------------------------| +| resumed | bool | Whether this session was resumed | +| sessionId | string | The Lavalink session id of this connection. Not to be confused with a Discord voice session id | + +
+Example Payload + +```json +{ + "op": "ready", + "resumed": false, + "sessionId": "..." +} +``` + +
+ +--- + +### Player Update OP + +Dispatched every x seconds (configurable in `application.yml`) with the current state of the player. + +| Field | Type | Description | +|---------|--------------------------------------|----------------------------| +| guildId | string | The guild id of the player | +| state | [Player State](#player-state) object | The player state | + +### Player State + +| Field | Type | Description | +|-----------|------|------------------------------------------------------------------------------------------| +| time | int | Unix timestamp in milliseconds | +| position | int | The position of the track in milliseconds | +| connected | bool | Whether Lavalink is connected to the voice gateway | +| ping | int | The ping of the node to the Discord voice server in milliseconds (`-1` if not connected) | + +
+Example Payload + +```json +{ + "op": "playerUpdate", + "guildId": "...", + "state": { + "time": 1500467109, + "position": 60000, + "connected": true, + "ping": 50 + } +} +``` + +
+ +--- + +### Stats OP + +A collection of statistics sent every minute. + +#### Stats Object + +| Field | Type | Description | +|----------------|-------------------------------------|--------------------------------------------------------------------------------------------------| +| players | int | The amount of players connected to the node | +| playingPlayers | int | The amount of players playing a track | +| uptime | int | The uptime of the node in milliseconds | +| memory | [Memory](#memory) object | The memory stats of the node | +| cpu | [CPU](#cpu) object | The cpu stats of the node | +| frameStats | ?[Frame Stats](#frame-stats) object | The frame stats of the node. `null` if the node has no players or when retrieved via `/v4/stats` | + +##### Memory + +| Field | Type | Description | +|------------|------|------------------------------------------| +| free | int | The amount of free memory in bytes | +| used | int | The amount of used memory in bytes | +| allocated | int | The amount of allocated memory in bytes | +| reservable | int | The amount of reservable memory in bytes | + +##### CPU + +| Field | Type | Description | +|--------------|-------|----------------------------------| +| cores | int | The amount of cores the node has | +| systemLoad | float | The system load of the node | +| lavalinkLoad | float | The load of Lavalink on the node | + +##### Frame Stats + +| Field | Type | Description | +|-----------|------|----------------------------------------------------------------------| +| sent | int | The amount of frames sent to Discord | +| nulled | int | The amount of frames that were nulled | +| deficit * | int | The difference between sent frames and the expected amount of frames | + +\* The expected amount of frames is 3000 (1 every 20 ms) per player. If the `deficit` is negative, too many frames were sent, and if it's positive, not enough frames got sent. + +
+Example Payload + +```json +{ + "op": "stats", + "players": 1, + "playingPlayers": 1, + "uptime": 123456789, + "memory": { + "free": 123456789, + "used": 123456789, + "allocated": 123456789, + "reservable": 123456789 + }, + "cpu": { + "cores": 4, + "systemLoad": 0.5, + "lavalinkLoad": 0.5 + }, + "frameStats": { + "sent": 6000, + "nulled": 10, + "deficit": -3010 + } +} +``` + +
+ +--- + +### Event OP + +Server dispatched an event. See the [Event Types](#event-types) section for more information. + +| Field | Type | Description | +|---------|---------------------------|-------------------------------------| +| type | [EventType](#event-types) | The type of event | +| guildId | string | The guild id | +| ... | ... | Extra fields depending on the event | + +
+Example Payload + +```yaml +{ + "op": "event", + "type": "...", + "guildId": "...", + ... +} +``` + +
+ +##### Event Types + +| Event Type | Description | +|-----------------------------------------------|-----------------------------------------------------------------------------| +| [TrackStartEvent](#trackstartevent) | Dispatched when a track starts playing | +| [TrackEndEvent](#trackendevent) | Dispatched when a track ends | +| [TrackExceptionEvent](#trackexceptionevent) | Dispatched when a track throws an exception | +| [TrackStuckEvent](#trackstuckevent) | Dispatched when a track gets stuck while playing | +| [WebSocketClosedEvent](#websocketclosedevent) | Dispatched when the websocket connection to Discord voice servers is closed | + +#### TrackStartEvent + +Dispatched when a track starts playing. + +| Field | Type | Description | +|-------|-------------------------------|--------------------------------| +| track | [Track](rest.md#track) object | The track that started playing | + +
+Example Payload + +```json +{ + "op": "event", + "type": "TrackStartEvent", + "guildId": "...", + "track": { + "encoded": "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==", + "info": { + "identifier": "dQw4w9WgXcQ", + "isSeekable": true, + "author": "RickAstleyVEVO", + "length": 212000, + "isStream": false, + "position": 0, + "title": "Rick Astley - Never Gonna Give You Up", + "uri": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", + "artworkUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", + "isrc": null, + "sourceName": "youtube" + }, + "pluginInfo": {} + } +} +``` + +
+ +--- + +#### TrackEndEvent + +Dispatched when a track ends. + +| Field | Type | Description | +|--------|-------------------------------------|------------------------------| +| track | [Track](rest.md#track) object | The track that ended playing | +| reason | [TrackEndReason](#track-end-reason) | The reason the track ended | + +##### Track End Reason + +| Reason | Description | May Start Next | +|--------------|----------------------------|----------------| +| `finished` | The track finished playing | true | +| `loadFailed` | The track failed to load | true | +| `stopped` | The track was stopped | false | +| `replaced` | The track was replaced | false | +| `cleanup` | The track was cleaned up | false | + +
+Example Payload + +```json +{ + "op": "event", + "type": "TrackEndEvent", + "guildId": "...", + "track": { + "encoded": "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==", + "info": { + "identifier": "dQw4w9WgXcQ", + "isSeekable": true, + "author": "RickAstleyVEVO", + "length": 212000, + "isStream": false, + "position": 0, + "title": "Rick Astley - Never Gonna Give You Up", + "uri": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", + "artworkUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", + "isrc": null, + "sourceName": "youtube" + }, + "pluginInfo": {} + }, + "reason": "finished" +} +``` + +
+ +--- + +#### TrackExceptionEvent + +Dispatched when a track throws an exception. + +| Field | Type | Description | +|-----------|---------------------------------------|------------------------------------| +| track | [Track](rest.md#track) object | The track that threw the exception | +| exception | [Exception](#exception-object) object | The occurred exception | + +##### Exception Object + +| Field | Type | Description | +|----------|-----------------------|-------------------------------| +| message | ?string | The message of the exception | +| severity | [Severity](#severity) | The severity of the exception | +| cause | string | The cause of the exception | + +##### Severity + +| Severity | Description | +|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common` | The cause is known and expected, indicates that there is nothing wrong with the library itself | +| `suspicious` | The cause might not be exactly known, but is possibly caused by outside factors. For example when an outside service responds in a format that we do not expect | +| `fault` | The probable cause is an issue with the library or there is no way to tell what the cause might be. This is the default level and other levels are used in cases where the thrower has more in-depth knowledge about the error | + +
+Example Payload + +```json +{ + "op": "event", + "type": "TrackExceptionEvent", + "guildId": "...", + "track": { + "encoded": "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==", + "info": { + "identifier": "dQw4w9WgXcQ", + "isSeekable": true, + "author": "RickAstleyVEVO", + "length": 212000, + "isStream": false, + "position": 0, + "title": "Rick Astley - Never Gonna Give You Up", + "uri": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", + "artworkUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", + "isrc": null, + "sourceName": "youtube" + }, + "pluginInfo": {} + }, + "exception": { + "message": "...", + "severity": "common", + "cause": "..." + } +} +``` + +
+ +--- + +#### TrackStuckEvent + +Dispatched when a track gets stuck while playing. + +| Field | Type | Description | +|-------------|-------------------------------|-------------------------------------------------| +| track | [Track](rest.md#track) object | The track that got stuck | +| thresholdMs | int | The threshold in milliseconds that was exceeded | + +
+Example Payload + +```json +{ + "op": "event", + "type": "TrackStuckEvent", + "guildId": "...", + "track": { + "encoded": "QAAAjQIAJVJpY2sgQXN0bGV5IC0gTmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXAADlJpY2tBc3RsZXlWRVZPAAAAAAADPCAAC2RRdzR3OVdnWGNRAAEAK2h0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9ZFF3NHc5V2dYY1EAB3lvdXR1YmUAAAAAAAAAAA==", + "info": { + "identifier": "dQw4w9WgXcQ", + "isSeekable": true, + "author": "RickAstleyVEVO", + "length": 212000, + "isStream": false, + "position": 0, + "title": "Rick Astley - Never Gonna Give You Up", + "uri": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", + "artworkUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", + "isrc": null, + "sourceName": "youtube" + }, + "pluginInfo": {} + }, + "thresholdMs": 123456789 +} +``` + +
+ +--- + +#### WebSocketClosedEvent + +Dispatched when an audio WebSocket (to Discord) is closed. +This can happen for various reasons (normal and abnormal), e.g. when using an expired voice server update. +4xxx codes are usually bad. +See the [Discord Docs](https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice-voice-close-event-codes). + +| Field | Type | Description | +|----------|--------|-----------------------------------------------------------------------------------------------------------------------------------| +| code | int | The [Discord close event code](https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice-voice-close-event-codes) | +| reason | string | The close reason | +| byRemote | bool | Whether the connection was closed by Discord | + +
+Example Payload + +```json +{ + "op": "event", + "type": "WebSocketClosedEvent", + "guildId": "...", + "code": 4006, + "reason": "Your session is no longer valid.", + "byRemote": true +} +``` + +
\ No newline at end of file diff --git a/docs/assets/favicon.png b/docs/assets/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..a35922acaaada5c48429c06bd2a84b080fccd3aa GIT binary patch literal 2445 zcmV;833B#{P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H12^vX6 zK~#90?Va0i99JF3Kfl?v)R3s6jUC$9RiVnwxinI4G$`T)LY0RqL28r=0k8ZCJU|~1 zeMOX(x4uC`MO7#vh$kW>@?<-Xv%V#DQlKhvZjF&hOHyCv$HVM;XFWT!bI#6f%^b;c ze(OEIbNTG=_dVy#%tlm|=pwT8^S*;xkyGNzF;t&IWeC`a3Mk$W-oK^s8jZjCeiLQ4 zsT13dUq?Fmi0os`et300Q{`Wx&TWN@s&cOHchlo^m~aXIyU5}jl{2EwPl0W5CbEPP z?L$?ri|}gY_tWo&foTim^EV#b?y&L>DhK0EWCsWVRQgI7DZ*g&_mZlfsGhG~2mpEX%MD+t zxXYuc^p`L)fT1AXYYUFU3!I<04&cx%aN5A51g(85fRt)M`yoQL#;_H2ffzc91elQxggC25J#~~$*ykVe%s2+1r9V}tw4+B6|wmPUhRl>+0hG)y518gc`6afaHY;sB%MS2I}q9OBe-#;Q=XO6b1$eP6?wpFjga^gi#C_s5CXCgpogtW`vY5 z3IPLzYBn(9`zSIDNeF0EAFJ6!h8Ya!dp|>tp2oDeZ~YLx_jFKMyIw@HC`=X-ak+HJiYC(W|3@He~p#=6)te0`rh&n)2IDw+<55j3!OBY}R%9`kg?($z?dkE~(T zHKZ6Y*!&FiTk6N%`M@A$k72q3sEv^)3=c>VV5oE#9cu?E7W%QggwE7*g#mt1&k+Vn z7y1$WRLkHt_~K?WN7ts{;lo^EG$SNu7+nea%vjLp9>d)^pjXCnhvA8O0btNS)P;WB z3`W~tOCW3e6=?^@-7O3Q4TR zM5779VDk|451LP6WB;)>+HCaWavVVqjyq`>5gHQFXp%5gkX@FmtLZkMYH^U=2h1FO z`T%asb?9es+-blF(U6En6NcfXE8FGnrGQWQ@-S?A+{{mH+&fe(0E}jY>ql!F^4u_dVWXL!%VXd+ zjC5gy&s{qW$pMBJA(r|v6ch+IWPnRmilq$R!bYA z!1LxnJpIT&P5I}tnhOjB*=2cLGS#}e62;Sx;r+(GoU`x8fAj4-xTjt=f)O`O`Cp4X zTuk%EXX6{#>P2V9TVbfcj=iwKOh1gxGg-RQ@B@V+UV4WVJtE0eKc*rchDjbTPGRP3W;>QWF!r4Jc?-pXI> z^56k{_))XXgws^0hGg}yj0{_*n5p3T6+8FAV;k)J#VmSdYR!2UB$=lCb~S4ls2~>C zya9A_JkYM}Pd{BTIUNb3+cf33ovdNdKWL{PcRc+F^kaYeg02%&k&%%U3{PbV$qa^C+;3Yi?N(Pd6Is+W<+rO@!9eZPl^lNwIsTGW-LA^L`?$wX z8%}JT_k`Oa4}B&N0~KU%24_wQ7M-129cP+pYK?|u1A{GFy`NqxvvXt#!vg}*6PW}I zu-={LT4v@lg@HzXaX@k~P(k)&aKw>xxq3ZQ7!dl!0m;HZ={f!2-hH^ckSUC2gaB#6 zU|_KK^kZf&-aI4-qrHYC4MWB5j*y&-&R!>L7(oq50S3^U`Y}D1ISdFrTucsz7a=_e zWHv(3Q-XmCvi$vRnkEdCT)#Ucj*qODwo!eI_XJf5BNG^C9F{5~91K3#7xq~I_l1P1UQN0nRa z2}Uobsnt5J%sZma{Ca{>%55TaLoT^#G%X)0Dr?-yBsr*XuBPI!*cOIPN)7 zkmYah{Xk%QufiyDWOPhZO?!?<@9c6=RS{k-VdM$}T<1rqs;DZ6$jYgGBe-(7gpmu3 z5f;xLMOEt?L)FTPoB=*5VPp&gSwQ5ws46se>HvSN{X<<|0hUS__Ap|urW*DV@G_5n zeWQV)2?VH~uU)9P`VCa>l`za<82Rub;{*@S{?*T*1qc9ts$FzeoUbV|S|k|jWS`;L zt}}wJ9O2>Hf&XAp>ZvPOL}cZ~1E&RELuGpz8PPCe`k2F&AG7$I_XwHza3DS~eCxmg zS1Tt)^_aTyH1G^68%r3T9TFLz;mXG#w@@zPxaauqZzsY)G|K+~aBT~%7)2_)00000 LNkvXXu0mjfwu6Lj literal 0 HcmV?d00001 diff --git a/docs/assets/images/lavalink.svg b/docs/assets/images/lavalink.svg new file mode 100644 index 000000000..0ade923fd --- /dev/null +++ b/docs/assets/images/lavalink.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + diff --git a/docs/assets/logo.svg b/docs/assets/logo.svg new file mode 100644 index 000000000..5c3e28f60 --- /dev/null +++ b/docs/assets/logo.svg @@ -0,0 +1,18 @@ + + + + + diff --git a/docs/changelog/index.md b/docs/changelog/index.md new file mode 100644 index 000000000..e86584f59 --- /dev/null +++ b/docs/changelog/index.md @@ -0,0 +1,119 @@ +--- +description: Lavalink changelog. +--- + +# Changelog + +Each release usually includes various fixes and improvements. +The most noteworthy of these, as well as any features and breaking changes, are listed here. + +## Significant changes + +
+v3.7.0 -> v4.0.0 + +* removed all non version `/v3` or `/v4` endpoints (except `/version`). +* `/v4/websocket` does not accept any messages anymore. +* `v4` uses the `sessionId` instead of the `resumeKey` for resuming. +* `v4` now returns the tracks `artworkUrl` and `isrc` if the source supports it. +* removal of deprecated json fields like `track`. +* addition of `artworkUrl` and `isrc` fields to the [Track Info](#track-info) object. +* addition of the full [Track](#track) object in [TrackStartEvent](#trackstartevent), [TrackEndEvent](#trackendevent), [TrackExceptionEvent](#trackexceptionevent) and [TrackStuckEvent](#trackstuckevent). +* updated capitalization of [Track End Reason](#track-end-reason) and [Severity](#severity) +* reworked [Load Result](#track-loading-result) object + +All websocket ops are removed as of `v4.0.0` and replaced with the following endpoints and json fields: + +* `play` -> [Update Player Endpoint](#update-player) `encodedTrack` or `identifier` field +* `stop` -> [Update Player Endpoint](#update-player) `encodedTrack` field with `null` +* `pause` -> [Update Player Endpoint](#update-player) `pause` field +* `seek` -> [Update Player Endpoint](#update-player) `position` field +* `volume` -> [Update Player Endpoint](#update-player) `volume` field +* `filters` -> [Update Player Endpoint](#update-player) `filters` field +* `destroy` -> [Destroy Player Endpoint](#destroy-player) +* `voiceUpdate` -> [Update Player Endpoint](#update-player) `voice` field +* `configureResuming` -> [Update Session Endpoint](#update-session) + +
+ +
+v3.6.0 -> v3.7.0 + +* Moved HTTP endpoints under the new `/v3` path with `/version` as the only exception. +* Deprecation of the old HTTP paths. +* WebSocket handshakes should be done with `/v3/websocket`. Handshakes on `/` are now deprecated. +* Deprecation of all client-to-server messages (play, stop, pause, seek, volume, filters, destroy, voiceUpdate & configureResuming). +* Addition of REST endpoints intended to replace client requests. +* Addition of new WebSocket dispatch [Ready OP](#ready-op) to get `sessionId` and `resume` status. +* Addition of new [Session](#update-session)/[Player](#get-player) REST API. +* Addition of `/v3/info`, replaces `/plugins`. +* Deprecation of `Track.track` in existing endpoints. Use `Track.encoded` instead. +* Deprecation of `TrackXEvent.track` in WebSocket dispatches. Use `TrackXEvent.encodedTrack` instead. +* Player now has a `state` field which contains the same structure as returned by the `playerUpdate` OP. + +All websocket ops are deprecated as of `v3.7.0` and replaced with the following endpoints and json fields: + +* `play` -> [Update Player Endpoint](#update-player) `track` or `identifier` field +* `stop` -> [Update Player Endpoint](#update-player) `track` field with `null` +* `pause` -> [Update Player Endpoint](#update-player) `pause` field +* `seek` -> [Update Player Endpoint](#update-player) `position` field +* `volume` -> [Update Player Endpoint](#update-player) `volume` field +* `filters` -> [Update Player Endpoint](#update-player) `filters` field +* `destroy` -> [Destroy Player Endpoint](#destroy-player) +* `voiceUpdate` -> [Update Player Endpoint](#update-player) `voice` field +* `configureResuming` -> [Update Session Endpoint](#update-session) + +
+ +
+v3.3 -> v3.4 + +* Added filters +* The `error` string on the `TrackExceptionEvent` has been deprecated and replaced by + the [Exception](#exception-object) object following the same structure as the `LOAD_FAILED` error on [`/loadtracks`](#track-loading). +* Added the `connected` boolean to player updates. +* Added source name to REST api track objects +* Clients are now requested to make their name known during handshake + +
+ +
+v2.0 -> v3.0 + +* The response of `/loadtracks` has been completely changed (again since the initial v3.0 pre-release). +* Lavalink v3.0 now reports its version as a handshake response header. + `Lavalink-Major-Version` has a value of `3` for v3.0 only. It's missing for any older version. + +
+ + +
+v1.3 -> v2.0 + +With the release of v2.0 many unnecessary ops were removed: + +* `connect` +* `disconnect` +* `validationRes` +* `isConnectedRes` +* `validationReq` +* `isConnectedReq` +* `sendWS` + +With Lavalink 1.x the server had the responsibility of handling Discord `VOICE_SERVER_UPDATE`s as well as its own internal ratelimiting. +This remote handling makes things unnecessarily complicated and adds a lot og points where things could go wrong. +One problem we noticed is that since JDA is unaware of ratelimits on the bot's gateway connection, it would keep adding +to the ratelimit queue to the gateway. With this update this is now the responsibility of the Lavalink client or the +Discord client. + +A voice connection is now initiated by forwarding a `voiceUpdate` (VOICE_SERVER_UPDATE) to the server. When you want to +disconnect or move to a different voice channel you must send Discord a new VOICE_STATE_UPDATE. If you want to move your +connection to a new Lavalink server you can simply send the VOICE_SERVER_UPDATE to the new node, and the other node +will be disconnected by Discord. + +Depending on your Discord library, it may be possible to take advantage of the library's OP 4 handling. For instance, +the JDA client takes advantage of JDA's websocket write thread to send OP 4s for connects, disconnects and reconnects. + +
+ + diff --git a/docs/changelog/v2.md b/docs/changelog/v2.md new file mode 100644 index 000000000..7773703ac --- /dev/null +++ b/docs/changelog/v2.md @@ -0,0 +1,36 @@ +## v2.2 + +* Lavaplayer updated to 1.3.x [\#115](https://github.com/lavalink-devs/Lavalink/pull/115) +* Version command line flag [\#121](https://github.com/lavalink-devs/Lavalink/pull/121) +* Fix race condition in `/loadtracks` endpoint leading to some requests never completing [\#125](https://github.com/lavalink-devs/Lavalink/pull/125) + +Contributors: +[@Devoxin](https://github.com/Devoxin), +[@freyacodes](https://github.com/freyacodes/), +[@napstr](https://github.com/napstr) + +## v2.1 + +* Add prometheus metrics [\#105](https://github.com/lavalink-devs/Lavalink/pull/105), [\#106](https://github.com/lavalink-devs/Lavalink/pull/106) + +Contributors: +[@freyacodes](https://github.com/freyacodes/), +[@napstr](https://github.com/napstr), +[@Repulser](https://github.com/Repulser/) + +## v2.0.1 + +* Configurable playlist load limit [\#60](https://github.com/lavalink-devs/Lavalink/pull/60) +* [Docker Releases](https://hub.docker.com/r/fredboat/lavalink/), [\#74](https://github.com/lavalink-devs/Lavalink/pull/74) + +Contributors: +[@Devoxin](https://github.com/Devoxin), +[@freyacodes](https://github.com/freyacodes/), +[@itslukej](https://github.com/itslukej/), +[@napstr](https://github.com/napstr), +[@Repulser](https://github.com/Repulser/) + +## v2.0 + +Please see [here](https://github.com/lavalink-devs/Lavalink/commit/b8dd3c8a7e186755c1ab343d19a552baecf138e7) +and [here](https://github.com/lavalink-devs/Lavalink/commit/08a34c99a47a18ade7bd14e6c55ab92348caaa88) diff --git a/docs/changelog/v3.md b/docs/changelog/v3.md new file mode 100644 index 000000000..1be1b2e34 --- /dev/null +++ b/docs/changelog/v3.md @@ -0,0 +1,315 @@ +## v3.7.9 +* Update lavaplayer to [`1.5.1`](https://github.com/lavalink-devs/lavaplayer/releases/tag/1.5.1) - Fixed YouTube access token errors +* Fixed websocket crash when seeking and nothing is playing +* Fixed error when seeking and player is not playing anything + +## v3.7.8 + +* Fix YouTube 403 errors +* Fix YouTube access token errors + +## v3.7.7 + +* Add JDA-NAS support for musl (`x86-64`, `aarch64`) based systems (most notably `alpine`) + +## v3.7.6 + +* Update Lavaplayer to [`1.4.1`](https://github.com/Walkyst/lavaplayer-fork/releases/tag/1.4.1) & [`1.4.2`](https://github.com/Walkyst/lavaplayer-fork/releases/tag/1.4.2) +* New support for `MUSL` based systems (most notably `alpine`) +* New `alpine` docker image variant (use `-alpine` suffix) + +## v3.7.5 + +* Fix `endTime` in `Player Update` endpoint only applying when playing a new track +* Fix errors when doing multiple session resumes +* Update lavaplayer to `1.4.0` see [here](https://github.com/Walkyst/lavaplayer-fork/releases/tag/1.4.0) for more info + +> **Note** +> Lavalink Docker images are now found in the GitHub Container Registry instead of DockerHub + +## v3.7.4 + +* Fix an issue where Lavalink would not destroy a session when a client disconnects + +## v3.7.3 + +* Fix breaking change where `/decodetrack` would return a full track instead of the track info + +## v3.7.2 + +* Fix breaking change where frameStats would be null instead of omitted + +## v3.7.1 + +* Revert of application.yml autocreate as it can cause issues with differently named configs + +## v3.7.0 + +* New REST API for player control and deprecation of all websocket OPs. For more info see [here](https://github.com/lavalink-devs/Lavalink/blob/master/IMPLEMENTATION.md#significant-changes-v360---v370) +* Autocreate default `application.yml` if none was found. https://github.com/lavalink-devs/Lavalink/pull/781 +* New config option to disable jda nas. https://github.com/lavalink-devs/Lavalink/pull/780 +* New config option to disable specific filters. https://github.com/lavalink-devs/Lavalink/pull/779 +* Update lavaplayer to `1.3.99.2`. https://github.com/lavalink-devs/Lavalink/pull/794 +* Update udpqueue.rs to `v0.2.6`. https://github.com/lavalink-devs/Lavalink/pull/802 + +Contributors: +[@topi314](https://github.com/topi314), [@Devoxin](https://github.com/Devoxin), [@melike2d](https://github.com/melike2d), [@freyacodes](https://github.com/freyacodes), [@aikaterna](https://github.com/aikaterna), [@ooliver1](https://github.com/ooliver1) + +## v3.6.2 + +* Update lavaplayer to `1.3.99.1`. For more info see [here](https://github.com/lavalink-devs/Lavalink/pull/773) + +## v3.6.1 + +* Update lavaplayer to `1.3.99`. For more info see [here](https://github.com/lavalink-devs/Lavalink/pull/768) + +## v3.6.0 + +* New userId & clientName getters in the plugin-api. For more info see [here](https://github.com/lavalink-devs/Lavalink/pull/743). + +Contributors: +[@melike2d](https://github.com/melike2d) + +## v3.5.1 + +* Update udpqueue.rs to `0.2.5` which fixes crashes when ipv6 is disabled +* Fix null socketContext in `IPlayer` for plugins +* New `ping` field in player update. see https://github.com/lavalink-devs/Lavalink/pull/738 for more info + +Contributors: +[@topi314](https://github.com/topi314), [@Devoxin](https://github.com/Devoxin), and [@freyacodes](https://github.com/freyacodes) + +## v3.5 + +* New plugin system. For more info see [here](https://github.com/lavalink-devs/Lavalink/blob/master/PLUGINS.md). +* Add support for HTTP proxying via httpConfig. For more info see [here](https://github.com/lavalink-devs/Lavalink/pull/595). +* Update koe version to 2.0.0-rc1. + - this fixes the WebSocketClosedEvent with code 1006 problem. +* Fix error when enabling timescale and lowpass filters. +* Fix player not playing after moving between voice chats or changing regions. +* Fix guild ids sent as numbers in json. +* Fix missing timescale natives. +* Fix setting endMarkerHit to correctly set FINISHED as the reason. +* Undeprecation of the `volume` property in the `play` OP. +* Configurable track stuck threshold. For more info see [here](https://github.com/lavalink-devs/Lavalink/pull/676). +* Add JDA-NAS support for more CPU Architectures. For more info see [here](https://github.com/lavalink-devs/Lavalink/pull/692). Big thanks goes to @MinnDevelopment here. +* Update lavaplayer to [`1.3.98.4`](https://github.com/Walkyst/lavaplayer-fork/releases/tag/1.3.98.4) which fixes the latest yt cipher issues and age restricted tracks + +Contributors: +[@freyacodes](https://github.com/freyacodes), +[@davidffa](https://github.com/davidffa), +[@Walkyst](https://github.com/Walkyst), +[@topi314](https://github.com/topi314), +[@duncte123](https://github.com/duncte123), +[@Kodehawa](https://github.com/Kodehawa), +[@Devoxin](https://github.com/Devoxin), +[@Muh9049](https://github.com/Muh9049), +[@melike2d](https://github.com/melike2d), +[@ToxicMushroom](https://github.com/ToxicMushroom), +[@mooner1022](https://github.com/mooner1022), +[@rohank05](https://github.com/rohank05), +[@Fabricio20](https://github.com/Fabricio20), +[@TheEssemm](https://github.com/TheEssemm), and +[@jack1142](https://github.com/jack1142) + +## v3.4 + +* New filters system +* Deprecation of `TrackExceptionEvent.error`, replaced by `TrackExceptionEvent.exception` +* Added the `connected` boolean to player updates. +* Updated lavaplayer, fixes Soundcloud +* Added source name to REST api track objects +* Clients are now requested to make their name known during handshake + +Contributors: +[@freyacodes](https://github.com/freyacodes), +[@duncte123](https://github.com/duncte123), +[@DaliborTrampota](https://github.com/DaliborTrampota), +[@Mandruyd](https://github.com/Mandruyd), +[@Allvaa](https://github.com/@Allvaa), and +[@topi314](https://github.com/topi314) + +## v3.3.2.5 + +* Update Lavaplayer to 1.3.76 + +## v3.3.2.4 + +* Update Lavaplayer to 1.3.74 + +## v3.3.2.3 + +* Update Lavaplayer to 1.3.65, fixes Soundcloud + +## v3.3.2.2 + +* Updated Lavaplayer to 1.3.61 +* Fixed a ConcurrentModificationException ([Thewsomeguy](https://github.com/Thewsomeguy)) + +## v3.3.2.1 + +* Updated to Sedmelluq's Lavaplayer 1.3.53 + +## v3.3.2 + +* Replaced Magma with Koe. +* Finally implemented `stopTime` for `play` op. +* Added `playerUpdateInterval` config option. +* Added `environment` to Sentry config. +* Fixed #332 +* Updated IP rotator. +* Update lavaplayer to `1.3.59` from devoxin's fork. +* Added a Testbot for development. + +Contributors: +[@freyacodes](https://github.com/freyacodes), +[@Thewsomeguy](https://github.com/Thewsomeguy), +[@Neuheit](https://github.com/Neuheit), +[@Sangoon_Is_Noob](https://github.com/Sangoon_Is_Noob), +[@TheEssem](https://github.com/Essem), and +[@Devoxin](https://github.com/Devoxin) + +## v3.3.1.4 + +* Update lavaplayer to `1.3.54.3` from devoxin's fork. + +## v3.3.1.3 + +* Update lavaplayer to `1.3.53` from devoxin's fork. + +## v3.3.1.2 + +* Update lavaplayer to [@Devoxin](https://github.com/Devoxin)'s' fork + +## v3.3.1.1 + +* Updated Lavaplayer to `1.3.50`. This notably fixes YouTube search. + +Search patch contributed by [@freyacodes](https://github.com/freyacodes) + +## v3.3.1 + +* Update Magma and Lavaplayer. +* Added TrackStartEvent event. +* Added retryLimit configuration option. +* Use a single AudioPlayerManager for all WS connections, reducing overhead. +* Docker images now use Zulu JDK 13 to mitigate TLS 1.3 problems. + +Contributors: +[@freyacodes](https://github.com/freyacodes), +[@duncte123](https://github.com/duncte123), +[@ByteAlex](https://github.com/ByteAlex), and +[@Xavinlol](https://github.com/Xavinlol) + +## v3.3 + +Officially limit Lavalink to JRE 11 and up. Magma has long been having issues with older versions. + +## v3.2.2 + +* IP rotation system for getting around certain ratelimits. +* Update Lavaplayer to 1.3.32. +* Docker container now uses a non-root user. + +Contributors: +[@freyacodes](https://github.com/freyacodes), +[@ByteAlex](https://github.com/ByteAlex), +[@duncte123](https://github.com/duncte123), and +[@james7132](https://github.com/james7132) + +## v3.2.1.1 + +* Updated Lavaplayer to 1.3.19. This release includes a patch which fixes loading youtube URLs. + https://github.com/sedmelluq/lavaplayer/pull/199 +* Made the WebSocket handshake return code 401 instead of 200 on bad auth. #208 + +Contributors: +[@freyacodes](https://github.com/freyacodes) and +[@Devoxin](https://github.com/Devoxin) + +## v3.2.1 + +* Update dependencies -- fixes frequent youtube HTTP errors +* Return `FriendlyException` message on `LOAD_FAILED` #174 +* Add option to disable `ytsearch` and `scsearch` #194 + +Contributors: +[@Devoxin](https://github.com/Devoxin), +[@duncte123](https://github.com/duncte123), +[@freyacodes](https://github.com/freyacodes), and +[@napstr](https://github.com/napstr) + +## v3.2.0.3 + +* Add compatibility for Java 8-10 + +Contributor: +[@MinnDevelopment](https://github.com/MinnDevelopment/) + +## v3.2.0.2 + +* Patched magma + +Contributor: +[@freyacodes](https://github.com/freyacodes/) + +## v3.2.0.1 + +* Bumped to Java 11. Treating this as a patch version, as v3.2 still requires Java 11 due to a Magma update. + +[@freyacodes](https://github.com/freyacodes) + +## v3.2 + +* Added support for resuming +* Added noReplace option to the play op +* Sending the same voice server update will not cause an existing connection to reconnect + +Contributor: +[@freyacodes](https://github.com/freyacodes) + +## v3.1.2 + +* Add API version header to all responses + +Contributor: +[@Devoxin](https://github.com/Devoxin) + +## v3.1.1 + +* Add equalizer support +* Update lavaplayer to 1.3.10 +* Fixed automatic versioning +* Added build config to upload binaries to GitHub releases from CI + +Contributors: +[@Devoxin](https://github.com/Devoxin), +[@freyacodes](https://github.com/freyacodes/), +[@calebj](https://github.com/calebj) + +## v3.1 + +* Replaced JDAA with Magma +* Added an event for when the Discord voice WebSocket is closed +* Replaced Tomcat and Java_Websocket with Undertow. WS and REST is now handled by the same + server and port. Port is specified by `server.port`. + +## v3.0 + +* **Breaking:** The minimum required Java version to run the server is now Java 10. + **Please note**: Java 10 will be obsolete + as of [September 2018 with the release of Java 11](http://www.java-countdown.xyz/). Expect a Lavalink major version release that will be targetting + Java 11 by that time. +* **Breaking:** Changes to the output of the /loadtracks endpoint. [\#91](https://github.com/lavalink-devs/Lavalink/pull/91), [\#114](https://github.com/lavalink-devs/Lavalink/pull/114), [\#116](https://github.com/lavalink-devs/Lavalink/pull/116) +* **Breaking:** The Java client has been moved to a [new repository](https://github.com/lavalink-devs/Lavalink-Client). +* **Breaking:** The Java client has been made generic. This is a breaking change so please read the [migration guide](https://github.com/lavalink-devs/Lavalink-Client#migrating-from-v2-to-v3). +* Better configurable logging. [\#97](https://github.com/lavalink-devs/Lavalink/pull/97) +* Add custom sentry tags, change sentry dsn configuration location. [\#103](https://github.com/lavalink-devs/Lavalink/pull/103) +* Add Lavalink version header to websocket handshake. [\#111](https://github.com/lavalink-devs/Lavalink/pull/111) +* Use git tags for easier version visibility. [\#129](https://github.com/lavalink-devs/Lavalink/pull/129) + +Contributors: +[@Devoxin](https://github.com/Devoxin), +[@freyacodes](https://github.com/freyacodes/), +[@napstr](https://github.com/napstr), +[@SamOphis](https://github.com/SamOphis) diff --git a/docs/changelog/v4.md b/docs/changelog/v4.md new file mode 100644 index 000000000..b877503d3 --- /dev/null +++ b/docs/changelog/v4.md @@ -0,0 +1,40 @@ +## v4.0.0-beta.5 +* Update lavaplayer to [`2.0.3`](https://github.com/lavalink-devs/lavaplayer/releases/tag/2.0.2) - Fixed YouTube access token errors +* Added default plugin repository. Plugin devs can now request their plugin to be added to the default repository. For more info see [here](../api/plugins.md#distributing-your-plugin) +* Fixed error when seeking and player is not playing anything in + +## v4.0.0-beta.4 + +* Update lavaplayer to [`2.0.2`](https://github.com/lavalink-devs/lavaplayer/releases/tag/2.0.2) - Support MPEG 2.5 and fixed some requests not timing out +* Add `Omissible#isPresent` & `Omissible#isOmitted` to the `protocol` module +* Fix null pointer when a playlist has no selected track + +## v4.0.0-beta.3 + +* Update lavaplayer to [`2.0.0`](https://github.com/lavalink-devs/lavaplayer/releases/tag/2.0.0) - Fixed YouTube 403 errors & YouTube access token errors + +## v4.0.0-beta.2 + +* Update lavaplayer to [`08cfbc0`](https://github.com/Walkyst/lavaplayer-fork/commit/08cfbc05953128f3cf727ea3bcbe41dabcd1c7db) - Fixed ogg streaming +* Add JDA-NAS support for musl (`x86-64`, `aarch64`) based systems (most notably `alpine`) +* New config option to specify the directory to load plugins from. `lavalink.pluginsDir` (defaults to `./plugins`) + +## v4.0.0-beta.1 + +* New Lavalink now requires Java 17 or higher to run +* **Removal of all websocket messages sent by the client. Everything is now done via [REST](../api/rest.md)** +* Update to [Lavaplayer custom branch](https://github.com/Walkyst/lavaplayer-fork/tree/custom), which includes native support for artwork urls and ISRCs in the track info +* Addition of full `Track` objects in following events: `TrackStartEvent`, `TrackEndEvent`, `TrackExceptionEvent`, `TrackStuckEvent` +* Resuming a session now requires the `Session-Id` header instead of `Resume-Key` header +* Reworked track loading result. For more info see [here](../api/rest.md#track-loading-result) +* Update to the [Protocol Module](https://github.com/lavalink-devs/Lavalink/tree/master/protocol) to support Kotlin/JS +* Removal of all `/v3` endpoints except `/version`. All other endpoints are now under `/v4` + +> **Warning** +> This is a beta release, and as such, may contain bugs. Please report any bugs you find to the [issue tracker](https://github.com/lavalink-devs/Lavalink/issues/new/choose). +> For more info on the changes in this release, see [here](../api/index.md#v370---v400) +> If you have any question regarding the changes in this release, please ask in the [support server]({{ discord_help }}) or [GitHub discussions](https://github.com/lavalink-devs/Lavalink/discussions/categories/q-a) + +Contributors: +[@topi314](https://github.com/topi314), [@freyacodes](https://github.com/freyacodes), [@DRSchlaubi](https://github.com/DRSchlaubi) and [@melike2d](https://github.com/melike2d) + diff --git a/docs/clients.md b/docs/clients.md new file mode 100644 index 000000000..075cbdb52 --- /dev/null +++ b/docs/clients.md @@ -0,0 +1,49 @@ +--- +description: A list of Lavalink client libraries. +--- + +# Client Libraries + +| Client | Platform | Compatible With | Additional Information | +|-------------------------------------------------------------------------|-----------------|--------------------------------------------|--------------------------------| +| [Lavalink-Client](https://github.com/lavalink-devs/Lavalink-Client) | Java/Kotlin/JVM | JDA/Discord4J/**Any** | Uses reactor | +| [Lavalink.kt](https://github.com/DRSchlaubi/Lavalink.kt) | Kotlin | Kord/JDA/**Any** | Kotlin Coroutines | +| [DisGoLink](https://github.com/disgoorg/disgolink) | Go | **Any** | | +| [Mafic](https://github.com/ooliver1/mafic) | Python | discord.py **V2**/nextcord/disnake/py-cord | | +| [Wavelink](https://github.com/PythonistaGuild/Wavelink/tree/feature/v3) | Python | discord.py **V2** | Pre-Release (Version 3+) | +| [Moonlink.js](https://github.com/1Lucas1apk/moonlink.js) | Node.js | **Any** | | +| [Magmastream](https://github.com/Blackfort-Hosting/magmastream) | Node.js | **Any** | | +| [Lavacord](https://github.com/lavacord/Lavacord) | Node.js | **Any** | | +| [Shoukaku](https://github.com/Deivu/Shoukaku) | Node.js | **Any** | | +| [Lavalink-Client](https://github.com/tomato6966/Lavalink-Client) | Node.js | **Any** | | +| [FastLink](https://github.com/PerformanC/FastLink) | Node.js | **Any** | | +| [Riffy](https://github.com/riffy-team/riffy) | Node.js | **Any** | | +| [DisCatSharp](https://github.com/Aiko-IT-Systems/DisCatSharp) | .NET | DisCatSharp | v10.4.2+ | +| [Lavalink4NET](https://github.com/angelobreuer/Lavalink4NET) | .NET | Discord.Net/DSharpPlus/Remora | v4+ | +| [Coglink](https://github.com/PerformanC/Coglink) | C | Concord | | +| [lavalink-rs](https://gitlab.com/vicky5124/lavalink-rs) | Rust, Python | **Any** | `tokio`-based, `asyncio`-based | + +
+v3.7 supporting Client Libraries + +| Client | Platform | Compatible With | Additional Information | +|---------------------------------------------------------------|----------|--------------------------------------------|---------------------------------| +| [Lavalink.kt](https://github.com/DRSchlaubi/lavalink.kt) | Kotlin | JDA/Kord/**Any** | Kotlin Coroutines | +| [lavaplay.py](https://github.com/HazemMeqdad/lavaplay.py) | Python | **Any\*** | *`asyncio`-based libraries only | +| [Mafic](https://github.com/ooliver1/mafic) | Python | discord.py **V2**/nextcord/disnake/py-cord | | +| [Wavelink](https://github.com/PythonistaGuild/Wavelink) | Python | discord.py **V2** | Version >=2, <3 | +| [Pomice](https://github.com/cloudwithax/pomice) | Python | discord.py **V2** | | +| [Lavacord](https://github.com/lavacord/lavacord) | Node.js | **Any** | | +| [Poru](https://github.com/parasop/poru) | Node.js | **Any** | | +| [Shoukaku](https://github.com/Deivu/Shoukaku) | Node.js | **Any** | | +| [Cosmicord.js](https://github.com/SudhanPlayz/Cosmicord.js) | Node.js | **Any** | | +| [DisCatSharp](https://github.com/Aiko-IT-Systems/DisCatSharp) | .NET | DisCatSharp | Only prior v10.4.1 | +| [Nomia](https://github.com/DHCPCD9/Nomia) | .NET | DSharpPlus | | +| [Lavalink4NET](https://github.com/angelobreuer/Lavalink4NET) | .NET | Discord.Net/DSharpPlus | < v4 | +| [DisGoLink](https://github.com/disgoorg/disgolink) | Go | **Any** | | + +
+ +Or alternatively, you can create your own client library, following the [implementation documentation](api/index.md). +Any client libraries marked with `Unmaintained` have been marked as such as their repositories have not received any commits for at least 1 year since time of checking, +however they are listed as they may still support Lavalink, and/or have not needed maintenance, however keep in mind that compatibility and full feature support is not guaranteed. diff --git a/docs/configuration/binary.md b/docs/configuration/binary.md new file mode 100644 index 000000000..9ebe74257 --- /dev/null +++ b/docs/configuration/binary.md @@ -0,0 +1,12 @@ +--- +description: How to run Lavalink as a standalone binary +--- + +# Standalone Binary + +Download binaries from the [Download Server](https://repo.arbjerg.dev/artifacts/lavalink/), [GitHub releases](https://github.com/lavalink-devs/Lavalink/releases) (specific versions prior to `v3.5` can be found in the [CI Server](https://ci.fredboat.com/viewLog.html?buildId=lastSuccessful&buildTypeId=Lavalink_Build&tab=artifacts&guest=1)) +or [GitHub actions](https://github.com/lavalink-devs/Lavalink/actions). + +Put an `application.yml` file in your working directory. ([Example here](index.md#example-applicationyml)) + +Run with `java -jar Lavalink.jar` from the same directory diff --git a/docs/configuration/docker.md b/docs/configuration/docker.md new file mode 100644 index 000000000..5f4284c2f --- /dev/null +++ b/docs/configuration/docker.md @@ -0,0 +1,54 @@ +--- +description: How to run Lavalink as a Docker container +--- + +# Docker + +Docker images can be found under [packages](https://github.com/lavalink-devs/Lavalink/pkgs/container/lavalink) with old builds prior to `v3.7.4` being available on [Docker Hub](https://hub.docker.com/r/fredboat/lavalink/). +There are 2 image variants `Ubuntu` and `Alpine`, the `Alpine` variant is smaller and can be used with the `-alpine` suffix, for example `ghcr.io/lavalink-devs/lavalink:3-alpine`. + +Install [Docker](https://docs.docker.com/engine/install/) & [Docker Compose](https://docs.docker.com/compose/install/) + +Create a `docker-compose.yml` with the following content: + +```yaml title="docker-compose.yml" +version: "3.8" + +services: + lavalink: + # pin the image version to Lavalink v4 + image: ghcr.io/lavalink-devs/lavalink:4 + container_name: lavalink + restart: unless-stopped + environment: + # set Java options here + - _JAVA_OPTIONS=-Xmx6G + # set lavalink server port + - SERVER_PORT=2333 + # set password for lavalink + - LAVALINK_SERVER_PASSWORD=youshallnotpass + volumes: + # mount application.yml from the same directory or use environment variables + - ./application.yml:/opt/Lavalink/application.yml + # persist plugins between restarts, make sure to set the correct permissions (user: 322, group: 322) + - ./plugins/:/opt/Lavalink/plugins/ + networks: + - lavalink + expose: + # lavalink exposes port 2333 to connect to for other containers (this is for documentation purposes only) + - 2333 + ports: + # you only need this if you want to make your lavalink accessible from outside of containers + - "2333:2333" +networks: + # create a lavalink network you can add other containers to, to give them access to Lavalink + lavalink: + name: lavalink +``` + +Create an `application.yml` file in the same directory as the `docker-compose.yml` file. ([Example here](index.md#example-applicationyml)) or use environment variables ([Example here](index.md#example-environment-variables)) + +Run `docker compose up -d`. See [Docker Compose Up](https://docs.docker.com/engine/reference/commandline/compose_up/) + +If your bot also runs in a docker container you can make that container join the lavalink network and use `lavalink` (service name) as the hostname to connect. +See [Docker Networking](https://docs.docker.com/network/) & [Docker Compose Networking](https://docs.docker.com/compose/networking/) diff --git a/docs/configuration/index.md b/docs/configuration/index.md new file mode 100644 index 000000000..91745d799 --- /dev/null +++ b/docs/configuration/index.md @@ -0,0 +1,115 @@ +--- +description: How to configure Lavalink +--- + +# Configuration + +The server configuration is done in `application.yml`. You can find an example below. + +## Example application.yml + +
+application.yml + +```yaml title="application.yml" +--8<-- "LavalinkServer/application.yml.example" +``` + +
+ +Alternatively, you can also use environment variables to configure the server. The environment variables are named the same as the keys in the `application.yml` file, but in uppercase and with `.` replaced with `_`. For example, `server.port` becomes `SERVER_PORT`. +For arrays, the index is appended to the key, starting at 0. For example, `LAVALINK_PLUGINS_0_DEPENDENCY` refers to the `dependency` key of the first plugin. + +## Example environment variables + +
+environment variables + +```env title="enviroment variables" +SERVER_PORT +SERVER_ADDRESS +SERVER_HTTP2_ENABLED + +LAVALINK_PLUGINS_0_DEPENDENCY +LAVALINK_PLUGINS_0_REPOSITORY + +LAVALINK_PLUGINS_1_DEPENDENCY +LAVALINK_PLUGINS_1_REPOSITORY + +LAVALINK_PLUGINS_DIR +LAVALINK_DEFAULT_PLUGIN_REPOSITORY +LAVALINK_DEFAULT_PLUGIN_SNAPSHOT_REPOSITORY + +LAVALINK_SERVER_PASSWORD +LAVALINK_SERVER_SOURCES_YOUTUBE +LAVALINK_SERVER_SOURCES_BANDCAMP +LAVALINK_SERVER_SOURCES_SOUNDCLOUD +LAVALINK_SERVER_SOURCES_TWITCH +LAVALINK_SERVER_SOURCES_VIMEO +LAVALINK_SERVER_SOURCES_HTTP +LAVALINK_SERVER_SOURCES_LOCAL + +LAVALINK_SERVER_FILTERS_VOLUME +LAVALINK_SERVER_FILTERS_EQUALIZER +LAVALINK_SERVER_FILTERS_KARAOKE +LAVALINK_SERVER_FILTERS_TIMESCALE +LAVALINK_SERVER_FILTERS_TREMOLO +LAVALINK_SERVER_FILTERS_VIBRATO +LAVALINK_SERVER_FILTERS_DISTORTION +LAVALINK_SERVER_FILTERS_ROTATION +LAVALINK_SERVER_FILTERS_CHANNEL_MIX +LAVALINK_SERVER_FILTERS_LOW_PASS + +LAVALINK_SERVER_BUFFER_DURATION_MS +LAVALINK_SERVER_FRAME_BUFFER_DURATION_MS +LAVALINK_SERVER_OPUS_ENCODING_QUALITY +LAVALINK_SERVER_RESAMPLING_QUALITY +LAVALINK_SERVER_TRACK_STUCK_THRESHOLD_MS +LAVALINK_SERVER_USE_SEEK_GHOSTING + +LAVALINK_SERVER_PLAYER_UPDATE_INTERVAL +LAVALINK_SERVER_YOUTUBE_SEARCH_ENABLED +LAVALINK_SERVER_SOUNDCLOUD_SEARCH_ENABLED + +LAVALINK_SERVER_GC_WARNINGS + +LAVALINK_SERVER_RATELIMIT_IP_BLOCKS +LAVALINK_SERVER_RATELIMIT_EXCLUDE_IPS +LAVALINK_SERVER_RATELIMIT_STRATEGY +LAVALINK_SERVER_RATELIMIT_SEARCH_TRIGGERS_FAIK +LAVALINK_SERVER_RATELIMIT_RETRY_LIMIT + +LAVALINK_SERVER_YOUTUBE_CONFIG_EMAIL +LAVALINK_SERVER_YOUTUBE_CONFIG_PASSWORD + +LAVALINK_SERVER_HTTP_CONFIG_PROXY_HOST +LAVALINK_SERVER_HTTP_CONFIG_PROXY_PORT +LAVALINK_SERVER_HTTP_CONFIG_PROXY_USER +LAVALINK_SERVER_HTTP_CONFIG_PROXY_PASSWORD + +METRICS_PROMETHEUS_ENABLED +METRICS_PROMETHEUS_ENDPOINT + +SENTRY_DSN +SENTRY_ENVIRONMENT +SENTRY_TAGS_SOME_KEY +SENTRY_TAGS_ANOTHER_KEY + +LOGGING_FILE_PATH +LOGGING_LEVEL_ROOT +LOGGING_LEVEL_LAVALINK + +LOGGING_REQUEST_ENABLED +LOGGING_REQUEST_INCLUDE_CLIENT_INFO +LOGGING_REQUEST_INCLUDE_HEADERS +LOGGING_REQUEST_INCLUDE_QUERY_STRING +LOGGING_REQUEST_INCLUDE_PAYLOAD +LOGGING_REQUEST_MAX_PAYLOAD_LENGTH + +LOGGING_LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE +LOGGING_LOGBACK_ROLLINGPOLICY_MAX_HISTORY +``` + +
+ +You can also use a combination of both. Environment variables take precedence over the `application.yml` file. diff --git a/docs/configuration/systemd.md b/docs/configuration/systemd.md new file mode 100644 index 000000000..da0cf695b --- /dev/null +++ b/docs/configuration/systemd.md @@ -0,0 +1,52 @@ +--- +description: How to run Lavalink as a Systemd service +--- + +# Systemd Service + +If you're using a Systemd-based Linux distribution you may want to install Lavalink as a background service. You will need to create a `lavalink.service` file inside `/usr/lib/systemd/system`. Create the file with the following template (replacing the values inside the `<>` brackets): + +```ini title="lavalink.service" +[Unit] +# Describe the service +Description=Lavalink Service + +# Configure service order +After=syslog.target network.target + +[Service] +# The user which will run Lavalink +User= + +# The group which will run Lavalink +Group= + +# Where the program should start +WorkingDirectory= + +# The command to start Lavalink +ExecStart=java -Xmx4G -jar /Lavalink.jar + +# Restart the service if it crashes +Restart=on-failure + +# Delay each restart by 5s +RestartSec=5s + +[Install] +# Start this service as part of normal system start-up +WantedBy=multi-user.target +``` + +To initiate the service, run + +```shell +$ sudo systemctl daemon-reload +$ sudo systemctl enable lavalink +$ sudo systemctl start lavalink +``` + +In addition to the usual log files, you can also view the log with +```shell +$ sudo journalctl -u lavalink +``` \ No newline at end of file diff --git a/docs/docker/Dockerfile b/docs/docker/Dockerfile new file mode 100644 index 000000000..faea375a7 --- /dev/null +++ b/docs/docker/Dockerfile @@ -0,0 +1,9 @@ +FROM squidfunk/mkdocs-material + +COPY requirements.txt /tmp/requirements.txt + +RUN pip install --no-cache-dir -r /tmp/requirements.txt + +# Start development server by default +ENTRYPOINT ["mkdocs"] +CMD ["serve", "--dev-addr=0.0.0.0:8000"] diff --git a/docs/docker/docker-compose.yml b/docs/docker/docker-compose.yml new file mode 100644 index 000000000..945c66c2a --- /dev/null +++ b/docs/docker/docker-compose.yml @@ -0,0 +1,15 @@ +version: '3.9' + +services: + mkdocs: + build: + context: ../../ + dockerfile: docs/docker/Dockerfile + container_name: mkdocs + restart: unless-stopped + ports: + - "8000:8000" + volumes: + - ../../:/docs/ + environment: + - TZ=Europe/Berlin diff --git a/docs/getting-started/faq.md b/docs/getting-started/faq.md new file mode 100644 index 000000000..8179ae8b7 --- /dev/null +++ b/docs/getting-started/faq.md @@ -0,0 +1,46 @@ +--- +description: Lavalink frequently asked questions. +--- + +# FAQ + +## What is Lavalink? + +Lavalink is a standalone audio player node that is used to stream music to Discord voice servers. It is written in Java and is based on [lavaplayer](https://github.com/lavalink-devs/lavaplayer) and [koe](https://github.com/KyokoBot/koe). + +## What is Lavalink used for? + +Lavalink is used to stream music to Discord voice servers. It is used by many Discord music bots, including [FredBoat](https://fredboat.com) and many others. + +## How do I install Lavalink? + +See the [Getting Started](index.md) for instructions on how to install Lavalink. + +## How do I configure Lavalink? + +See the [Configuration](../configuration/index.md) page for instructions on how to configure Lavalink. + +## How do I connect to Lavalink? + +See the [Clients](../clients.md) page for a list of clients that can connect to Lavalink. Each client has its own instructions on how to connect to Lavalink. + +## How do I run Lavalink in the background? + +See the [Docker](../configuration/docker.md) or [Systemd](../configuration/systemd.md) configuration pages for instructions on how to run Lavalink in the background. + +## How do I update Lavalink? + +Updating Lavalink is as simple as downloading the latest `Lavalink.jar` from [GitHub](https://github.com/lavalink-devs/Lavalink/releases/latest) and replacing the old jar file with the new one. +When using Docker, you can simply pull the latest image from [GitHub Container Registry](https://github.com/lavalink-devs/Lavalink/pkgs/container/lavalink). + +## How do I report a bug? + +Open an issue on the [issue tracker](https://github.com/lavalink-devs/Lavalink/issues/new?labels=bug&template=bug_report.md). + +## How do I get help? + +Join the [Lavalink support Discord]({{ discord_help }}) or open a [GitHub discussion](https://github.com/lavalink-devs/Lavalink/discussions/new?category=q-a). + +## How do I get support for a client? + +Open an issue on the client's GitHub repository or join the client's support Discord. The [Lavalink support Discord]({{ discord }}) also has a channel for each client where you can get support. \ No newline at end of file diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md new file mode 100644 index 000000000..d10319171 --- /dev/null +++ b/docs/getting-started/index.md @@ -0,0 +1,33 @@ +--- +description: Lavalink getting started guide. +--- + +# Getting Started + +Welcome to the Lavalink Getting Started guide. If you're new to Lavalink, follow these steps to get started: + +## Prerequisites + +Install Java 17 or higher. You can download it [here](https://www.azul.com/downloads/?package=jdk#zulu). + +## Installation + +Download the latest `Lavalink.jar` from [GitHub](https://github.com/lavalink-devs/Lavalink/releases/latest). + +## Configuration + +Check out the [configuration](../configuration/index.md) page to learn how to configure Lavalink. + +## Running Lavalink + +Run Lavalink with `java -jar Lavalink.jar`. + +If you want to keep Lavalink running in the background, you can check out the [Docker](../configuration/docker.md) or [Systemd](../configuration/systemd.md) configuration pages. + +## Connecting to Lavalink + +Pick a client from the [clients](../clients.md) page and follow their instructions on how to connect to Lavalink. + +## Getting stuck? + +If you're stuck, you can join the [Lavalink support Discord]({{ discord_help }}) or open a [GitHub discussion](https://github.com/lavalink-devs/Lavalink/discussions/new?category=q-a) for help or questions. diff --git a/docs/getting-started/troubleshooting.md b/docs/getting-started/troubleshooting.md new file mode 100644 index 000000000..c92f29a57 --- /dev/null +++ b/docs/getting-started/troubleshooting.md @@ -0,0 +1,55 @@ +--- +description: Lavalink troubleshooting steps. +--- + +# Trouble Shooting + +## Lavalink won't start + +If Lavalink won't start, check the following: + +- Make sure you have Java 17 or higher installed. You can download it [here](https://www.azul.com/downloads/?package=jdk#zulu). + +- Make sure you have downloaded the latest `Lavalink.jar` from [GitHub](https://github.com/lavalink-devs/Lavalink/releases/latest). + +- Make sure you have configured Lavalink correctly. Check out the [configuration](../configuration/index.md) page for more information. + +- If you're using Docker, make sure you have configured Docker correctly. + Check out the [Docker](../configuration/docker.md) page for more information. + +- If you're using Systemd, make sure you have configured Systemd correctly. + Check out the [Systemd](../configuration/systemd.md) page for more information. + +- If you are using a firewall, make sure you have opened the port you configured Lavalink to use. + +- If you are using a reverse proxy, make sure you have configured it correctly. Nginx needs to be configured to pass the `Upgrade` header for WebSockets to work. + +## Configuring more detailed Logging + +If you are having issues with Lavalink, you can enable more detailed logging by adding the following to your `application.yml`: + +In general there are 6 log levels: `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR` and `OFF`. + + +```yaml title="application.yml" +logging: + level: + # Set this to DEBUG to enable more detailed logging. Please note that this will log probably spam your console. + root: INFO + # Set this to DEBUG to enable more detailed logging from Lavalink + lavalink: DEBUG + # Set this to TRACE to see all WebSocket messages + lavalink.server.io.SocketContext: TRACE + + # This will log all requests to the REST API + request: + enabled: true + includeClientInfo: true + includeHeaders: false + includeQueryString: true + includePayload: true +``` + +## Lavalink won't connect to Discord / Play Audio + +If Lavalink doesn't connect to Discord, make sure you forward the `sessionId`, `token` and `enpoint` to Lavalink via the [player update endpoint](../api/rest.md#update-player). diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..fc5f0491d --- /dev/null +++ b/docs/index.md @@ -0,0 +1,60 @@ +--- +template: home.html +title: Home +description: Standalone audio sending node based on Lavaplayer. +hide: + - footer + - navigation + - navigation.tabs + - toc + - path +--- + +## Features + +::cards:: + +- title: Powered by Lavaplayer + icon: ':material-music:' + url: https://github.com/lavalink-devs/lavaplayer +- title: Minimal CPU/memory footprint + icon: ':octicons-cpu-16:' +- title: Twitch/YouTube stream support + icon: ':material-youtube:' +- title: Event system + icon: ':fontawesome-solid-right-left:' + url: api/websocket.md +- title: Seeking + icon: ':material-fast-forward-10:' + url: api/rest.md#update-player +- title: Volume control + icon: ':material-volume-high:' + url: api/rest.md#update-player +- title: Full REST API + icon: ':material-api:' + url: api/rest.md +- title: Statistics + icon: ':octicons-graph-16:' + url: api/rest.md#get-lavalink-stats +- title: Basic authentication + icon: ':material-lock:' + url: api/rest.md +- title: Prometheus metrics + icon: ':simple-prometheus:' + url: https://prometheus.io/ +- title: Docker images + icon: ':simple-docker:' + url: configuration/docker.md +- title: Plugin support + icon: ':material-power-plug-outline:' + url: plugins.md + +::/cards:: + +## Found a Bug? + +If you found a bug, please report it on the [issue tracker](https://github.com/lavalink-devs/Lavalink/issues/new?labels=bug&template=bug_report.md). + +## Need Help? + +Join the [Lavalink support Discord]({{ discord_help }}) or open a [GitHub discussion](https://github.com/lavalink-devs/Lavalink/discussions/new?category=q-a) for help or questions. diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml new file mode 100644 index 000000000..17d70d029 --- /dev/null +++ b/docs/mkdocs.yml @@ -0,0 +1,127 @@ +# yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json + +site_name: Lavalink Docs +site_description: Lavalink Documentation +site_author: Lavalink Contributors +site_url: https://lavalink.dev +site_dir: ../site +docs_dir: . + +repo_name: Lavalink +repo_url: https://github.com/lavalink-devs/Lavalink +edit_uri: edit/master/docs/ + +copyright: Licensed under the MIT license + +nav: + - Home: index.md + - Getting Started: + - getting-started/index.md + - Troubleshooting: getting-started/troubleshooting.md + - FAQ: getting-started/faq.md + - Configuration: + - configuration/index.md + - Binary: configuration/binary.md + - Systemd: configuration/systemd.md + - Docker: configuration/docker.md + - Clients: clients.md + - Plugins: plugins.md + - API: + - api/index.md + - Websocket: api/websocket.md + - Rest: api/rest.md + - Plugins: api/plugins.md + - Changelog: + - changelog/index.md + - v4: changelog/v4.md + - v3: changelog/v3.md + - v2: changelog/v2.md + +extra_css: + - stylesheets/style.css + - stylesheets/neoteroi-cards.css + +extra: + homepage: / + discord: https://discord.gg/BTHvsc7WsT + discord_help: https://discord.gg/ZW4s47Ppw4 + social: + - icon: fontawesome/brands/github + link: https://github.com/lavalink-devs + - icon: fontawesome/brands/discord + link: https://discord.gg/BTHvsc7WsT + +theme: + name: material + custom_dir: overrides + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: custom + accent: custom + toggle: + icon: material/brightness-7 + name: Switch to dark mode + + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: custom + accent: custom + toggle: + icon: material/brightness-4 + name: Switch to light mode + features: + - navigation.instant + - navigation.tracking + - navigation.tabs + - navigation.top + - navigation.tabs.sticky + - navigation.footer + - navigation.path + - navigation.indexes + - toc.follow + - content.code.annotate + - announce.dismiss + font: + text: Roboto + code: Roboto Mono + favicon: assets/favicon.png + logo: assets/logo.svg + icon: + repo: fontawesome/brands/github + +markdown_extensions: + - admonition + - meta + - pymdownx.details + - pymdownx.superfences + - pymdownx.highlight + - footnotes + - def_list + - attr_list + - md_in_html + - toc: + permalink: true + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.tabbed: + alternate_style: true + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg + - pymdownx.snippets: + check_paths: true + base_path: ../ + - neoteroi.cards + +plugins: + - offline + - search: + lang: en + - same-dir + - social: + cards_layout_options: + background_color: "#ff624a" + color: "#FFFFFF" + - git-revision-date-localized + - markdownextradata diff --git a/docs/overrides/home.html b/docs/overrides/home.html new file mode 100644 index 000000000..db0525ebe --- /dev/null +++ b/docs/overrides/home.html @@ -0,0 +1,32 @@ +{% extends "main.html" %} +{% block tabs %} +{{ super() }} + +
+
+
+
+
+
+ +
+
+ {{ page.content }} +
+
+
+
+{% endblock %} +{% block content %} +{% endblock %} +{% block footer %} +{{ super() }} +{% endblock %} + diff --git a/docs/overrides/main.html b/docs/overrides/main.html new file mode 100644 index 000000000..af7d54ba8 --- /dev/null +++ b/docs/overrides/main.html @@ -0,0 +1,5 @@ +{% extends "base.html" %} + +{% block announce %} +The Lavalink docs are currently a work in progress. You can give us feedback on GitHub +{% endblock %} \ No newline at end of file diff --git a/docs/plugins.md b/docs/plugins.md new file mode 100644 index 000000000..4ea709419 --- /dev/null +++ b/docs/plugins.md @@ -0,0 +1,24 @@ +--- +description: A list of plugins for Lavalink. +--- + +# Plugins + +Lavalink supports third-party plugins to add additional functionality such as custom audio sources, custom filters, +WebSocket handling, REST endpoints, and much more. + +Lavalink loads all .jar files placed in the `plugins` directory, which you may need to create yourself. Lavalink can +also download plugin .jar files automatically by editing the configuration file. See the respective plugin repository +for instructions. + +| Plugin | Description | +|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------| +| [Google Cloud TTS plugin](https://github.com/DuncteBot/tts-plugin) | A text to speech plugin using the [google cloud tts api](https://cloud.google.com/text-to-speech/docs) | +| [SponsorBlock plugin](https://github.com/topi314/Sponsorblock-Plugin) | Skip sponsor segments in YouTube videos & return YouTube video chapter information | +| [LavaSrc plugin](https://github.com/topi314/LavaSrc) | Spotify, Apple Music & Deezer(native play) support | +| [LavaSearch plugin](https://github.com/topi314/LavaSearch) | Advanced search functionality including playlists, albums, artists, tracks & terms | +| [DuncteBot plugin](https://github.com/DuncteBot/skybot-lavalink-plugin) | Additional source managers that are not widely used | +| [Extra Filter plugin](https://github.com/rohank05/lavalink-filter-plugin) | Additional audio filters for lavalink | +| [XM plugin](https://github.com/esmBot/lava-xm-plugin) | Support for various [music tracker module](https://en.wikipedia.org/wiki/Module_file) formats | + +If you want to make your own plugin see [here](api/plugins.md) \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..c29c2bb64 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,9 @@ +mkdocs +mkdocs-material +mkdocs-material-extensions +mkdocs-git-revision-date-localized-plugin +pillow +cairosvg +neoteroi-mkdocs +mkdocs-same-dir +mkdocs-markdownextradata-plugin \ No newline at end of file diff --git a/docs/stylesheets/neoteroi-cards.css b/docs/stylesheets/neoteroi-cards.css new file mode 100644 index 000000000..13cbf35ef --- /dev/null +++ b/docs/stylesheets/neoteroi-cards.css @@ -0,0 +1,76 @@ +.nt-cards.nt-grid { + display: grid; + grid-auto-columns: 1fr; + gap: 0.5rem; + max-width: 100vw; + overflow-x: auto; + padding: 1rem; +} + +.nt-cards.nt-grid.cols-2 { + grid-template-columns: repeat(2, 1fr); +} + +.nt-cards.nt-grid.cols-3 { + grid-template-columns: repeat(3, 1fr); +} + +@media only screen and (max-width: 900px) { + .nt-cards.nt-grid { + grid-template-columns: repeat(2, 1fr) !important; + } +} + +@media only screen and (max-width: 600px) { + .nt-cards.nt-grid { + grid-template-columns: repeat(1, 1fr) !important; + } +} + +.nt-card { + border-radius: 4px; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12); + background-color: var(--md-typeset-kbd-color); +} + +.nt-card:hover { + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.24), 0 3px 1px -2px rgba(0, 0, 0, 0.3), 0 1px 5px 0 rgba(0, 0, 0, 0.22); +} + +.nt-card-wrap > div, +.nt-card > a > div { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + padding: .625em; + border-radius: 4px; +} + +.nt-card > a > div:hover { + background-color: var(--md-typeset-kbd-color); +} + +.nt-card-content { + width: 100%; +} + +.nt-card-title { + font-weight: bold; + margin: 4px 0 8px 0; + text-align: center; + color: var(--md-default-fg-color); +} + +.nt-card-icon { + color: var(--md-default-fg-color); +} + +.nt-card-icon svg, +.nt-card-icon .twemoji, +.nt-card-icon .icon { + display: block; + width: 34px !important; + height: 34px !important; + max-height: 34px !important; +} diff --git a/docs/stylesheets/style.css b/docs/stylesheets/style.css new file mode 100644 index 000000000..e3fd47d9e --- /dev/null +++ b/docs/stylesheets/style.css @@ -0,0 +1,51 @@ +:root { + --md-primary-fg-color: #ff624a; + --md-primary-fg-color--light: #d39674; + --md-primary-fg-color--dark: #b25d09; + + --md-accent-fg-color: #c24734; + + @media screen { + [data-md-color-scheme="slate"] { + --md-default-fg-color: hsla(var(--md-hue), 15%, 90%, 1); + } + } +} + +.md-tabs__link { + opacity: 1; + font-weight: bold; +} + +.md-tabs__item.md-tabs__item--active { + border-bottom: 4px solid var(--md-default-fg-color); +} + +.container { + margin-top: 1rem; +} + +.container .logo { + text-align: center; +} + +.logo .md-button { + margin-bottom: 4px; +} + +#home__title { + font-size: 2rem; + font-weight: 600; + margin-bottom: 1rem; + color: unset; +} + +#home__logo { + width: 10rem; +} + +@media only screen and (max-width: 479px) { + .home__logo { + width: 6rem; + } +} \ No newline at end of file