A Telegram bot that builds a music playlist from a few of your favorite tracks. Send it 1–5 songs, and it returns a curated playlist of similar tracks powered by the Last.fm API.
Try it: @bremcm_playlist_bot»
Built in Go as a hands-on backend/DevOps project: clean architecture, concurrency, a persistent database, containerization, CI, and a live deployment on a VPS.
- Send tracks in
Artist — Titleformat, one per message (1 to 5 tracks). - The bot fetches similar tracks for each seed from Last.fm and ranks them: a track similar to several of your seeds ranks higher.
- The playlist size scales with how many tracks you send: 1→10, 2→15, 3→20, 4–5→30.
- Typo correction: minor misspellings are auto-corrected via Last.fm search
(e.g.
Madona — Frozn→Madonna — Frozen). - Suggestions: for bigger mismatches, the bot offers options as inline buttons ("did you mean…?").
- Honest feedback: if Last.fm has no similar tracks for a song, you're told right away, not after the fact.
- Duplicate seeds and unrecognizable input are filtered out.
/historyshows your past requests, stored in PostgreSQL.
- Language: Go
- Bot API: Telegram (long polling)
- Data source: Last.fm API (
track.getsimilar,track.search) - Database: PostgreSQL (request history), schema embedded via
//go:embed - Containerization: Docker (multi-stage build, ~9 MB final image), Docker Compose
- CI: GitHub Actions (build + tests with the race detector)
- Deployment: VPS running Ubuntu, containers auto-restart on reboot
The project is split into small, single-responsibility packages. The core logic
(recommender) is fully decoupled from the outside world and depends only on
interfaces it declares itself — so it can be tested without any network access.
cmd/bot entry point: wires everything together
internal/
models shared domain types (Track, Candidate)
lastfm Last.fm HTTP client (GetSimilar, Search)
recommender ranking logic — pure, tested, no I/O
telegram bot handlers, dialog state, inline buttons
session per-user in-memory state (accumulated tracks)
storage PostgreSQL access (request history)
Key design choices:
- Interfaces declared by the consumer. For example,
recommenderdefines theSimilarFetcherinterface it needs; the Last.fm client happens to satisfy it. Dependencies are wired together only inmain. - Pure ranking core. The ranking function takes data in and returns data out — no HTTP, no database — which makes it trivial to unit-test.
- Concurrency-safe state. Shared state is guarded with mutexes; correctness was
verified with Go's race detector (
go test -race).
You'll need Docker and Docker Compose installed.
-
Create a
.envfile in the project root:LASTFM_API_KEY=your_lastfm_api_key TELEGRAM_BOT_TOKEN=your_telegram_bot_token DATABASE_URL=postgres://playlist:playlist@db:5432/playlist?sslmode=disable- Get a Last.fm API key at https://www.last.fm/api/account/create
- Create a bot and get a token from @BotFather
-
Start everything:
docker compose up --build
This launches PostgreSQL and the bot together. The database schema is applied automatically on startup.
-
Open Telegram, find your bot, and send
/start.
go test -race ./...The bot runs in Docker on a VPS. Because the app is fully containerized, deployment is the same command as local:
git pull
docker compose up -d --buildrestart: unless-stopped in the compose file, combined with Docker starting on
boot, means the bot survives crashes and server reboots.
| Command | Description |
|---|---|
/start |
Show instructions |
/done |
Build the playlist from your tracks |
/history |
Show your past requests |
MIT