From 001f9ce691fefcc76ce311c58619433bb559087b Mon Sep 17 00:00:00 2001 From: Vladimir Zagainov Date: Tue, 19 May 2026 17:42:12 +0300 Subject: [PATCH] Initial commit --- CLAUDE.md | 170 +++++++++++++++++++++++++++++++++ License.md | 13 +++ README.md | 217 ++++++++++++++++++++++++++++++++++++++++++ docs/Specification.md | 150 +++++++++++++++++++++++++++++ 4 files changed, 550 insertions(+) create mode 100644 CLAUDE.md create mode 100644 License.md create mode 100644 README.md create mode 100644 docs/Specification.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..6966c84 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,170 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Development Commands + +### Building +```bash +# Build the application +go build -o naviwatcher + +# Build with race detector +go build -race -o naviwatcher + +# Cross-platform build examples +GOOS=linux GOARCH=amd64 go build -o naviwatcher-linux +GOOS=darwin GOARCH=amd64 go build -o naviwatcher-mac +GOOS=windows GOARCH=amd64 go build -o naviwatcher.exe +``` + +### Running +```bash +# Run the application +./naviwatcher + +# Run with specific config file +./naviwatcher -config=/path/to/config.yaml + +# Run in development mode (if implemented) +go run main.go +``` + +### Testing +```bash +# Run all tests +go test ./... + +# Run tests with coverage +go test ./... -cover + +# Run a specific test package +go test ./internal/scanner + +# Run tests verbose +go test ./... -v +``` + +### Dependency Management +```bash +# Add a new dependency +go get github.com/example/package@v1.2.3 + +# Update dependencies +go get -u ./... + +# Tidy up dependencies +go mod tidy + +# Vendor dependencies (if needed) +go mod vendor +``` + +### Linting and Formatting +```bash +# Format code +go fmt ./... + +# Vet for potential issues +go vet ./... + +# Static analysis (if golangci-lint is installed) +golangci-lint run +``` + +### Docker +```bash +# Build Docker image +docker build -t naviwatcher . + +# Run with docker compose +docker compose up + +# Run in background +docker compose up -d + +# View logs +docker compose logs -f +``` + +## Code Architecture + +Based on the specification (docs/Specification.md), the application follows a modular architecture: + +### Core Modules +1. **Navidrome Client** (`internal/navidrome/` or similar) + - Handles Subsonic API communication + - Fetches artist and album data from Navidrome instance + - Implements authentication and error handling + +2. **MusicBrainz Provider** (`internal/musicbrainz/` or similar) + - Interfaces with MusicBrainz API + - Implements rate limiting (1 request/second) + - Manages caching of artist discographies (24-hour TTL) + - Works with Release Group (RG) entities to minimize duplicates + +3. **Scanner Engine** (`internal/scanner/` or similar) + - Normalizes string comparisons for fuzzy matching + - Implements the comparison algorithm (0.85 similarity threshold) + - Handles removal of special characters, years, and bracketed keywords + - Compares local albums vs. external discographies + +4. **Database Layer** (`internal/database/` or similar) + - SQLite 3 integration via github.com/mattn/go-sqlite3 + - Manages schema migrations + - Handles tables: + - `artist_settings`: Artist monitoring configuration + - `external_releases`: Cached MusicBrainz data + - `notifications_sent`: Sent notification tracking + +5. **Notifier** (`internal/notifier/` or similar) + - Telegram bot integration + - Scheduled task execution (cron-based) + - Batch notification sending + - Message formatting with Web UI links + +6. **Web UI** (`internal/web/` or similar) + - Go standard library net/http + html/template + - Embedded templates using //go:embed + - Basic authentication protection + - Dashboard for viewing missing albums + - Artist detail views with ignore functionality + - Archive view for previously ignored releases + +### Key Technical Requirements +- **Concurrency**: Separate goroutines for external API calls to keep web UI responsive +- **Rate Limiting**: Strict adherence to MusicBrainz 1 request/second limit +- **Graceful Shutdown**: Proper SIGTERM handling to close database connections +- **Configuration**: YAML-based config (config.yaml) with environment-specific overrides +- **Static Assets**: HTML templates embedded via //go:embed for single-binary deployment +- **Data Modeling**: Focus on Release Group entities rather than specific releases + +### Common Development Patterns +- Use context.Context for cancellation and timeouts +- Implement proper error handling with logging +- Follow Go idioms and conventions +- Write table-driven tests for complex logic +- Use dependency injection for testability +- Apply the specified fuzzy matching algorithm consistently + +## Configuration Reference +See docs/Specification.md Section 7 for full config.yaml structure including: +- Server settings (host, port, basic auth) +- Navidrome connection details +- MusicBrainz API configuration +- Telegram notification settings +- Scanner parameters (fuzzy threshold, filters) + +## Database Schema +See docs/Specification.md Section 5 for complete SQLite schema including: +- artist_settings table columns and defaults +- external_releases table structure +- notifications_sent table for tracking + +## Getting Started +1. Copy config.yaml.example to config.yaml and fill in your values +2. Ensure Navidrome instance is running and accessible +3. Set up Telegram bot and obtain token/chat ID +4. Initialize database: ./naviwatcher (will create tables on first run) +5. Start the service: ./naviwatcher +6. Access Web UI at http://localhost:8080 (or configured host/port) \ No newline at end of file diff --git a/License.md b/License.md new file mode 100644 index 0000000..860e2a1 --- /dev/null +++ b/License.md @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, May 2026 + + Copyright (C) 2026 Zagainov Vladimir + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/README.md b/README.md new file mode 100644 index 0000000..cc2d0a4 --- /dev/null +++ b/README.md @@ -0,0 +1,217 @@ +# NaviWatcher + +> **Language:** [English](#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. **Notifies** you about missing albums/singles/EPs through daily Telegram digests and a web dashboard. + +### 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). +- **Configurable filters** — ignore bootlegs, singles, compilations, live albums, remixes, soundtracks per artist or globally. +- **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. +- **Web dashboard** — browse missing albums, ignore releases, manage artist-specific settings. +- **Single binary deployment** — all HTML templates embedded via `//go:embed`. +- **Docker support** — ready for `docker compose` deployment. + +### Technology Stack + +- **Language:** Go 1.21+ +- **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:** + ```bash + cp config.yaml.example config.yaml + # Edit config.yaml with your settings + ``` + +3. **Build and run:** + ```bash + go build -o naviwatcher + ./naviwatcher + ``` + +4. **Or use Docker:** + ```bash + docker compose up -d + ``` + +5. **Access the web UI** at `http://localhost:8080` + +### Configuration + +```yaml +server: + host: "0.0.0.0" + port: 8080 + username: "admin" + password: "password123" + +navidrome: + url: "http://localhost:4533" + user: "watcher_service" + password: "user_password" + +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 + ignore_bootlegs: true + include_compilations: true +``` + +See [docs/Specification.md](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 for settings, cache, and state | +| **Notifier** | Scheduled Telegram notifications | +| **Web UI** | Dashboard for browsing and managing missing releases | + +### License + +[WTFPL](License.md) — 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. **Уведомляет** об отсутствующих альбомах/синглах/EP через ежедневные дайджесты в Telegram и веб-панель. + +### Возможности + +- **Совместим с Subsonic API** — работает с Navidrome, Airsonic, Ampache и другими Subsonic-совместимыми серверами. +- **Нечёткое сравнение** — умная нормализация строк (игнорирует ремастеры, deluxe/anniversary-издания, год в скобках, спецсимволы). +- **Гибкие фильтры** — игнорирование бутлегов, синглов, компиляций, лайвов, ремиксаундов — глобально или для конкретного артиста. +- **Кэширование MusicBrainz** — TTL 24 часа для минимизации запросов и соблюдения лимитов (1 запрос/сек). +- **Уведомления в Telegram** — ежедневные сводки со ссылками на веб-интерфейс. +- **Веб-панель** — просмотр отсутствующих альбомов, игнорирование релизов, управление настройками артистов. +- **Один бинарный файл** — все 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:** + ```bash + cp config.yaml.example config.yaml + # Отредактируйте config.yaml + ``` + +3. **Соберите и запустите:** + ```bash + go build -o naviwatcher + ./naviwatcher + ``` + +4. **Или используйте Docker:** + ```bash + docker compose up -d + ``` + +5. **Откройте веб-интерфейс** по адресу `http://localhost:8080` + +### Конфигурация + +```yaml +server: + host: "0.0.0.0" + port: 8080 + username: "admin" + password: "password123" + +navidrome: + url: "http://localhost:4533" + user: "watcher_service" + password: "user_password" + +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 + ignore_bootlegs: true + include_compilations: true +``` + +Полную справку по конфигурации и архитектуру см. в [docs/Specification.md](docs/Specification.md). + +### Архитектура + +| Модуль | Назначение | +|--------|------------| +| **Navidrome Client** | Взаимодействие с Subsonic API v1.16.1 (токенная аутентификация) | +| **MusicBrainz Provider** | Загрузка дискографий с кэшированием и rate limiting | +| **Scanner Engine** | Нормализация строк и нечёткое сравнение | +| **Database Layer** | Хранение настроек, кэша и состояния в SQLite | +| **Notifier** | Планировщик уведомлений в Telegram | +| **Web UI** | Панель управления отсутствющими релизами | + +### Лицензия + +[WTFPL](License.md) — Do What The Fuck You Want To Public License diff --git a/docs/Specification.md b/docs/Specification.md new file mode 100644 index 0000000..606b1c0 --- /dev/null +++ b/docs/Specification.md @@ -0,0 +1,150 @@ +# ЦЕЛЬ ПРОЕКТА + +## 1. Общее описание проекта +**NaviWatcher** — это автономный сервисный инструмент (демон), предназначенный для автоматического мониторинга музыкальных коллекций, хранящихся в Navidrome (или любом сервере с поддержкой Subsonic API). + +**Основная задача:** сопоставлять текущую медиатеку пользователя с полной дискографией артистов во внешних базах данных (MusicBrainz), находить отсутствующие релизы (альбомы, синглы, сборники) и уведомлять об этом пользователя через Telegram и встроенный веб-интерфейс. + + +## 2. Технологический стек +* **Язык программирования:** Go 1.21+ +* **База данных:** SQLite 3 (для хранения кэша, настроек и состояний). +* **HTTP-сервер:** Стандартная библиотека Go (`net/http`) + `html/template`. +* **Внешние зависимости (библиотеки):** + * `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=&s=&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 (флаг скрытия из списка новинок) + +### Таблица `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) + +```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 + ignore_bootlegs: true + include_compilations: true +``` + + +## 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`.