Files
NaviWatcher/docs/Specification.md
Vladimir Zagainov b21bf07208 fix: address code review findings
- Store cached_at in canonical UTC layout so the cache TTL cutoff comparison is a valid time ordering (previously go-sqlite3 serialized time.Time as RFC3339, making the space-separated cutoff match only by ASCII accident; same-day expired entries were falsely served as fresh).

- Remove stale no-op scanner config keys (ignore_bootlegs, include_compilations) from config.yaml.example and docs; these fields were removed from ScannerConfig but left in configs, silently doing nothing.
2026-07-19 20:01:26 +03:00

11 KiB
Raw Blame History

ЦЕЛЬ ПРОЕКТА

1. Общее описание проекта

NaviWatcher — это автономный сервисный инструмент (демон), предназначенный для автоматического мониторинга музыкальных коллекций, хранящихся в Navidrome (или любом сервере с поддержкой Subsonic API).

Основная задача: сопоставлять текущую медиатеку пользователя с полной дискографией артистов во внешних базах данных (MusicBrainz), находить отсутствующие релизы (альбомы, синглы, сборники) и уведомлять об этом пользователя через Telegram и встроенный веб-интерфейс.

2. Технологический стек

  • Язык программирования: Go 1.25+
  • База данных: SQLite 3 (для хранения кэша, настроек и состояний).
  • HTTP-сервер: Стандартная библиотека Go (net/http) + html/template.
  • Внешние зависимости (библиотеки):
    • github.com/delucks/go-subsonic — клиент Subsonic API (getArtists, getArtist, ping).
    • github.com/mattn/go-sqlite3 — драйвер базы данных.
    • github.com/lithammer/fuzzysearch — библиотека для нечеткого сравнения строк.
    • gopkg.in/yaml.v3 — парсинг конфигурационных файлов.

2.1. Аутентификация в Navidrome (Subsonic API)

