Files
NaviWatcher/README.md
Vladimir Zagainov 389d177d85 fix: address code review findings
- Fix duplicate Telegram notifications: SyncArtistDiscography no longer wipes
  notifications_sent for the whole artist on every cache-miss re-sync; only
  markers for releases that disappear are pruned (FK-safe via INSERT OR REPLACE
  + rgid NOT IN (...)).
- Cache empty MusicBrainz discographies via a new artist_settings.last_synced
  column (migration 009) so zero-release artists honor the TTL instead of being
  re-fetched every cycle.
- Wire the Web UI server and Telegram notifier scheduler into main.run/NewApp.
- Guard startPeriodicSync against overlapping syncs with a done-channel slot.
- Add server.public_url config; NewServerWithConfig derives reachable links
  and no longer advertises the 0.0.0.0 bind address.
- Web handlers: use scanner.ScanArtist per artist, drop always-false
  releaseIgnored lookup and dead endsWith, thread configured threshold.
- Limit :memory: DB pool to one connection so migrations and queries share the
  same in-memory store.
2026-07-19 23:38:33 +03:00

16 KiB
Raw Permalink Blame History

NaviWatcher

Language: English | Русский


English

NaviWatcher is an autonomous service daemon that monitors your Navidrome music collection, compares it against artist discographies from MusicBrainz, and notifies you about missing releases via Telegram and a built-in web interface.

How It Works

  1. Scans your Navidrome library via Subsonic API to get the list of artists and albums.
  2. Fetches full artist discographies from MusicBrainz (using Release Groups to avoid duplicate editions).
  3. Compares local collection with external data using fuzzy matching (configurable threshold, default 0.85).
  4. Syncs automatically on a periodic loop (sync.interval, default 6h): Navidrome artist/album pull → lazy MusicBrainz ID resolution → discography cache → re-scan, so the dashboard and digests always reflect current state.
  5. Notifies you about missing albums/singles/EPs through daily Telegram digests and a web dashboard.

How It Works Now

On startup NaviWatcher opens (or auto-migrates) a local SQLite database at naviwatcher.db in the working directory, performs one immediate sync+scan, then runs a ticker-driven periodic sync+scan goroutine until SIGTERM. The Web UI serves a basic-auth-protected dashboard; the Notifier runs a cron scheduler emitting a daily digest of newly-found missing releases (tracked via notifications_sent). The MusicBrainz ID for each artist is resolved lazily on first sync and cached on the artist_settings row.

Features

  • Subsonic API compatible — works with Navidrome, Airsonic, Ampache, and other Subsonic-compatible servers.
  • Fuzzy matching — smart string normalization (ignores remastered/deluxe/anniversary editions, year suffixes, special characters).
  • Per-artist filters — opt out of Singles and Compilations per artist (via artist_settings); type filtering includes only Album/Single/EP primary types (plus release groups whose secondary types include Single/EP/Compilation).
  • MusicBrainz caching — 24-hour TTL cache to minimize API calls and respect rate limits (1 req/sec).
  • Telegram notifications — daily summary messages with links to the web UI, sent on the configured telegram.cron_schedule.
  • Web dashboard — browse missing albums, ignore releases, manage artist-specific settings, with an archive of ignored releases.
  • Single binary deployment — all HTML templates embedded via //go:embed.
  • Docker support — ready for docker compose deployment.

Technology Stack

  • Language: Go 1.25+
  • Database: SQLite 3
  • HTTP Server: Go standard library (net/http + html/template)
  • Key dependencies:
    • github.com/mattn/go-sqlite3 — SQLite driver
    • github.com/lithammer/fuzzysearch — fuzzy string matching
    • gopkg.in/yaml.v3 — configuration parsing

Quick Start

  1. Create a service user in Navidrome:

    • Go to Settings → Users → Add User
    • Set a username and password
    • Ensure the user has access to your music libraries
  2. Configure NaviWatcher:

    cp config.yaml.example config.yaml
    # Edit config.yaml with your settings
    
  3. Build and run:

    go build -o naviwatcher
    ./naviwatcher
    
  4. Or use Docker:

    docker compose up -d
    
  5. Access the web UI at http://localhost:8080

Note (current status): On startup the service opens a local SQLite database at naviwatcher.db in the working directory (existing databases are auto-migrated), performs one immediate sync+scan, then runs a periodic sync+scan loop (sync.interval, default 6h) until shutdown. musicbrainz.user_agent is required and validated at startup. The Web UI (basic-auth protected) and Telegram notifier (cron-scheduled) are wired in; set telegram.enabled to activate digests.

