Translate documentation to english

This commit is contained in:
heboba
2026-02-26 20:25:20 +00:00
parent 06add127ff
commit bd23a9da8a
2 changed files with 341 additions and 338 deletions

345
README.md
View File

@@ -1,64 +1,64 @@
# Spotify Daily Vibe Bot (Telegram + Spotify + Docker)
Готовый backend-сервис, который:
Ready-to-run backend service that:
- привязывается к вашему Spotify-аккаунту
- читает лайкнутые треки (`Liked Songs`)
- учитывает недавние прослушивания (последние дни)
- генерирует Spotify-плейлист с похожим вайбом по команде `/generate`
- опционально может запускаться по расписанию через `cron`
- минимизирует повторы и старается держать `>=80%` новых треков (не лайкнутых и не рекомендованных ранее ботом)
- управляется через Telegram
- запускается в Docker (`app`, опционально `cron`)
- connects to your Spotify account
- reads your liked tracks (`Liked Songs`)
- uses your recent listening history
- generates a Spotify playlist with a similar vibe via `/generate`
- can optionally run on a schedule via `cron`
- minimizes repeats and tries to keep `>=80%` of tracks "new" (not liked and not previously recommended by the bot)
- is controlled via Telegram
- runs in Docker (`app`, optional `cron`)
## Что внутри
## What's inside
- `FastAPI` backend (OAuth callback + internal job endpoint)
- `python-telegram-bot` (polling)
- `SQLite` (история рекомендаций, кэш лайкнутых, run log)
- `supercronic` в отдельном контейнере для nightly cron trigger (опционально)
- `SQLite` (recommendation history, liked-track cache, run log)
- `supercronic` in a separate container for nightly cron trigger (optional)
## Важный момент по Spotify API
## Important note about the Spotify API
Spotify endpoint `/recommendations` может быть ограничен/недоступен для некоторых приложений. В сервисе реализован fallback:
Spotify endpoint `/recommendations` may be limited/unavailable for some apps. The service includes fallbacks:
- Spotify recommendations (если доступен)
- top tracks по артистам из вашего recent listening / liked library
- Spotify search по seed-артистам (если recommendations/top-tracks недоступны)
- optional Last.fm similarity (очень желательно для лучшего "вайба")
- Spotify recommendations (if available)
- top tracks by artists from your recent listening / liked library
- Spotify search by seed artists (fallback when recommendations/top-tracks are unavailable)
- optional Last.fm similarity (very helpful for better "vibe" quality)
Для лучшего качества рекомендаций рекомендуется добавить `LASTFM_API_KEY`.
For better recommendation quality, adding `LASTFM_API_KEY` is recommended.
## Быстрый старт
## Quick Start
1. Создайте Telegram-бота через `@BotFather` и получите токен.
2. Создайте Spotify App: https://developer.spotify.com/dashboard
3. В Spotify App добавьте Redirect URI (должен совпасть 1 в 1), например:
1. Create a Telegram bot via `@BotFather` and get a token.
2. Create a Spotify App: https://developer.spotify.com/dashboard
3. Add a Redirect URI in the Spotify App (must match exactly), for example:
- `https://your-domain.com/auth/spotify/callback`
- или для локальной разработки через tunnel: `https://xxxx.ngrok-free.app/auth/spotify/callback`
4. Скопируйте `.env.example` в `.env` и заполните значения.
5. Запустите:
- or for local development via tunnel: `https://xxxx.ngrok-free.app/auth/spotify/callback`
4. Copy `.env.example` to `.env` and fill in the values.
5. Start:
```bash
docker compose up -d --build
```
По умолчанию это поднимет только `app` (ручной режим через Telegram `/generate`).
By default this starts only `app` (manual mode via Telegram `/generate`).
Если захотите включить ночной `cron`, запустите отдельно:
If you want nightly `cron`, start it separately:
```bash
docker compose --profile cron up -d cron
```
6. Откройте Telegram и напишите боту:
6. Open Telegram and message the bot:
- `/start`
- `/connect` (получите ссылку на Spotify auth)
- после подключения: `/generate`
- `/connect` (get the Spotify auth link)
- after connecting: `/generate`
## Настройка `.env`
## `.env` configuration
Минимально обязательные поля:
Minimum required fields:
- `TELEGRAM_BOT_TOKEN`
- `SPOTIFY_CLIENT_ID`
@@ -66,237 +66,238 @@ docker compose --profile cron up -d cron
- `SPOTIFY_REDIRECT_URI`
- `INTERNAL_JOB_TOKEN`
Рекомендуемые:
Recommended:
- `LASTFM_API_KEY` (улучшает похожесть треков)
- `LASTFM_API_KEY` (improves similarity quality)
- `APP_TIMEZONE` / `TZ`
- `SPOTIFY_DEFAULT_MARKET` (двухбуквенный код страны, например `NL`, `DE`, `US`)
- `CRON_SCHEDULE` (например `15 2 * * *`, только если включаете `cron`)
- `SPOTIFY_DEFAULT_MARKET` (two-letter country code, e.g. `NL`, `DE`, `US`)
- `CRON_SCHEDULE` (e.g. `15 2 * * *`, only if you enable `cron`)
## Telegram команды
## Telegram commands
- `/connect` — привязать Spotify
- `/status` — статус подключения и последний плейлист
- `/generate` — сгенерировать плейлист сейчас
- `/latest` — ссылка на последний плейлист
- `/setsize 30` — размер плейлиста (5..100)
- `/setratio 0.8` — целевая доля новых треков (0.5..1.0)
- `/sync` — принудительно обновить лайкнутые треки
- `/connect` - connect Spotify
- `/status` - connection status and latest playlist run
- `/generate` - generate a playlist now
- `/latest` - latest playlist link
- `/setsize 30` - playlist size (5..100)
- `/setratio 0.8` - target new-track ratio (0.5..1.0)
- `/sync` - force sync liked tracks
- `/lang ru|en` - switch bot language
## Алгоритм подбора рекомендаций
## Recommendation Algorithm
Ниже описан фактический пайплайн генерации плейлиста (как он сейчас работает в коде).
This is the actual playlist generation pipeline used by the current code.
### 1. Подготовка входных данных
### 1. Input preparation
Перед генерацией бот:
Before generation, the bot:
- обновляет Spotify access token по refresh token (если нужно)
- синхронизирует лайкнутые треки из `Liked Songs` в локальный кэш (`saved_tracks`)
- загружает recent listening за окно `RECENT_DAYS_WINDOW` (по умолчанию `5` дней)
- загружает историю ранее рекомендованных треков (`recommendation_history`)
- refreshes Spotify access token if needed
- syncs liked tracks from `Liked Songs` into the local cache (`saved_tracks`)
- loads recent listening for the `RECENT_DAYS_WINDOW` period (default `5` days)
- loads history of previously recommended tracks (`recommendation_history`)
### 2. Построение seed-профиля
### 2. Seed profile construction
Бот собирает seed'ы из двух источников: recent plays и liked library.
The bot builds seeds from two sources: recent plays and liked library.
- Recent plays:
- каждый recent track получает вес с убыванием по позиции (более свежие прослушивания важнее)
- накапливаются веса по трекам и артистам
- each track gets a recency-weighted score (newer plays matter more)
- weights are accumulated for both tracks and artists
- Liked tracks:
- берется срез последних лайков (`~120`)
- плюс случайная выборка из более старых лайков (для разнообразия)
- из них также накапливаются веса по артистам
- takes a slice of recent likes (`~120`)
- adds a random sample from older likes (for exploration/diversity)
- accumulates artist weights from this pool as well
На выходе seed-профиля формируются:
Seed profile output includes:
- `seed_track_ids` (до ~10 треков)
- `seed_artists` (до ~20 артистов)
- `seed_artist_names` (для Last.fm и Spotify Search fallback)
- `recent_track_meta` (нужно для Last.fm track-similar)
- `seed_track_ids` (up to ~10 tracks)
- `seed_artists` (up to ~20 artists)
- `seed_artist_names` (used by Last.fm and Spotify Search fallback)
- `recent_track_meta` (used for Last.fm track-similar lookups)
### 3. Сбор кандидатов (candidate pool)
### 3. Candidate collection (candidate pool)
Бот собирает общий пул кандидатов из нескольких источников и дедуплицирует их.
The bot builds a shared candidate pool from multiple sources and deduplicates results.
Источники (по порядку):
Sources (in order):
1. `Spotify recommendations`
- вызывается батчами
- соблюдается лимит Spotify: максимум `5` seed'ов на запрос (суммарно track + artist)
- requested in batches
- respects Spotify limit: max `5` seeds per request (track + artist combined)
2. `Spotify artist top tracks`
- по seed-артистам
3. `Spotify search` по seed-артистам (fallback)
- используется, если recommendations / top-tracks ограничены или дали мало результатов
- by seed artists
3. `Spotify search` by seed artists (fallback)
- used when recommendations / top-tracks are restricted or return too few results
4. `Last.fm track similar` -> `Spotify search`
- для recent seed-треков
- for recent seed tracks
5. `Last.fm artist similar` -> `Spotify search`
- для seed-артистов
- for seed artists
Если Spotify/Last.fm отдают ошибки на отдельных вызовах, бот старается деградировать мягко (использовать другие источники), а не валить весь run сразу.
If Spotify/Last.fm fails on individual calls, the bot tries to degrade gracefully (use other sources) instead of failing the whole run immediately.
### 4. Дедупликация кандидатов
### 4. Candidate deduplication
Кандидаты дедуплицируются:
Candidates are deduplicated:
- по `spotify_track_id`
- по нормализованной сигнатуре `track_name + artist_names` (на случай дублей / разных версий)
- by `spotify_track_id`
- by normalized signature `track_name + artist_names` (to catch duplicates / alternate versions)
Если один и тот же трек найден из нескольких источников:
If the same track is found via multiple sources:
- сохраняется лучший score
- источник объединяется (например, `source1+source2`)
- the best score is kept
- the source field is merged (e.g. `source1+source2`)
### 5. Фильтрация и ранжирование
### 5. Filtering and ranking
Базовая логика:
Base logic:
- сначала исключаются треки, которые уже есть в ваших лайках (`liked_ids`)
- если после этого пул пустой, включается fallback:
- разрешается использовать already-liked треки (с penalty), чтобы не падать с пустым результатом
- first, tracks already in your likes (`liked_ids`) are excluded
- if that leaves an empty pool, a fallback is enabled:
- already-liked tracks may be used (with a penalty) so the run does not fail with an empty result
Дополнительные коррекции score:
Additional score adjustments:
- penalty за уже рекомендованные раньше ботом (`history_ids`)
- penalty за лайкнутые (если включился liked fallback)
- небольшой boost за коллаборации / нескольких артистов
- небольшой boost за накопленные причины/источники
- popularity scoring слегка тяготеет к mid-popularity (не только мейнстрим и не только deep cuts)
- penalty for tracks previously recommended by the bot (`history_ids`)
- penalty for liked tracks (only if liked fallback is active)
- small boost for collaborations / multiple artists
- small boost for tracks with multiple source/reason signals
- popularity scoring slightly favors mid-popularity tracks (not only mainstream and not only obscure tracks)
### 6. Отбор финального списка (selection)
### 6. Final selection
После ранжирования кандидаты делятся на:
After ranking, candidates are split into:
- `novel` — не были рекомендованы ранее и не в лайках
- `reused` — уже были рекомендованы или (fallback) уже лайкнуты
- `novel` - not previously recommended and not in likes
- `reused` - previously recommended or (fallback case) already liked
Далее бот:
Then the bot:
- сначала пытается набрать минимум по `min_new_ratio`
- соблюдает artist caps (ограничение количества треков одного артиста)
- если новых треков недостаточно, ослабляет ограничения
- затем дозаполняет повторными кандидатами
- first tries to satisfy `min_new_ratio`
- enforces artist caps (limit tracks per artist)
- relaxes caps if there are not enough new tracks
- fills the remainder with reused candidates
Результат:
Result includes:
- `tracks` — финальный порядок треков
- `tracks` - final ordered playlist tracks
- `new_count` / `reused_count`
- `notes` — пояснение, если не удалось выдержать target по новым трекам
- `notes` - explanation if the target new ratio could not be met
### 7. Создание плейлиста и запись истории
### 7. Playlist creation and history persistence
После сборки списка бот:
After the final track list is selected, the bot:
- создает Spotify playlist
- добавляет треки
- записывает run в `playlist_runs` и `playlist_run_tracks`
- обновляет `recommendation_history`
- сохраняет `latest_playlist_url` у пользователя
- creates a Spotify playlist
- adds tracks to it
- writes the run to `playlist_runs` and `playlist_run_tracks`
- updates `recommendation_history`
- stores `latest_playlist_url` for the user
## Как работает анти-повтор
## Anti-repeat behavior
Бот хранит:
The bot stores:
- все треки, которые уже рекомендовал раньше
- все ваши лайкнутые треки (кэш обновляется)
- all tracks it has recommended before
- all your liked tracks (cached and refreshed)
При сборке нового плейлиста:
When building a new playlist:
- сначала исключает лайкнутые треки (если это возможно)
- отдает приоритет трекам, которых не было в рекомендациях ранее
- если новых треков не хватает, дозаполняет повторами из истории
- если кандидаты есть только среди лайкнутых, может использовать liked fallback вместо полного фейла run
- пишет статистику в БД (`new / reused`)
- it first excludes liked tracks (when possible)
- prioritizes tracks that have not been recommended before
- fills with history repeats only if there are not enough new tracks
- may use a liked-track fallback instead of failing the run if all candidates are already liked
- stores `new / reused` stats in the DB
Если в доступном пуле не хватает новых треков для `80%`, бот сообщит об этом в статусе run.
If there are not enough new tracks to satisfy the `80%` target, the run status includes a note explaining that.
## Cron (ночной запуск)
## Cron (nightly run)
По умолчанию `cron` отключен (manual-first режим: запускаете `/generate` вручную в Telegram).
`cron` is disabled by default (manual-first mode: run `/generate` manually in Telegram).
В `docker-compose.yml` сервис `cron` помечен профилем `cron`, поэтому он не стартует при обычном:
In `docker-compose.yml`, the `cron` service is under profile `cron`, so it does not start with a normal:
```bash
docker compose up -d --build
```
Если хотите включить ночной запуск, поднимите его отдельно:
To enable nightly runs:
```bash
docker compose --profile cron up -d cron
```
`cron` по `CRON_SCHEDULE` вызывает внутренний endpoint:
`cron` calls the internal endpoint on schedule:
- `POST /internal/jobs/nightly`
Измените время через `.env`:
Change time via `.env`:
```env
CRON_SCHEDULE=15 2 * * *
TZ=Europe/Moscow
TZ=Europe/Amsterdam
```
Отключить обратно:
Disable again:
```bash
docker compose stop cron
```
## Хранилище данных
## Data storage
- SQLite БД: `./data/app.db`
- SQLite DB: `./data/app.db`
Эта папка примонтирована как volume, поэтому данные переживают перезапуск контейнеров.
This folder is mounted as a Docker volume, so data persists across container restarts.
## Проверка работы
## Health check / verification
- `GET /health` должен вернуть `{"ok": true}`
- после `/generate` в Telegram появится ссылка на Spotify playlist
- `GET /health` should return `{"ok": true}`
- after `/generate`, Telegram should send a Spotify playlist link
## Типичный деплой
## Typical deployment
- VPS + Docker Compose
- `APP_BASE_URL` = публичный URL сервиса
- `APP_BASE_URL` = public service URL
- `SPOTIFY_REDIRECT_URI` = `${APP_BASE_URL}/auth/spotify/callback`
- Telegram работает через polling (webhook не нужен)
- `cron` можно не включать совсем, если генерация только вручную
- Telegram runs via polling (no webhook required)
- `cron` can remain disabled if you only want manual generation
## Архитектура
## Architecture
Подробное описание архитектуры приложения, потоков данных и таблиц БД вынесено в `DESIGN.md`.
Detailed architecture, data flow, and DB table docs are in `DESIGN.md`.
## Feature Plans
Ниже план ближайших улучшений (roadmap), которые хорошо ложатся на текущую архитектуру.
Roadmap items that fit the current architecture well:
- Явный feedback loop:
- команды вроде `/ban`, `/unban`, `/prefer`
- отдельная blacklist-таблица, чтобы "не понравилось" != "просто не лайкнул"
- Настройки anti-repeat:
- жесткий запрет повторов на N дней/недель
- отдельные правила для liked / previously recommended
- Explicit feedback loop:
- commands like `/ban`, `/unban`, `/prefer`
- separate blacklist table so "didn't like it" != "just didn't save it"
- Anti-repeat controls:
- hard no-repeat window (N days/weeks)
- separate rules for liked / previously recommended tracks
- Explainability / debug:
- why-this-track (показать источник, score, причины попадания)
- dry-run endpoint/команда без создания плейлиста
- Тонкая настройка алгоритма:
- веса источников (Spotify / Last.fm / search fallback)
- режимы генерации (explore / familiar / mixed)
- Улучшение источников кандидатов:
- дополнительные музыкальные источники / метаданные
- более умная работа с жанрами/артист-кластерами
- Персональный scheduler:
- per-user timezone и per-user cron schedule
- выбор дней недели / времени генерации
- Наблюдаемость:
- структурированные логи по source coverage и причинам фильтрации
- простые метрики по ошибкам Spotify/Last.fm и latency
- Хранилище / масштабирование:
- миграции (Alembic)
- Postgres вместо SQLite для multi-user сценариев
- why-this-track (source, score, reasons)
- dry-run endpoint/command without creating a playlist
- Fine-tuning the algorithm:
- source weights (Spotify / Last.fm / search fallback)
- generation modes (explore / familiar / mixed)
- Better candidate sources:
- additional music metadata sources
- smarter genre/artist clustering
- Personal scheduler:
- per-user timezone and per-user cron schedule
- weekday / time selection
- Observability:
- structured logs for source coverage and filtering reasons
- basic metrics for Spotify/Last.fm errors and latency
- Storage / scaling:
- migrations (Alembic)
- Postgres instead of SQLite for multi-user usage
## Ограничения / улучшения (если захотите дальше)
## Limitations / future improvements
- Персонификация по timezone на пользователя (сейчас cron общий, но user-specific generation поддерживается вручную)
- Больше источников похожих треков (например, MusicBrainz/Discogs mapping)
- Выделенный Postgres вместо SQLite для multi-user нагрузки
- Per-user timezone support is only partially used today (cron is global, though manual per-user generation is supported)
- More candidate sources could improve quality (e.g. MusicBrainz/Discogs mapping)
- Postgres would be better than SQLite for higher multi-user load