# MusicLink Bot - Platform Setup Guide ## Overview MusicLink is a bot that detects music links (Spotify, YouTube, Apple Music, etc.) in chat messages and responds with equivalent links on other streaming services. It uses the [idonthavespotify](https://github.com/sjdonado/idonthavespotify) API for link conversion, so **no Spotify/YouTube API credentials are required**. ## Architecture ``` ┌─────────────────────────── jrz1 (NixOS VPS) ───────────────────────────┐ │ │ │ ┌─────────────────────────┐ ┌─────────────────────────────┐ │ │ │ matterbridge.service │ │ musiclink.service │ │ │ │ │ │ │ │ │ │ Bridges chat platforms │ WS │ Detects music links and │ │ │ │ and exposes local API │◄─────►│ responds with alternatives │ │ │ │ │ │ │ │ │ │ - Slack bridge │ │ - Link detection (regex) │ │ │ │ - API bridge (:4242) │ │ - Calls idonthavespotify │ │ │ │ - (Discord, Matrix │ │ API for conversion │ │ │ │ can be added later) │ │ │ │ │ └────────────┬────────────┘ └──────────────┬──────────────┘ │ │ │ │ │ └───────────────┼────────────────────────────────────┼────────────────────┘ │ │ ▼ ▼ ┌───────────┐ ┌────────────────────┐ │ Slack │ │ idonthavespotify │ └───────────┘ │ (external API) │ └────────────────────┘ ``` ### How It Works 1. User posts a music link in Slack (e.g., `https://open.spotify.com/track/abc123`) 2. Matterbridge forwards the message to musiclink via WebSocket 3. MusicLink detects the music URL and calls the idonthavespotify API 4. The API returns equivalent links for Spotify, YouTube, Apple Music, Tidal, Deezer, etc. 5. MusicLink formats and sends the response back through matterbridge to Slack ### Why Matterbridge? Matterbridge acts as a universal adapter. Instead of writing platform-specific code for each chat service, we: 1. Run matterbridge (connects to Slack, Discord, Matrix, etc.) 2. Matterbridge exposes a simple WebSocket API locally 3. Our bot connects to that API and receives/sends messages as JSON This means: - **Adding a new platform** = config change in matterbridge (no code changes) - **Bot code stays simple** = just WebSocket + JSON, works from any language - **Already in nixpkgs** = `services.matterbridge` module ready to use ### Why idonthavespotify API? - **No API credentials needed** - No Spotify Developer account, no YouTube API keys - **Supports 8 platforms** - Spotify, YouTube, Apple Music, Deezer, Tidal, SoundCloud, Qobuz, Bandcamp - **Simple integration** - Single HTTP POST, returns all platform links - **Open source** - Can self-host if needed later ## Components ### 1. Matterbridge (existing nixpkgs module) **Package:** `pkgs.matterbridge` (v1.26.0) **Module:** `services.matterbridge` **Docs:** https://github.com/42wim/matterbridge/wiki ### 2. MusicLink Bot (this repo) **Language:** Go **Location:** `/home/dan/proj/musiclink` **Connects to:** Matterbridge API via WebSocket **Uses:** idonthavespotify API for link conversion ## NixOS Configuration ### Matterbridge Service ```nix { config, pkgs, ... }: { services.matterbridge = { enable = true; configPath = "/var/lib/matterbridge/matterbridge.toml"; }; # Ensure config directory exists systemd.tmpfiles.rules = [ "d /var/lib/matterbridge 0750 matterbridge matterbridge -" ]; } ``` ### MusicLink Service ```nix { config, pkgs, ... }: let musiclink = pkgs.buildGoModule { pname = "musiclink"; version = "0.1.0"; src = /home/dan/proj/musiclink; # or fetchFromGitHub vendorHash = null; # update after first build }; in { systemd.services.musiclink = { description = "MusicLink Bot"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" "matterbridge.service" ]; requires = [ "matterbridge.service" ]; serviceConfig = { Type = "simple"; ExecStart = "${musiclink}/bin/musiclink -config /var/lib/musiclink/config.toml"; Restart = "always"; RestartSec = "5s"; # Hardening DynamicUser = true; StateDirectory = "musiclink"; ProtectSystem = "strict"; ProtectHome = true; NoNewPrivileges = true; ProtectKernelTunables = true; ProtectKernelModules = true; ProtectControlGroups = true; RestrictNamespaces = true; LockPersonality = true; MemoryDenyWriteExecute = true; RestrictRealtime = true; RestrictSUIDSGID = true; PrivateMounts = true; SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ]; }; }; systemd.tmpfiles.rules = [ "d /var/lib/musiclink 0750 musiclink musiclink -" ]; } ``` ## Configuration Files ### Matterbridge (`/var/lib/matterbridge/matterbridge.toml`) ```toml # ============================================================================= # Matterbridge Configuration # Bridges Slack to local API for musiclink bot # ============================================================================= # ----------------------------------------------------------------------------- # Slack Connection # ----------------------------------------------------------------------------- [slack.workspace] # Bot User OAuth Token (starts with xoxb-) # Get from: https://api.slack.com/apps → Your App → OAuth & Permissions Token = "xoxb-YOUR-SLACK-BOT-TOKEN" # Show username before messages PrefixMessagesWithNick = true # Optional: customize how remote users appear RemoteNickFormat = "[{PROTOCOL}] {NICK}" # ----------------------------------------------------------------------------- # Local API Bridge (for musiclink bot) # ----------------------------------------------------------------------------- [api.musiclink] # Only bind to localhost (not exposed externally) BindAddress = "127.0.0.1:4242" # Shared secret for bot authentication Token = "GENERATE-A-RANDOM-TOKEN-HERE" # Message buffer size Buffer = 1000 # ----------------------------------------------------------------------------- # Gateway Configuration # Routes messages between bridges # ----------------------------------------------------------------------------- [[gateway]] name = "main" enable = true # Slack channels to bridge [[gateway.inout]] account = "slack.workspace" channel = "music" # Change to your channel name # API endpoint for musiclink bot [[gateway.inout]] account = "api.musiclink" channel = "api" ``` ### MusicLink (`/var/lib/musiclink/config.toml`) ```toml # ============================================================================= # MusicLink Bot Configuration # # Uses idonthavespotify API - no external API credentials needed! # ============================================================================= [matterbridge] # Must match matterbridge API bridge config url = "ws://127.0.0.1:4242/api/websocket" token = "GENERATE-A-RANDOM-TOKEN-HERE" # Same as matterbridge [api.musiclink].Token gateway = "main" # Bot identity username = "MusicLink" avatar = "" # Optional URL ``` That's the entire config - no Spotify/YouTube API keys required! ## Secrets Management The only secrets needed are: - **Slack bot token** (`xoxb-...`) - **Matterbridge↔MusicLink shared token** (generate a random string) Options for managing secrets: 1. **sops-nix** - Encrypted secrets in repo, decrypted at deploy time 2. **agenix** - Age-encrypted secrets 3. **Environment files** - `EnvironmentFile=` in systemd service Example with environment file: ```nix systemd.services.matterbridge.serviceConfig = { EnvironmentFile = "/run/secrets/matterbridge.env"; }; ``` ```bash # /run/secrets/matterbridge.env MATTERBRIDGE_SLACK_TOKEN=xoxb-... ``` Then in matterbridge.toml: ```toml [slack.workspace] Token = "${MATTERBRIDGE_SLACK_TOKEN}" ``` ## Slack App Setup 1. Go to https://api.slack.com/apps 2. Create New App → From scratch 3. App name: "MusicLink", select workspace 4. **OAuth & Permissions:** - Bot Token Scopes needed: - `channels:history` - Read messages - `channels:read` - See channel list - `chat:write` - Send messages - `users:read` - Get user info 5. **Install to Workspace** 6. Copy **Bot User OAuth Token** (`xoxb-...`) 7. Invite bot to channel: `/invite @MusicLink` ## Deployment Checklist - [ ] Slack app created and bot token obtained - [ ] Generate random token for matterbridge↔musiclink auth - [ ] Add matterbridge NixOS config - [ ] Create `/var/lib/matterbridge/matterbridge.toml` - [ ] Add musiclink NixOS config - [ ] Create `/var/lib/musiclink/config.toml` - [ ] Deploy NixOS config (`nixos-rebuild switch`) - [ ] Verify services: `systemctl status matterbridge musiclink` - [ ] Test: Post a Spotify link in the configured Slack channel ## Logs & Debugging ```bash # Check service status systemctl status matterbridge systemctl status musiclink # View logs journalctl -u matterbridge -f journalctl -u musiclink -f # Test matterbridge API directly curl -H "Authorization: Bearer YOUR-TOKEN" http://localhost:4242/api/health # Test idonthavespotify API directly curl -X POST 'https://idonthavespotify.sjdonado.com/api/search?v=1' \ -H 'Content-Type: application/json' \ -d '{"link":"https://open.spotify.com/track/4iV5W9uYEdYUVa79Axb7Rh"}' ``` ## Adding More Platforms Later To add Discord, Matrix, or other platforms, just update `matterbridge.toml`: ```toml # Add Discord [discord.myserver] Token = "discord-bot-token" Server = "server-id" # Add to gateway [[gateway.inout]] account = "discord.myserver" channel = "music" ``` No changes needed to musiclink bot - matterbridge handles the bridging. ## Self-Hosting idonthavespotify (Optional) If you want to avoid the external API dependency, idonthavespotify can be self-hosted: - Repo: https://github.com/sjdonado/idonthavespotify - Requires: Docker or Bun runtime - Note: Self-hosting still requires Spotify/Tidal API credentials For most use cases, the hosted API at `idonthavespotify.sjdonado.com` is sufficient. ## Questions? - Matterbridge docs: https://github.com/42wim/matterbridge/wiki - idonthavespotify: https://github.com/sjdonado/idonthavespotify - MusicLink repo: `/home/dan/proj/musiclink`