Configuration

server:
  host: "0.0.0.0"
  port: 8080
  username: "admin"
  password: "CHANGE_ME"
  # Externally-reachable base URL for links in Telegram digests (e.g. behind a
  # reverse proxy). If omitted, links are derived from host:port — but when host
  # is 0.0.0.0 (the unspecified bind address) no link is emitted, since it is
  # not reachable from outside the host.
  public_url: "https://naviwatcher.example.com"

navidrome:
  url: "http://localhost:4533"
  user: "watcher_service"
  password: "CHANGE_ME"

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 * * *"

scanner:
  fuzzy_threshold: 0.85

# Periodic sync+scan loop frequency (Go duration, e.g. "6h", "30m"); defaults to 6h.
sync:
  interval: 6h

See docs/Specification.md for the full configuration reference and architecture details.

Architecture

Module Purpose
Navidrome Client Subsonic API v1.16.1 communication (token-based auth)
MusicBrainz Provider Discography fetching with rate limiting and caching
Scanner Engine String normalization and fuzzy comparison
Database Layer SQLite persistence: artist_settings, local_albums (Navidrome sync), external_releases (MusicBrainz cache), notifications_sent
Notifier Scheduled Telegram notifications
Web UI Dashboard for browsing and managing missing releases

Implementation Status

  • Scanner Engine — implemented. The missing-release detection core is complete: string normalization lives in internal/normalize, similarity scoring and the diff engine (FindMissingReleases, ScanArtist, ScanAll) in internal/scanner. It uses the configurable scanner.fuzzy_threshold (default 0.85), normalizes titles (ignoring (Remastered)/year/special-char variants), and skips releases marked ignored.
  • Sync pipeline — implemented. SyncAll pulls artists/albums from Navidrome into the DB, resolves each artist's MusicBrainz ID lazily (cached on artist_settings.mbid), syncs the MusicBrainz discography, then re-runs the scanner. Wired into main.run() as an immediate + periodic (sync.interval) loop.
  • Notifier — implemented. A Sender interface with a Telegram implementation, FormatDigest for daily summaries, and a cron scheduler honoring telegram.cron_schedule (no-op when enabled=false); sent-tracking via notifications_sent.
  • Web UI — implemented. Basic-auth-protected net/http server with //go:embed templates: dashboard with missing-release counts, artist detail with ignore actions, and an archive of ignored releases with restore.

License

WTFPL — Do What The Fuck You Want To Public License


Русский

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

Как это работает

  1. Сканирует библиотеку Navidrome через Subsonic API — получает список артистов и альбомов.
  2. Загружает полные дискографии артистов из MusicBrainz (использует Release Groups, чтобы избежать дубликатов изданий).
  3. Сравнивает локальную коллекцию с внешними данными через нечёткое сравнение строк (настраиваемый порог, по умолчанию 0.85).
  4. Синхронизируется автоматически по периодическому циклу (sync.interval, по умолчанию 6h): выгрузка артистов/альбомов из Navidrome → ленивое разрешение MusicBrainz ID → кэш дискографии → повторное сканирование, чтобы панель и дайджесты всегда отражали текущее состояние.
  5. Уведомляет об отсутствующих альбомах/синглах/EP через ежедневные дайджесты в Telegram и веб-панель.

Как это работает сейчас

При запуске NaviWatcher открывает (или автомигрирует) локальную SQLite-БД naviwatcher.db в рабочей директории, выполняет одну немедленную синхронизацию+сканирование, затем запускает управляемый тикером периодический цикл до получения SIGTERM. Веб-интерфейс — это панель под basic-auth; нотификатор запускает cron-планировщик, отправляющий ежедневный дайджест новых отсутствующих релизов (отслеживается через notifications_sent). MusicBrainz ID каждого артиста разрешается лениво при первой синхронизации и кэшируется в строке artist_settings.

