# Spotify Daily Vibe Bot (Telegram + Spotify + Docker) Готовый backend-сервис, который: - привязывается к вашему Spotify-аккаунту - читает лайкнутые треки (`Liked Songs`) - учитывает недавние прослушивания (последние дни) - генерирует Spotify-плейлист с похожим вайбом по команде `/generate` - опционально может запускаться по расписанию через `cron` - минимизирует повторы и старается держать `>=80%` новых треков (не лайкнутых и не рекомендованных ранее ботом) - управляется через Telegram - запускается в Docker (`app`, опционально `cron`) ## Что внутри - `FastAPI` backend (OAuth callback + internal job endpoint) - `python-telegram-bot` (polling) - `SQLite` (история рекомендаций, кэш лайкнутых, run log) - `supercronic` в отдельном контейнере для nightly cron trigger (опционально) ## Важный момент по Spotify API Spotify endpoint `/recommendations` может быть ограничен/недоступен для некоторых приложений. В сервисе реализован fallback: - Spotify recommendations (если доступен) - top tracks по артистам из вашего recent listening / liked library - Spotify search по seed-артистам (если recommendations/top-tracks недоступны) - optional Last.fm similarity (очень желательно для лучшего "вайба") Для лучшего качества рекомендаций рекомендуется добавить `LASTFM_API_KEY`. ## Быстрый старт 1. Создайте Telegram-бота через `@BotFather` и получите токен. 2. Создайте Spotify App: https://developer.spotify.com/dashboard 3. В Spotify App добавьте Redirect URI (должен совпасть 1 в 1), например: - `https://your-domain.com/auth/spotify/callback` - или для локальной разработки через tunnel: `https://xxxx.ngrok-free.app/auth/spotify/callback` 4. Скопируйте `.env.example` в `.env` и заполните значения. 5. Запустите: ```bash docker compose up -d --build ``` По умолчанию это поднимет только `app` (ручной режим через Telegram `/generate`). Если захотите включить ночной `cron`, запустите отдельно: ```bash docker compose --profile cron up -d cron ``` 6. Откройте Telegram и напишите боту: - `/start` - `/connect` (получите ссылку на Spotify auth) - после подключения: `/generate` ## Настройка `.env` Минимально обязательные поля: - `TELEGRAM_BOT_TOKEN` - `SPOTIFY_CLIENT_ID` - `SPOTIFY_CLIENT_SECRET` - `SPOTIFY_REDIRECT_URI` - `INTERNAL_JOB_TOKEN` Рекомендуемые: - `LASTFM_API_KEY` (улучшает похожесть треков) - `APP_TIMEZONE` / `TZ` - `SPOTIFY_DEFAULT_MARKET` (двухбуквенный код страны, например `NL`, `DE`, `US`) - `CRON_SCHEDULE` (например `15 2 * * *`, только если включаете `cron`) ## Telegram команды - `/connect` — привязать Spotify - `/status` — статус подключения и последний плейлист - `/generate` — сгенерировать плейлист сейчас - `/latest` — ссылка на последний плейлист - `/setsize 30` — размер плейлиста (5..100) - `/setratio 0.8` — целевая доля новых треков (0.5..1.0) - `/sync` — принудительно обновить лайкнутые треки ## Алгоритм подбора рекомендаций Ниже описан фактический пайплайн генерации плейлиста (как он сейчас работает в коде). ### 1. Подготовка входных данных Перед генерацией бот: - обновляет Spotify access token по refresh token (если нужно) - синхронизирует лайкнутые треки из `Liked Songs` в локальный кэш (`saved_tracks`) - загружает recent listening за окно `RECENT_DAYS_WINDOW` (по умолчанию `5` дней) - загружает историю ранее рекомендованных треков (`recommendation_history`) ### 2. Построение seed-профиля Бот собирает seed'ы из двух источников: recent plays и liked library. - Recent plays: - каждый recent track получает вес с убыванием по позиции (более свежие прослушивания важнее) - накапливаются веса по трекам и артистам - Liked tracks: - берется срез последних лайков (`~120`) - плюс случайная выборка из более старых лайков (для разнообразия) - из них также накапливаются веса по артистам На выходе seed-профиля формируются: - `seed_track_ids` (до ~10 треков) - `seed_artists` (до ~20 артистов) - `seed_artist_names` (для Last.fm и Spotify Search fallback) - `recent_track_meta` (нужно для Last.fm track-similar) ### 3. Сбор кандидатов (candidate pool) Бот собирает общий пул кандидатов из нескольких источников и дедуплицирует их. Источники (по порядку): 1. `Spotify recommendations` - вызывается батчами - соблюдается лимит Spotify: максимум `5` seed'ов на запрос (суммарно track + artist) 2. `Spotify artist top tracks` - по seed-артистам 3. `Spotify search` по seed-артистам (fallback) - используется, если recommendations / top-tracks ограничены или дали мало результатов 4. `Last.fm track similar` -> `Spotify search` - для recent seed-треков 5. `Last.fm artist similar` -> `Spotify search` - для seed-артистов Если Spotify/Last.fm отдают ошибки на отдельных вызовах, бот старается деградировать мягко (использовать другие источники), а не валить весь run сразу. ### 4. Дедупликация кандидатов Кандидаты дедуплицируются: - по `spotify_track_id` - по нормализованной сигнатуре `track_name + artist_names` (на случай дублей / разных версий) Если один и тот же трек найден из нескольких источников: - сохраняется лучший score - источник объединяется (например, `source1+source2`) ### 5. Фильтрация и ранжирование Базовая логика: - сначала исключаются треки, которые уже есть в ваших лайках (`liked_ids`) - если после этого пул пустой, включается fallback: - разрешается использовать already-liked треки (с penalty), чтобы не падать с пустым результатом Дополнительные коррекции score: - penalty за уже рекомендованные раньше ботом (`history_ids`) - penalty за лайкнутые (если включился liked fallback) - небольшой boost за коллаборации / нескольких артистов - небольшой boost за накопленные причины/источники - popularity scoring слегка тяготеет к mid-popularity (не только мейнстрим и не только deep cuts) ### 6. Отбор финального списка (selection) После ранжирования кандидаты делятся на: - `novel` — не были рекомендованы ранее и не в лайках - `reused` — уже были рекомендованы или (fallback) уже лайкнуты Далее бот: - сначала пытается набрать минимум по `min_new_ratio` - соблюдает artist caps (ограничение количества треков одного артиста) - если новых треков недостаточно, ослабляет ограничения - затем дозаполняет повторными кандидатами Результат: - `tracks` — финальный порядок треков - `new_count` / `reused_count` - `notes` — пояснение, если не удалось выдержать target по новым трекам ### 7. Создание плейлиста и запись истории После сборки списка бот: - создает Spotify playlist - добавляет треки - записывает run в `playlist_runs` и `playlist_run_tracks` - обновляет `recommendation_history` - сохраняет `latest_playlist_url` у пользователя ## Как работает анти-повтор Бот хранит: - все треки, которые уже рекомендовал раньше - все ваши лайкнутые треки (кэш обновляется) При сборке нового плейлиста: - сначала исключает лайкнутые треки (если это возможно) - отдает приоритет трекам, которых не было в рекомендациях ранее - если новых треков не хватает, дозаполняет повторами из истории - если кандидаты есть только среди лайкнутых, может использовать liked fallback вместо полного фейла run - пишет статистику в БД (`new / reused`) Если в доступном пуле не хватает новых треков для `80%`, бот сообщит об этом в статусе run. ## Cron (ночной запуск) По умолчанию `cron` отключен (manual-first режим: запускаете `/generate` вручную в Telegram). В `docker-compose.yml` сервис `cron` помечен профилем `cron`, поэтому он не стартует при обычном: ```bash docker compose up -d --build ``` Если хотите включить ночной запуск, поднимите его отдельно: ```bash docker compose --profile cron up -d cron ``` `cron` по `CRON_SCHEDULE` вызывает внутренний endpoint: - `POST /internal/jobs/nightly` Измените время через `.env`: ```env CRON_SCHEDULE=15 2 * * * TZ=Europe/Moscow ``` Отключить обратно: ```bash docker compose stop cron ``` ## Хранилище данных - SQLite БД: `./data/app.db` Эта папка примонтирована как volume, поэтому данные переживают перезапуск контейнеров. ## Проверка работы - `GET /health` должен вернуть `{"ok": true}` - после `/generate` в Telegram появится ссылка на Spotify playlist ## Типичный деплой - VPS + Docker Compose - `APP_BASE_URL` = публичный URL сервиса - `SPOTIFY_REDIRECT_URI` = `${APP_BASE_URL}/auth/spotify/callback` - Telegram работает через polling (webhook не нужен) - `cron` можно не включать совсем, если генерация только вручную ## Архитектура Подробное описание архитектуры приложения, потоков данных и таблиц БД вынесено в `DESIGN.md`. ## Feature Plans Ниже план ближайших улучшений (roadmap), которые хорошо ложатся на текущую архитектуру. - Явный feedback loop: - команды вроде `/ban`, `/unban`, `/prefer` - отдельная blacklist-таблица, чтобы "не понравилось" != "просто не лайкнул" - Настройки anti-repeat: - жесткий запрет повторов на N дней/недель - отдельные правила для liked / previously recommended - 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 сценариев ## Ограничения / улучшения (если захотите дальше) - Персонификация по timezone на пользователя (сейчас cron общий, но user-specific generation поддерживается вручную) - Больше источников похожих треков (например, MusicBrainz/Discogs mapping) - Выделенный Postgres вместо SQLite для multi-user нагрузки