NaviWatcher взаимодействует с Navidrome через Subsonic API v1.16.1 (эндпоинты /rest/*). Используется токенная аутентификация — стандартный метод Subsonic API:

  • Формула токена: token = md5(password + salt)
  • Salt генерируется клиентом случайно при каждом запросе.
  • Пароль пользователя не передаётся в открытом виде.
  • Это обеспечивает совместимость с любыми Subsonic-совместимыми серверами (Navidrome, Airsonic, Ampache).

Пример запроса:

/rest/getArtists?u=watcher_service&t=<md5_hash>&s=<random_salt>&v=1.16.1&c=NaviWatcher

Создание сервисного пользователя в Navidrome:

  1. В веб-интерфейсе Navidrome: Settings → Users → Add User.
  2. Задать username и надёжный пароль.
  3. Убедиться, что роль имеет доступ к нужным медиатекам.
  4. Указать эти данные в config.yaml (поля user и password).

3. Архитектура системы

Проект разделен на логические модули:

  1. Navidrome Client: Взаимодействие с Subsonic API v1.16.1 (чтение артистов и локальных альбомов). Аутентификация — токенная (md5), пароль не передаётся в открытом виде. Используются эндпоинты: getArtists, getArtist, getAlbum, ping.
  2. MusicBrainz Provider: Запрос дискографий с механизмом кэширования и соблюдением Rate Limit (1 запрос в секунду).
  3. Scanner Engine: Логика нормализации строк и сравнения списков (Diff).
  4. Database Layer: Персистентное хранение данных и пользовательских фильтров.
  5. Notifier: Планировщик задач и отправка уведомлений в Telegram.
  6. Web UI: Интерфейс для просмотра находок и управления исключениями.

4. Алгоритмы и бизнес-логика

4.1. Работа с внешними данными (MusicBrainz)

Для минимизации дубликатов (разные издания одного альбома) сервис должен работать с сущностью Release Group (RG), а не с конкретными релизами.

  • Кэширование: Данные о дискографии артиста сохраняются в SQLite на 24 часа. Повторные запросы в этот период идут только в БД.

4.2. Фильтрация контента

При получении списка Release Groups из MusicBrainz применяется многоуровневый фильтр:

  1. По статусу: Игнорировать Bootleg, Promotion, Pseudo-Release.
  2. По типу (глобально): Разрешены Album, Single, EP, Compilation.
  3. По типу (персонально для артиста): Возможность отключить Single или Compilation для конкретного исполнителя через Web UI.
  4. По вторичным признакам: Опциональное игнорирование Live, Remix, Soundtrack.

4.3. Сравнение (Fuzzy Matching)

Чтобы избежать ложных срабатываний (например, «The Wall» vs «The Wall (Remastered)»), используется алгоритм:

  1. Нормализация: Приведение к нижнему регистру, удаление спецсимволов, удаление года (20xx), удаление ключевых слов в скобках (Deluxe, Anniversary, Expanded).
  2. Сравнение: Если коэффициент сходства строк выше 0.85 (настраиваемо), альбом считается «уже имеющимся».

4.4. Жизненный цикл уведомлений

  1. Найден новый релиз -> Проверка, нет ли его в таблице ignored_releases или notifications_sent.
  2. Если нет -> Добавление в очередь на отправку.
  3. Раз в сутки -> Отправка сводного сообщения в Telegram -> Пометка в notifications_sent.

5. Модель данных (SQLite)

Таблица artist_settings

Хранит параметры мониторинга для каждого артиста из Navidrome.

  • id: string (MBID или имя)
  • name: string
  • ignore_singles: boolean (default: false)
  • ignore_compilations: boolean (default: false)
  • monitored: boolean (default: true)

Таблица external_releases

Кэш релизов, найденных во внешнем мире.

  • rgid: string (MusicBrainz Release Group ID) — Primary Key.
  • artist_id: string (FK)
  • title: string
  • type: string (album/single/ep)
  • release_date: string
  • is_ignored: boolean (флаг скрытия из списка новинок)
  • cached_at: datetime — время последней синхронизации/кэширования из MusicBrainz; используется для проверки TTL кэша (см. миграцию 005_add_cached_at_to_external_releases). Значение NULL означает отсутствие актуального кэша.

Таблица local_albums

Локальные альбомы, синхронизированные из Navidrome через Subsonic API.

  • id: string — Subsonic album ID, Primary Key.
  • artist_id: string — FK → artist_settings.id.
  • title: string — название альбома.

Решение по хранению локальных альбомов: Для локальных альбомов используется отдельная таблица local_albums (вариант 1 из трёх рассмотренных). Это обеспечивает чистое разделение ответственности: external_releases хранит данные MusicBrainz (Release Groups), а local_albums — данные Navidrome. Смешивание этих сущностей в одной таблице (через колонку source или флаг) усложнило бы запросы и фильтрацию, а также привело бы к неоднородности схемы (разные типы ID, разные наборы полей).

Таблица notifications_sent

  • rgid: string (FK)
  • sent_at: datetime

6. Требования к интерфейсу

6.1. Web UI (Dashboard)

  • Главный экран: Список артистов, у которых есть «Missing Albums».
  • Страница артиста:
    • Список альбомов в Navidrome (Source: Subsonic).
    • Список найденных новинок (Source: MB Cache).
    • Действия: «Игнорировать этот релиз», «Игнорировать все синглы артиста».
  • Страница «Архив»: Просмотр ранее проигнорированных релизов с возможностью их восстановления.

6.2. Telegram

  • Сообщение должно содержать список имен артистов и количество найденных новинок.
  • Ссылка на Web UI для детального просмотра.

7. Конфигурация (config.yaml)

server:
  host: "0.0.0.0"
  port: 8080
  # Basic Auth для доступа к веб-интерфейсу
  username: "admin"
  password: "password123"

navidrome:
  url: "http://localhost:4533"
  user: "watcher_service"
  password: "user_password"
  # Токен и salt генерируются автоматически при каждом запросе (md5(password + salt))

musicbrainz:
  user_agent: "NaviWatcher/1.0 ( mail@example.com )"
  cache_ttl: 24h

telegram:
  enabled: true
  token: "bot_token"
  chat_id: "your_chat_id"
  cron_schedule: "0 10 * * *" # Каждый день в 10:00

scanner:
  fuzzy_threshold: 0.85

8. Программные требования и ограничения

  1. Rate Limiting: MusicBrainz API позволяет делать не более 1 запроса в секунду. Демон должен строго соблюдать этот интервал.
  2. Concurrency: Запросы к Navidrome и MusicBrainz должны выполняться в отдельных горутинах, чтобы веб-интерфейс оставался отзывчивым.
  3. Graceful Shutdown: При получении сигнала завершения (SIGTERM) демон должен корректно закрыть соединение с SQLite.
  4. Static Files: Все HTML-шаблоны должны быть встроены в бинарный файл (использование //go:embed).
  5. Docker: Проект должен быть запущен через docker compose.