Возможности

  • Совместим с Subsonic API — работает с Navidrome, Airsonic, Ampache и другими Subsonic-совместимыми серверами.
  • Нечёткое сравнение — умная нормализация строк (игнорирует ремастеры, deluxe/anniversary-издания, год в скобках, спецсимволы).
  • Фильтры по артистам — отключение синглов и компиляций для конкретного артиста (через artist_settings); фильтрация по типам включает только основные типы Album/Single/EP (а также группы релизов, чьи вторичные типы содержат Single/EP/Compilation).
  • Кэширование MusicBrainz — TTL 24 часа для минимизации запросов и соблюдения лимитов (1 запрос/сек).
  • Уведомления в Telegram — ежедневные сводки со ссылками на веб-интерфейс, отправляемые по расписанию telegram.cron_schedule.
  • Веб-панель — просмотр отсутствующих альбомов, игнорирование релизов, управление настройками артистов, архив проигнорированных релизов.
  • Один бинарный файл — все HTML-шаблоны встроены через //go:embed.
  • Поддержка Docker — готов к развёртыванию через docker compose.

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

  • Язык: Go 1.21+
  • База данных: SQLite 3
  • HTTP-сервер: стандартная библиотека Go (net/http + html/template)
  • Ключевые зависимости:
    • github.com/mattn/go-sqlite3 — драйвер SQLite
    • github.com/lithammer/fuzzysearch — нечёткое сравнение строк
    • gopkg.in/yaml.v3 — парсинг конфигурации

Быстрый старт

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

    • Перейдите в Settings → Users → Add User
    • Задайте имя пользователя и пароль
    • Убедитесь, что у пользователя есть доступ к медиатекам
  2. Настройте NaviWatcher:

    cp config.yaml.example config.yaml
    # Отредактируйте config.yaml
    
  3. Соберите и запустите:

    go build -o naviwatcher
    ./naviwatcher
    
  4. Или используйте Docker:

    docker compose up -d
    
  5. Откройте веб-интерфейс по адресу http://localhost:8080

Конфигурация

server:
  host: "0.0.0.0"
  port: 8080
  username: "admin"
  password: "CHANGE_ME"
  # Внешний базовый URL для ссылок в дайджестах Telegram (напр. за обратным прокси).
  # Если не задан, ссылки строятся из host:port — но при host 0.0.0.0 (несpecificированный
  # адрес привязки) ссылка не генерируется, так как недоступна снаружи хоста.
  public_url: "https://naviwatcher.example.com"

navidrome:
  url: "http://localhost:4533"
  user: "watcher_service"
  password: "CHANGE_ME"

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 * * *"

scanner:
  fuzzy_threshold: 0.85

# Частота периодического цикла синхронизации+сканирования (длительность Go, напр. "6h", "30m"); по умолчанию 6h.
sync:
  interval: 6h

Полную справку по конфигурации и архитектуру см. в docs/Specification.md.

Архитектура

Модуль Назначение
Navidrome Client Взаимодействие с Subsonic API v1.16.1 (токенная аутентификация)
MusicBrainz Provider Загрузка дискографий с кэшированием и rate limiting
Scanner Engine Нормализация строк и нечёткое сравнение
Database Layer SQLite: artist_settings, local_albums (синхронизация из Navidrome), external_releases (кэш MusicBrainz), notifications_sent
Notifier Планировщик уведомлений в Telegram
Web UI Панель управления отсутствющими релизами

Статус реализации

  • Scanner Engine — реализован. Ядро поиска отсутствующих релизов готово: нормализация строк в internal/normalize, оценка схожести и движок сравнения (FindMissingReleases, ScanArtist, ScanAll) в internal/scanner. Используется настраиваемый scanner.fuzzy_threshold (по умолчанию 0.85), игнорируются варианты (Remastered)/год/спецсимволы, пропускаются отмеченные как игнорируемые.
  • Sync pipeline — реализован. SyncAll выгружает артистов/альбомы из Navidrome в БД, лениво разрешает MusicBrainz ID каждого артиста (кэшируется в artist_settings.mbid), синхронизирует дискографию MusicBrainz, затем повторно запускает сканер. Подключён в main.run() как немедленный + периодический (sync.interval) цикл.
  • Notifier — реализован. Интерфейс Sender с Telegram-реализацией, FormatDigest для ежедневных сводок и cron-планировщик по telegram.cron_schedule (no-op при enabled=false); отслеживание отправок через notifications_sent.
  • Web UI — реализован. Защищённый basic-auth net/http сервер с //go:embed шаблонами: панель со счётчиками отсутствующих релизов, страница артиста с действиями игнорирования и архив проигнорированных релизов с восстановлением.

Лицензия

WTFPL — Do What The Fuck You Want To Public License