Telegram bot that downloads music links and Telegram audio uploads into a local music folder for Navidrome or any other library scanner.
- Telegram audio files and audio-like document uploads
- YouTube and YouTube Music track and playlist links
- Spotify track, playlist, and album links
- Apple Music track, playlist, and album links
- Shazam links
- Multiple links in one message
Spotify, Apple Music, and Shazam links are used as search inputs. The bot does not bypass DRM or download directly from those providers; it resolves a matching YouTube or YouTube Music result and stores that audio. Spotify and Apple Music playlists and albums use tokenless public extraction and may fail if the public page does not expose track data. Large Spotify playlists can page beyond the first 100 public embed tracks when Spotify exposes an anonymous public token.
- Link downloads are converted to MP3 at up to 320 kbps via
yt-dlpandffmpeg. - Telegram uploads are stored in their original format with their original filename when available.
- Link downloads are stored directly under
MUSIC_DIRasArtist - Title.mp3when artist metadata exists. - If artist metadata is missing, link downloads are stored as
Title.mp3. - Existing target files are skipped instead of overwritten.
- Playlist and album links are expanded into individual track jobs, capped by
MAX_COLLECTION_TRACKS. - Playlist and album downloads are stored under
Owner - Collection Namefolders when provider metadata exposes a real owner. If the owner is missing or looks like a URL, the folder falls back toCollection Name(<collection URL>). - Finished Telegram requests with failed jobs show a
Rerun failedbutton that queues only the failed items again. - Allowlisted owners can create one-time guest links that grant revocable, in-memory access to submit music in a private bot chat.
- Telegram status messages follow a clear
Preparing -> Queued -> Downloading -> Completedlifecycle, use source-specific icons, show playlist/album or single-track title and artist when known, and keep final statuses compact. IfRECEIVED_STICKER_IDis configured, the bot shows that sticker for 5 seconds while work continues, then replaces it with the latest status message.
Create a Telegram bot with BotFather, copy .env.example to .env, and fill in the required values:
TELEGRAM_BOT_TOKEN=123456:replace-me
ALLOWED_TELEGRAM_USER_IDS=123456789
MUSIC_DIR=./music
DOWNLOAD_TMP_DIR=./tmp/sync-me-maybe
MAX_DOWNLOAD_SECONDS=900
MAX_COLLECTION_TRACKS=1000
UPLOAD_BATCH_WINDOW_SECONDS=2
RECEIVED_STICKER_ID=
LOG_LEVEL=INFOFind your Telegram user ID by running the bot and sending /id.
Install and run locally:
uv sync
uv run sync-me-maybeffmpeg must be available on PATH for audio conversion.
Published images are available from GitHub Container Registry:
ghcr.io/artamrj/sync-me-maybe:latestFor a NAS, VPS, or home server deployment, copy .env.example to .env and set the Telegram token, allowed user IDs, host paths, and host UID/GID. On Linux or Synology hosts, get the UID and GID for the account that should own downloaded music:
idThen start the service:
docker compose pull
docker compose up -d
docker compose logs -fThe compose file passes PUID and PGID into the container and mounts:
${MUSIC_DIR_HOST}to/music${DOWNLOAD_TMP_DIR_HOST}to/tmp/sync-me-maybe${YTDLP_COOKIES_FILE_HOST}to/config/cookies.txtfor optional yt-dlp cookies
Use writable host folders for the configured UID/GID. The app sets MUSIC_DIR=/music and DOWNLOAD_TMP_DIR=/tmp/sync-me-maybe inside the container, so the container remains stateless except for mounted music, temporary downloads, and optional cookies. To use cookies, set YTDLP_COOKIES_FILE=/config/cookies.txt and point YTDLP_COOKIES_FILE_HOST at the host cookies file.
The compose file intentionally does not use Docker's user: ${PUID}:${PGID} setting because some NAS runtimes reject that switch with operation not permitted. If your NAS reports uid=1000(arta) gid=10(admin), use PUID=1000 and PGID=10.
Tags:
latest: latest image published by the release pipeline.<project version>: exact version frompyproject.toml, without avprefix.
See CHANGELOG.md for release history and versioning notes.
TELEGRAM_BOT_TOKEN: required Telegram bot token.ALLOWED_TELEGRAM_USER_IDS: required comma- or semicolon-separated Telegram user IDs allowed to use the bot.MUSIC_DIR: directory where completed files are stored. Defaults to./music.DOWNLOAD_TMP_DIR: directory for temporary downloads. Defaults to./tmp/sync-me-maybe.YTDLP_COOKIES_FILE: optional cookies file passed toyt-dlp.MAX_DOWNLOAD_SECONDS: maximum download duration before a job fails. Defaults to900.MAX_COLLECTION_TRACKS: maximum playlist or album tracks to enqueue. Defaults to1000.UPLOAD_BATCH_WINDOW_SECONDS: seconds to group quickly forwarded audio uploads. Defaults to2; set0to disable batching.RECEIVED_STICKER_ID: optional Telegram sticker file ID shown for 5 seconds as a temporary acknowledgement after the user sends a supported link or upload.LOG_LEVEL: Python logging level. Defaults toINFO.
/start: show accepted inputs and authorization status./help: show supported links and usage./id: show your Telegram user ID for allowlist setup./health: verify the bot can write to the music directory./queue: show the active download and pending queue./guests: create one-time guest invitations, list active guests, and revoke guest access (allowlisted owners only).
Every user configured in ALLOWED_TELEGRAM_USER_IDS is an owner. Open /guests and select
Create invite to receive a one-time Telegram link. The recipient must open that link in a
private chat with the bot. Once accepted, the guest can submit the same supported links and audio
uploads as an owner, but cannot inspect the global queue, run health checks, or manage guests.
Guest access lasts until an owner revokes it from /guests or the bot restarts. Unused invitation
links are also kept only in memory and become invalid after a restart. Revoking a guest blocks new
requests immediately but does not cancel work that is already queued or downloading.
Incoming links and uploads are added to a global in-memory queue immediately. The bot optionally shows the configured received sticker for 5 seconds without blocking the queue, removes that temporary sticker once the status exists, then edits the status reply as the item moves through preparing, queued, downloading, and completed states. The queued state is shown only when another job is ahead.
If a message contains multiple links, each supported link becomes its own queue item and unsupported links are reported individually.
uv sync --group dev
uv run sync-me-maybe
uv run ruff format .
uv run ruff check .
uv run mypy src