Initial commit

This commit is contained in:
2026-05-19 17:42:12 +03:00
commit 001f9ce691
4 changed files with 550 additions and 0 deletions

170
CLAUDE.md Normal file
View File

@@ -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)

13
License.md Normal file
View File

@@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, May 2026
Copyright (C) 2026 Zagainov Vladimir <mrixs@mrixs.me>
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.

217
README.md Normal file
View File

@@ -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

150
docs/Specification.md Normal file
View File

@@ -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=<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 (флаг скрытия из списка новинок)
### Таблица `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`.