2026-02-26 20:25:09 +00:00
2026-02-26 20:25:09 +00:00
2026-02-26 19:33:05 +00:00
2026-02-26 19:33:05 +00:00
2026-02-26 19:33:05 +00:00
2026-02-26 19:33:05 +00:00
2026-02-26 19:33:05 +00:00
2026-02-26 19:33:05 +00:00
2026-02-26 19:33:05 +00:00
2026-02-26 19:33:05 +00:00
2026-02-26 19:33:05 +00:00

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. Запустите:
docker compose up -d --build

По умолчанию это поднимет только app (ручной режим через Telegram /generate).

Если захотите включить ночной cron, запустите отдельно:

docker compose --profile cron up -d cron
  1. Откройте 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, поэтому он не стартует при обычном:

docker compose up -d --build

Если хотите включить ночной запуск, поднимите его отдельно:

docker compose --profile cron up -d cron

cron по CRON_SCHEDULE вызывает внутренний endpoint:

  • POST /internal/jobs/nightly

Измените время через .env:

CRON_SCHEDULE=15 2 * * *
TZ=Europe/Moscow

Отключить обратно:

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 нагрузки
Description
No description provided
Readme 116 KiB
Languages
Python 98.9%
Dockerfile 0.6%
Shell 0.5%