Aoi was sending Matrix cards as soon as a playlist row appeared in the discovery-playlist SQLite, before Navidrome had scanned and indexed it. That produced 'ready' notifications for playlists that were not yet playable. The gate now requires the matching playlist (exact name match, case-insensitive) to be visible in Navidrome via getPlaylists with at least N entries (default 1, knob 'polling.discovery_min_tracks_in_navidrome') before the notification fires. If a playlist stays missing from Navidrome past 'polling.discovery_stale_after_hours' (default 48h), it is marked sent without notification so we don't loop forever. Refactored navidrome_playlist_cover into navidrome_find_playlist (name match) and navidrome_playlist_cover_for (cover for an already-resolved playlist), so the gate and the cover lookup share a single Navidrome roundtrip. |
||
|---|---|---|
| assets | ||
| deploy | ||
| .gitignore | ||
| aoi-avatar.png | ||
| aoi.py | ||
| config.example.json | ||
| README.md | ||
| requirements.txt | ||
Aoi
Aoi (Аой-тян) — домашний Matrix-бот для музыкальной библиотеки. Она следит за Navidrome и discovery-playlist, аккуратно докладывает о новых альбомах, свежих Discovery-плейлистах и предстоящих релизах любимых артистов, обогащая карточки данными Last.fm и ссылками на внешние источники.
Что умеет
| Блок | Что происходит |
|---|---|
| Navidrome albums | Поллинг библиотек main и anime — новые альбомы превращаются в карточки с обложкой, описанием, тегами и рейтингом. |
| Discovery playlists | Свежесгенерированные плейлисты discovery-playlist (имя содержит «Discovery») приходят в Matrix отдельным постом. |
| Release watch | Аналитика по любимым/высоко оцененным артистам: анонсы и фактические релизы из дискографии, отслеживаемой discovery-playlist. |
| Rich-описания | Last.fm-биография, теги, обложка из album.getInfo, ссылки на Last.fm и поиск Bandcamp. |
| Baseline | На первом запуске можно зафиксировать всё уже существующее как «уже видела», чтобы не получить спам с историей. |
| State & dedupe | SQLite хранит увиденные альбомы, плейлисты и релизы — повторов не будет. |
Как устроена логика
Aoi не делает запросов к LLM и не пытается «угадывать» — она опирается на конкретные источники и кеширует увиденное:
- По расписанию опрашивает Navidrome и discovery-playlist (раздельные интервалы для альбомов, плейлистов и релизов).
- Сравнивает с локальным state и забирает только новые сущности.
- Обогащает карточки Last.fm-данными при наличии ключа.
- Шлёт карточку в Matrix-комнату с обложкой, описанием и ссылками.
- Помечает сущность как обработанную в SQLite.
Каждый источник работает независимо, так что падение Navidrome не валит discovery, и наоборот.
Сообщения в Matrix
Карточка нового альбома выглядит примерно так:
🎧 Новый альбом в Navidrome
Artist — Album Title (2026)
━━ ОПИСАНИЕ ━━
Краткая биография/описание альбома из Last.fm…
━━ ТЕГИ ━━
shoegaze · indie · dream-pop
🔗 Last.fm: https://www.last.fm/music/Artist/Album
🔗 Bandcamp: https://bandcamp.com/search?q=Artist+Album
Discovery-плейлисты приходят отдельной карточкой со списком треков, release watch — отдельной с пометкой типа релиза (announced, released).
Файлы проекта
| Файл | Назначение |
|---|---|
aoi.py |
Основной сервис. |
config.example.json |
Безопасный пример конфигурации. |
config.json |
Локальная конфигурация с секретами, не коммитится. |
requirements.txt |
Python-зависимости. |
deploy/aoi.service |
systemd unit для production. |
assets/banner.png |
Баннер README. |
aoi.db |
SQLite-состояние, не коммитится. |
aoi.log |
Лог сервиса, не коммитится. |
Конфигурация
Создать конфиг из примера:
cp config.example.json config.json
Заполнить:
matrix.homeserver,matrix.room_id,matrix.access_token,matrix.user_id;bot.name(по умолчаниюАой-тян) иbot.avatar_pathдля Matrix-профиля;navidrome.url,navidrome.username,navidrome.password, идентификаторы библиотек;discovery.db_path— путь к SQLite базе discovery-playlist;metadata.lastfm_api_key— ключ Last.fm для био и тегов;polling.*— интервалы опроса источников;polling.baseline_existing_on_first_run—trueна первом запуске, чтобы не спамить историей.
Production-путь:
/storage/scripts/aoi
Установка
В production Aoi использует виртуальное окружение Watcher (общие зависимости):
cd /storage/scripts/watcher
python3 -m venv .venv
. .venv/bin/activate
pip install -r /storage/scripts/aoi/requirements.txt
Можно завести отдельный venv — тогда обновить deploy/aoi.service.
Запуск
/storage/scripts/watcher/.venv/bin/python /storage/scripts/aoi/aoi.py
Healthcheck:
curl -fsS http://127.0.0.1:18323/healthz
Ожидаемый ответ:
{"ok":true}
systemd
Unit хранится в репозитории:
deploy/aoi.service
Установка или обновление:
sudo cp /storage/scripts/aoi/deploy/aoi.service /etc/systemd/system/aoi.service
sudo systemctl daemon-reload
sudo systemctl enable aoi.service
sudo systemctl restart aoi.service
Операции:
sudo systemctl restart aoi.service
systemctl --no-pager -l status aoi.service
journalctl -u aoi.service -f
tail -f /storage/scripts/aoi/aoi.log
Ручные команды
curl -X POST http://127.0.0.1:18323/run/navidrome-albums
curl -X POST http://127.0.0.1:18323/run/discovery-playlists
curl -X POST http://127.0.0.1:18323/run/release-watch
Что не коммитить
config.json*.db*.log.venv/__pycache__/