BeatDock Discord Music Bot

repository·main·Indexed 41 days ago

https://github.com/albertgmz/beatdock

A modern Discord music bot powered by Lavalink supporting YouTube, SoundCloud, and Spotify. Version 2.11.0 features slash commands, multi-language support, and a Docker-based deployment system including an automated YouTube poToken refresher to ensure seamless playback of non-music videos and autoplay.

Tokens
5.6K
Snippets
27
Records
33
Agent score
51%

What's inside BeatDock

  1. How YouTube poToken auto-refresh works

    main

    To play non-music videos or use autoplay/recommendations, YouTube's WEB client requires a short-lived poToken. BeatDock automates this process without requiring Google credentials:

    1. bgutil-provider: A service that mints a {poToken, visitorData} pair using your server's own IP address.
    2. pot-refresher: A service that pushes this token to Lavalink's POST /youtube hot-swap route on a loop. It re-pushes every POT_REFRESH_INTERVAL seconds (default 1800).

    This allows for seamless token updates without restarting Lavalink. If the refresher is down, music playback (via MUSIC/ANDROID_VR clients) will continue to work, though age-restricted videos may become unavailable.

  2. Deploy BeatDock without self-hosted Lavalink

    main

    If you do not want to manage a Lavalink server, BeatDock can automatically connect to free public Lavalink v4 servers.

    To use this mode, ensure the following environment variables are not set in your .env file:

    • LAVALINK_HOST
    • LAVALINK_PORT
    • LAVALINK_PASSWORD

    Note: When using public nodes, the automated YouTube poToken refresh mechanism (via bgutil-provider and pot-refresher) does not apply. Token management is handled by the public node operators. You can restrict which public hosts are used by setting the PUBLIC_NODE_HOST_ALLOWLIST variable.

    # To use public servers, comment out these lines in your .env:
    # LAVALINK_HOST=lavalink
    # LAVALINK_PORT=2333
    # LAVALINK_PASSWORD=youshallnotpass
  3. Deploy BeatDock with Docker (Recommended)

    main

    The recommended way to deploy BeatDock is using Docker Compose with a self-hosted Lavalink instance and a YouTube poToken refresher. This setup ensures full control over playback and automated YouTube token management.

    Prerequisites

    • A Discord bot token from the Discord Developer Portal.
    • Important: Enable all 3 Privileged Gateway Intents (Presence, Server Members, Message Content) in your Discord application settings.
    • Docker installed.

    Setup Steps

    1. Create a project directory:

      mkdir beatdock && cd beatdock
    2. Create a .env file with your Discord token:

      TOKEN=your_discord_bot_token
    3. Create a docker-compose.yml file containing the bot, lavalink, bgutil-provider, and pot-refresher services. (See the full configuration in the official documentation).

    4. Create an application.yml file for Lavalink configuration. This file defines server ports, YouTube plugin settings, and enabled sources (SoundCloud, Bandcamp, etc.).

    5. Add the pot-refresher.sh script: Download scripts/pot-refresher.sh and place it in a scripts/ folder relative to your docker-compose.yml.

    6. Deploy:

      docker compose up -d
    mkdir beatdock && cd beatdock
    # Create .env, docker-compose.yml, application.yml, and scripts/pot-refresher.sh
    docker compose up -d
  4. Manage the BeatDock Docker deployment

    main

    Use standard Docker Compose commands to manage the bot lifecycle.

    Important for Configuration Updates: If you modify application.yml, you must use the --force-recreate flag when bringing the containers up. Because application.yml is bind-mounted as a single file, Docker will not automatically detect changes to the file content unless the container is forced to recreate.

    TaskCommand
    View logsdocker compose logs -f
    Restart botdocker compose restart
    Stop botdocker compose down
    Update image and configdocker compose pull && docker compose up -d --force-recreate

    Portainer Users: Use Pull and redeploy with Re-pull image and redeploy enabled to ensure all containers are recreated and bind mounts are refreshed.

    docker compose pull && docker compose up -d --force-recreate
  5. Configure BeatDock via environment variables

    main

    All bot behavior is configured using environment variables in a .env file.

    VariableDefaultDescription
    TOKEN-Discord bot token (required)
    SPOTIFY_ENABLEDfalseEnable Spotify search support
    SPOTIFY_CLIENT_ID-Spotify app client ID
    SPOTIFY_CLIENT_SECRET-Spotify app client secret
    DEFAULT_LANGUAGEenGlobal bot language (en, es, tr, it, pt-BR)
    DEFAULT_VOLUME80Default playback volume (0-100)
    AUTOPLAY_DEFAULTfalseEnable autoplay by default when music starts
    ALLOWED_ROLES-Comma-separated role IDs to restrict access
    DEFAULT_SEARCH_PLATFORMytmsearchDefault search platform for user queries
    LAVALINK_PASSWORDyoushallnotpassLavalink server password
    PUBLIC_NODE_HOST_ALLOWLIST-Comma-separated host or *.domain allowlist for public Lavalink fallback
    QUEUE_EMPTY_DESTROY_MS30000Disconnect after queue empties (ms)
    EMPTY_CHANNEL_DESTROY_MS60000Disconnect from empty channel (ms)
  6. Configure BeatDock via Docker Compose

    main

    BeatDock is deployed using a multi-container Docker Compose setup consisting of the bot, lavalink, bgutil-provider, and pot-refresher services.

    To run the stack, ensure you have a .env file in the same directory to provide necessary credentials and a scripts/pot-refresher.sh file for the token refresh mechanism. The lavalink service requires an application.yml file mounted from your host to /opt/Lavalink/application.yml.

    docker compose up -d
  7. Troubleshoot YouTube playback failures

    main

    If all YouTube tracks fail to play, YouTube may be blocking the clients used by Lavalink.

    1. Verify client status: Check the Lavalink logs to see which clients were initialized and if they are being blocked by searching for specific error strings.
    2. Verify configuration: If the client list in the logs does not match your application.yml, the container is likely running an old configuration. Recreate the Lavalink container to apply changes.
    3. Workaround: If YouTube is blocked, you can switch the default search platform to SoundCloud in your .env file to keep the bot functional while waiting for an upstream fix. Note that Autoplay is a YouTube-only feature and will remain idle if SoundCloud is the default.

    Note: In logs, MUSIC is expected to initialize under the name WEB_REMIX.

    # Check which clients loaded
    docker compose logs lavalink | grep -i "YouTube source initialised with clients"
    
    # Check for blocking errors
    docker compose logs lavalink | grep -iE "AllClientsFailedException|Sign in to confirm|requires login"
    
    # Recreate Lavalink to apply new application.yml config
    docker compose up -d --force-recreate lavalink
    # Switch to SoundCloud in .env
    DEFAULT_SEARCH_PLATFORM=scsearch
    # Restart the bot after changing .env
    docker compose restart bot
  8. Fix audio issues on Raspberry Pi 5

    main

    Raspberry Pi 5 running Debian 13 may use a 16KB memory page size, which is incompatible with Lavalink's DAVE encryption library. Lavalink requires a 4096 page size.

    1. Check page size: Run getconf PAGE_SIZE. If the output is not 4096, you must change the kernel configuration.
    2. Apply fix: Add kernel=kernel8.img under the [all] section in /boot/firmware/config.txt.
    3. Restart: Reboot the device and then restart your containers.
    getconf PAGE_SIZE
  9. Configure BeatDock healthcheck heartbeat path

    main

    The bot service uses a file-based heartbeat mechanism to determine health. You can customize the path where the heartbeat file is located using the HEALTHCHECK_HEARTBEAT_PATH environment variable. The service considers itself healthy if the file's modification time is less than 120,000ms (2 minutes) old.

    # Custom path for the heartbeat file (default: /tmp/beatdock-alive)
    HEALTHCHECK_HEARTBEAT_PATH=/tmp/custom-heartbeat
  10. Configure the YouTube poToken refresher

    main

    The pot-refresher service automates the process of minting YouTube poToken and visitorData via the bgutil-provider and pushing them to Lavalink's POST /youtube hot-swap route. This prevents issues with non-music videos and autoplay/RD mixes.

    You can control the frequency of these refreshes using the POT_REFRESH_INTERVAL environment variable.

    # Password for the Lavalink instance
    LAVALINK_PASSWORD=youshallnotpass
    
    # Interval in seconds between poToken refreshes (default: 1800)
    POT_REFRESH_INTERVAL=1800
  11. BeatDock Discord Commands Reference

    main

    The following slash commands are available for controlling the music bot:

    CommandDescription
    /play <query> [next]Play a song (optionally add to front of queue)
    /search <query>Search and select tracks
    /pausePause/resume
    /skipSkip track
    /backPrevious track
    /stopStop and disconnect
    /queueShow queue
    /shuffleShuffle queue
    /autoplayToggle autoplay mode
    /loopToggle loop mode
    /clearClear queue
    /volume <1-100>Set volume
    /lyricsShow lyrics for the current song
    /filterApply audio effects and EQ presets
    /nowplayingCurrent track info
    /inviteGet bot invite link
    /aboutBot info
  12. Use LanguageManager for translations

    main

    The LanguageManager class handles multi-language support by loading JSON locale files from a locales directory. It supports a fallback mechanism to English (en) if a requested locale or key is missing.

    Translations can include placeholders in the format {index} (e.g., {0}, {1}), which are replaced by arguments passed to the get method.

    const LanguageManager = require('./LanguageManager');
    const lm = new LanguageManager();
    
    // Basic translation
    const text = lm.get('en', 'welcome_message');
    
    // Translation with placeholders
    // If 'greet' in en.json is "Hello, {0}!"
    const greeting = lm.get('en', 'greet', 'Alice'); // Returns "Hello, Alice!"
    
    // Fallback to English if locale is missing
    const fallbackText = lm.get('non-existent-locale', 'some_key');