Commit Graph

82 Commits

Author SHA1 Message Date
nk
e1bceb8bd1 feat(privacy): страница политики конфиденциальности на /privacy 2026-06-08 13:55:44 +03:00
nk
0dd52ddc3b ops: вернуть в compose конфиг авто-обновления (bind-mount appdist + env)
Регрессия: правки авто-обновления (mount /opt/radiola/appdist:/data/dist +
APP_VERSION_FILE/DOWNLOADS_DIR) жили только на сервере, в репо их не было. scp
docker-compose.yml при деплое Shazam затёр их → /app-version отдавал фолбэк.
Теперь они в репозитории — деплой их не теряет.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 19:27:23 +03:00
nk
791156f814 feat(shazam): глобальный лимит распознаваний (защита баланса коинов)
Скользящее окно 60с, максимум 30 реальных вызовов Shazam/мин (кэш-хиты не в счёт).
Превышение → 429. Защищает платный баланс от перебора станций.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 18:47:25 +03:00
nk
059ebc9c45 feat(shazam): реальный двухстадийный флоу shazam-api.com (recognize → poll)
- ShazamClient: POST /api/recognize (multipart file) → uuid, затем поллинг
  POST /api/results/{uuid} до status="completed" (12×1.2с ≈ до 15с)
- из ответа берём track.title (песня) и track.subtitle (исполнитель); обложки
  в API нет — подтягиваем из нашей БД по normKey (resolveCover в сервисе)
- авторизация Authorization: Bearer; база https://shazam-api.com/api по умолч.
- SHAZAM_API_KEY проброшен в docker-compose + .env.example (значение — на сервере)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 18:45:00 +03:00
nk
1616c231b7 feat(shazam): распознавание трека через Shazam API для станций без метаданных
- новый модуль shazam: POST /shazam/recognize/:stationId — тянет ~6с аудио из
  потока станции, отдаёт в изолированный ShazamClient, возвращает artist/song/cover
- ShazamClient — адаптер к shazam-api.com, ключ из env (SHAZAM_API_KEY); точный
  контракт запроса/ответа помечен TODO до получения доки из ЛК
- кэш результата по станции (15с) — троттлинг + экономия платных вызовов
- общий реестр не-музыкальных жанров (common/station-classification.ts);
  charts.service переведён на него, shazam использует для гейта «есть ли музыка»

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 18:37:53 +03:00
nk
05e3796b85 feat(app-version): эндпоинт /app-version + хостинг APK для авто-обновления
GET /app-version читает манифест с диска (data/app-version.json, путь — env
APP_VERSION_FILE) → {android:{version_name,version_code,download_url,force_update,
sha256,notes}}. Релиз = заменить APK в /downloads + отредактировать json, без
пересборки. При сбое файла отдаёт version_code:0 (апдейт не навязываем).
Статика /downloads/ (DOWNLOADS_DIR) — раздаёт APK.
2026-06-06 20:21:54 +03:00
nk
4aa3b55b5e perf(backend): ретенция track_plays, прун сирот-треков, проекция now-playing, пул БД
- MaintenanceService (@Cron daily 4:00): ретенция track_plays >180д чанками по 20k
  (без ретенции таблица растёт ~100k строк/сутки) + прун осиротевших треков
  (без проигрываний/лайков/обложки, >30д). Сейчас удаляет 0 (данным 4 дня) —
  только ограничивает будущий рост. ВНИМАНИЕ: 180д ограничивает и чарт period=all.
- getAllNowPlaying: select-проекция (stationId+name) вместо include station:true —
  не тянем всю строку Station (streamUrl, tags[], даты) на каждый ряд now_playing.
- PrismaService: connection_limit=20 в URL идемпотентно (дефолт ~5 мал под ~16
  конкурентных поллеров).
2026-06-06 17:08:36 +03:00
nk
944ec63df0 refactor(now-playing): единый IcyReader + реестр dedicated-источников
Убраны 3 копии state-machine разбора icy-metaint (icy/novoeby/love) → один
readIcyStreamTitle в icy-reader.ts (с опцией decode auto-1251 для cp1251-потоков
и корректным терминатором ';' — апострофы в названиях больше не обрезаются).

Ручной genre-notIn список в IcyNowPlayingService заменён центральным реестром
dedicated-sources.ts (host + genre), согласованным с селекторами самих сервисов:
добавил выделенный сервис — впиши host/genre в одно место, ICY его пропустит
автоматически. Исключение по хосту провабельно совпадает с тем, что сервис
реально обрабатывает (раньше genre легко забывали добавить).
2026-06-06 16:54:02 +03:00
nk
a3434ed894 perf(backend): индексы, кэш чартов, пропуск upsert, фикс N+1 обогащения
- track_plays(played_at,track_id,station_id) покрывающий + tracks(first_seen_at) WHERE
  pending частичный (применены CONCURRENTLY на проде + миграция idempotent)
- ChartsService.getTopTracks: in-memory TTL-кэш 90с по (period,genre,limit) → детальная
  страница и параллельные запросы не пересчитывают тяжёлые агрегации
- NowPlayingService.ingest: не пишем now_playing и не шлём сокет, если трек не изменился
  (было ~20k бесполезных upsert/час)
- enrichNowPlaying: вместо N+1 (300 upsert/мин) — один batched findMany по normKey

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:20:25 +03:00
nk
924a4a0ab1 fix(charts): отсев заглушек Online-Radio и джинглов FX-NN 2026-06-06 15:52:41 +03:00
nk
0084177d15 fix(charts): отсев мусора и разговорных/шуточных станций из чарта
recordPlay теперь не считает: разговорные/шуточные жанры (Кассиопея, Юмор ФМ,
Рассказы, Радио Вера, Comedy Radio, ВГТРК, Старое радио) и мусорные названия
(хекс-плейсхолдеры с цифрой, URL, числовые коды, artist==song). Исторический мусор
почищен напрямую в БД (−4273 трека, −13274 плея талк/комеди-станций).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 15:49:17 +03:00
nk
38e380a59f fix(now-playing): Radio 7 главные каналы — slug не съедает '7'
Баг: strip /\d+$/ у маунта radio7128 резал '7128' → 'radio' (пусто в meta).
Теперь slug = radio7(_слово)? без битрейта: radio7128→radio7, radio7_love64→radio7-love.
2026-06-06 09:50:18 +03:00
nk
cc30422d8d feat(now-playing): 101.ru (Comedy Radio + Радио Energy) и Radio 7 через EMG
101.ru (Comedy, NRJ/Energy, ~15 каналов): id канала = последний сегмент потока
pub*.101.ru/.../{id}; трек GET 101.ru/api/channel/getTrackOnAir/{id}/?idcity=1 →
result.short {titleExecutorFull, titleTrack, cover.coverOriginal}; обложка cdn0.101.ru.
Radio 7 — это ЕМГ на старых мейнах radio7.hostingradio.ru: расширил EmgNowPlayingService
(slug radio7128→radio7, radio7_love64→radio7-love). Три жанра исключены из ICY-поллера.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 09:29:41 +03:00
nk
c4c475544a feat(now-playing): Радио Ваня + Русская Волна; Питер объединён в SpbRadio
Радио Ваня (20 каналов) — тот же движок/API, что Питер ФМ (один разработчик):
объединил в SpbRadioNowPlayingService (NETWORKS=[piterfm, radiovanya]), матч
станции по МАУНТУ из поля link (у Вани slug≠маунт). Обложки iTunes.
Русская Волна (~27, amgradio.ru, ICY нет) — VolnaNowPlayingService: единый
info.volna.top/radio.json, поля {prefix}_title, маунт→префикс (RusRock128→rusrock,
ChillaFM128→chilla). Обложки через обогащение. Оба жанра исключены из ICY-поллера.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 09:18:23 +03:00
nk
3c4f349f71 fix(now-playing): Орфей — чиним кодировку (двойная мойибейк cp1251) + режем хекс-хвосты
status-json смешанной кодировки: часть тайтлов нормальный UTF-8, часть — cp1251-
байты, прочитанные как latin1 и завёрнутые в UTF-8 (мойибейк «Íèêîëà»→«Никола»).
fixEncoding: реальную кириллицу не трогаем, мойибейк (À-ÿ) восстанавливаем
latin1→windows-1251. Срезаем приклеенный служебный id трека (- f0098627), фильтр
hex/числовых плейсхолдеров усилен. Каналы без реального произведения — без подписи.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 09:01:03 +03:00
nk
c87a0caa5c feat(now-playing): Питер ФМ и Орфей
Питер ФМ (16 каналов, cdnvideo) — ICY пуст; берём трек+обложку из их API
radiopiterfm.ru: /api/v1/streams/ (slug↔id) + /api/v5/playlists/{id}/ →
items[0].track {name, artist.name, imglarge}. Обложки готовые (iTunes).
Орфей (классика, radio.orpheus.ru) — через Icecast status-json.xsl по маунтам
(Chan_N), парсим «Композитор — Произведение», отсекаем мусор (hex/URL/undefined);
обложка через обогащение. Оба жанра исключены из общего ICY-поллера.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 08:34:32 +03:00
nk
426fd0e197 feat(now-playing): Новое Радио BY — now-playing с правильной кодировкой + Wake Up
5 мейнов live.novoeradio.by отдают ICY, но кириллица в windows-1251 (общий
поллер читал UTF-8 → каша). NovoeByNowPlayingService (@Interval 30с): свой ICY-
ридер с декодом UTF-8→fallback 1251, джинглы без трека пропускаем, обложка через
обогащение. Исключён из общего ICY-поллера. Маунт Wake Up исправлен
(wakeupshow→wakeup, был 404/offline) в seed и прод-БД.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 21:20:06 +03:00
nk
d5f30cd05d fix(now-playing): ГУСЬ — не использовать битый art AzuraCast, обложки через обогащение
song.art (/api/station/{slug}/art/{hash}) на radiogoose.ru отдаёт 404 — route
обложек не включён. Передаём coverUrl=null → обложку подтянет iTunes/Deezer по
normKey, как у ICY-станций.
2026-06-05 20:36:45 +03:00
nk
d8b6a6024f fix(now-playing): ГУСЬ Технорейв — алиас slug harddance→technorave
У этого канала mount потока (harddance) не совпадает с ключом AzuraCast API
(technorave). Остальные 15 — совпадают.
2026-06-05 20:27:01 +03:00
nk
cb0e401854 feat(now-playing): ГУСЬ (radiogoose) — now-playing + обложки, починка потоков
Сеть ГУСЬ (16 каналов) на AzuraCast (radiogoose.ru). Потоки в каталоге были
многострочными (url1\nurl2) → клиент рвал воспроизведение, health-check метил
offline, станции скрывались. Почищены на канонический https://radiogoose.ru/listen/{slug}/play.
GooseNowPlayingService (@Interval 30с): AzuraCast API /api/nowplaying/{slug} →
now_playing.song {artist,title,art}, ingest + isOnline=true. Исключён из ICY-поллера.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 20:22:28 +03:00
nk
fd26e4df57 chore(stations): удалены мёртвые каналы Зайцева New year/Hvilya
Потоки zaycevfm.cdnvideo.ru мертвы (000), станции оффлайн. Удалены из seed
и из прод-БД (station_id 399, 402).
2026-06-05 20:07:20 +03:00
nk
a06a9b2a2b feat(now-playing): now-playing + обложки для Зайцев ФМ
19 каналов Зайцев ФМ (MP3 abs.zaycev.fm) не отдают ICY → трека не было.
ZaycevNowPlayingService (@Interval 30с): тянет текущий трек из API сайта
GET https://www.zaycev.fm/api/v1/recent?channel={slug}&limit=1 (slug = буквенная
часть имени потока, pop256k→pop), берёт artist/title и готовую обложку
(radio2.zaycev.fm/artistimages), фильтр is_music. Зайцев исключён из ICY-поллера.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 19:52:37 +03:00
nk
4d9fd24074 feat(now-playing): now-playing + обложки для каналов Unistar
8 HLS-каналов Unistar (Беларусь) не отдают ICY, поэтому трек брали неоткуда.
Добавлен UnistarNowPlayingService (@Interval 30с): тянет текущий трек из их API
https://api3.unistar.by/client/latest/{slug} (slug = сегмент /hls/{slug}/ потока),
берёт Artist/Title и имя файла обложки (unistar.by/upload/music/photos/), ingest'ит.
Только Type=Music. Unistar исключён из ICY-поллера.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 19:37:17 +03:00
nk
1f67e01ac8 feat(covers): POST /covers/submit — приём обложки, найденной клиентом
Клиент (со своего IP, не забанен Apple) ищет арт в iTunes и шлёт ссылку.
Сервер качает (CDN из РФ доступен), конвертит в WebP (CoverStorageService),
кладёт к себе, апсертит Track по normKey. Защита: host-whitelist
(mzstatic/dzcdn, против SSRF), идемпотентность, кап одновременных загрузок.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:59:36 +03:00
nk
52c8c3f69f fix(enrich): быстрый now-playing проход — только Deezer (без затыка iTunes)
iTunes-фолбэк в coverFast с троттлингом 3.5с сериализовал проход — он не
успевал за сменой треков, покрытие падало. Теперь быстрый проход = только
Deezer (параллельно, без троттла). Промахи Deezer добирает фоновый enrichOne
(iTunes через прокси с троттлингом).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:40:06 +03:00
nk
28487a7911 fix(enrich): iTunes/Deezer через DE-прокси + троттлинг (RU-IP забанен)
Корневая причина пропавших обложек: RU-IP сервера ЗАБАНЕН Apple (iTunes search
→ 429 «Rate limit ... 121.127.37.212»), а Deezer из РФ отдаёт ПУСТОЙ каталог.
Оба источника с сервера не работали. Теперь iTunes/Deezer-поиск ходит через тот
же DE-прокси, что и Discogs (DISCOGS_PROXY): с DE-IP iTunes доступен, Deezer
отдаёт каталог. Deezer сделан первичным (высокий лимит), iTunes — фолбэк с
сериализацией (3.5с интервал), чтобы не забанить общий DE-IP. Скачивание самих
картинок (mzstatic/dzcdn) — напрямую, они из РФ доступны.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:30:06 +03:00
nk
59aa23ff77 fix(enrich): coverFast — очищенный iTunes + Deezer (не множить лимит)
Прошлый вариант делал 2 запроса iTunes на каждый now-playing-трек без обложки
(170+ играющих) → упирался в лимит iTunes ~20/мин, обложки не наливались.
coverFast теперь: 1 запрос iTunes по ОЧИЩЕННОМУ названию (чаще матчит ремиксы/
«(Original Mix)») → на промахе/429 фолбэк Deezer (отдельный лимит). Полный
fetchItunes (full→clean→deezer) остаётся для фонового enrichOne.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:06:43 +03:00
nk
ba9b4054e8 feat(enrich): больше обложек — очистка запроса iTunes + фолбэк Deezer
Суффиксы названий («(Original Mix)», «(SEA)», «[... Dub]», «feat. X»)
ломали точный матч iTunes (limit=1) — у многих треков (особенно электроника/
лаунж/ремиксы Royal Radio и др.) обложка не находилась, хотя в iTunes/Deezer
она есть. Теперь fetchItunes: (1) запрос как есть → (2) очищенный (без скобок/
feat) → (3) фолбэк Deezer (публичный API, без ключа) только за обложкой.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:50:20 +03:00
nk
35f9a2b7cc chore(stations): Royal Radio — https вместо http (301-редирект)
10 каналов royalradio.space переведены на https (http отдавал 301→https,
ExoPlayer не шёл по кросс-протокольному редиректу). Прод-БД обновлена точечно.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:31:13 +03:00
nk
c2e941f1c3 chore(stations): Romantika (711) — рабочий HLS вместо мёртвого srv21
Главный Радио Романтика был в offline-ids (мёртвый srv21.gpmradio). Обновлён
на hls-01-gpm.hostingradio.ru/romantika495. Прод-БД поправлена точечно.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:18:25 +03:00
nk
326bbbc0ee chore(stations): Like FM (718) — рабочий HLS, группа Like FM
Старый поток srv21.gpmradio мёртв (станция была в offline-ids). Обновлён на
hls-01-gpm.hostingradio.ru/likefm495 (главный Like FM), groupId 27. Прод-БД
обновлена точечно (без полного reseed, чтобы не сбросить жанры now-playing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:05:09 +03:00
nk
87cc67072c chore(scripts): пробер битрейт-вариантов потоков станций
Перебирает соседние битрейты в маунте каждого потока, проверяет живость
(заголовки + content-type), пишет массив qualities в stations.json клиента.
Пропускает HLS (emgsound) и Love (UID). Использован для фичи переключения
качества в плеере.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 12:37:24 +03:00
nk
3c6dbed659 feat(now-playing): Radio ROKS через TavR Media API (трек + обложки)
Главный канал ROKS не отдаёт трек по ICY (StreamTitle пустой), сабканалы —
без обложек. Новый RoksNowPlayingService опрашивает o.tavr.media/roks
(главный) и /roks4songs (сабканалы по type ukr/bal/new/har), отдаёт и трек,
и обложку static.radioroks.ua. Исключил genre='Radio ROKS' из ICY-поллера.
2026-06-04 11:44:30 +03:00
nk
51576f7198 feat(now-playing): Радио Монте-Карло через Крутой Медиа API
Все 21 канал Монте-Карло — сеть Крутой Медиа (dfm.ru/api/n/current).
Добавил genre='Radio Monte Carlo' в DfmNowPlayingService, матчинг по
слагу из маута потока (basename без битрейта: blues96.aacp -> blues),
исключил из ICY-поллера. Чинит 5 каналов, залипших на 'Дух — Тишина'
(Blues, Chill Lounge, Italiano, Meditation, Summertime).
2026-06-04 10:55:09 +03:00
nk
d6b8be124e perf(enrich): 3-й IP Discogs (token3 через форс-IPv4 RU) + concurrency 12
token3 ходит напрямую с RU, но форсирован IPv4 (121.127.37.212) — для Discogs
это 3-й IP (token1=RU-v6, token2=DE-v4, token3=RU-v4). ~162/мин потолок. Без
доп. инфры. concurrency 12 чтобы задействовать 3 IP.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:21:37 +03:00
nk
7457498f5b perf(enrich): concurrency 8 — задействовать оба IP Discogs (RU+DE)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:10:39 +03:00
nk
e982fde730 feat(enrich): 2-й токен Discogs через DE-прокси (2 IP → ~108/мин жанров)
Discogs лимитит по IP. token1 идёт напрямую (IP RU), token2 — через форвард-прокси
на DE (IP DE, tinyproxy, доступ только с RU). Два IP, у каждого свой слот ~54/мин
→ суммарно ~108/мин жанров без 429. undici ProxyAgent. Без DISCOGS_PROXY — только
token1 (54/мин).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:01:37 +03:00
nk
dfdfb7e4ab fix(enrich): Discogs троттлит по IP — общий лимит ~54/мин (стоп 429-шторм)
Два токена с одного IP не помогают (Discogs лимитит по IP, не по токену) —
вызывало 100% 429. Вернул общий интервал 1100мс (~54/мин). 2-й токен полезен
только на втором IP (DE-воркер).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:52:39 +03:00
nk
94e7f46b39 perf(enrich): ротация двух токенов Discogs (~108/мин жанров)
Discogs лимит ~60/мин на токен. Поддержка нескольких токенов (DISCOGS_TOKEN,
DISCOGS_TOKEN2) — у каждого свой слот, берём наименее загруженный → суммарно
вдвое быстрее жанры/стили/лейблы, без 429. Токены — в env (не в гите).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:45:35 +03:00
nk
ed94bd73d7 perf(enrich): cover-проход эфира 8 параллельно + защита от наложения
~447 живых станций, смена ~120 треков/мин — проход по 4 не успевал. Теперь 8
параллельно + guard против перекрытия крон-запусков.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:02:33 +03:00
nk
36043c32b0 perf(enrich): быстрый cover-only проход эфира через iTunes (без Discogs-гейта)
Discogs-лимитер делал Discogs узким местом (54/мин) для ВСЕХ треков, тормозя
обложки. Теперь крон now-playing красит эфир обложками напрямую через iTunes
(4 параллельно, без Discogs), а полное обогащение жанрами идёт фоном. Обложки
живого набора появляются быстро.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:56:50 +03:00
nk
5164843824 perf(enrich): rate-limiter на Discogs + concurrency 5 (обложки быстрее)
Discogs ограничен ≥1.1с между вызовами (≤54/мин, без 429) независимо от
параллельности. Параллельность 5 → обложки (iTunes, без лимита) и скачивание
льются быстрее. Решает медленное наполнение обложек живого набора.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:51:28 +03:00
nk
588857a73e feat(now-playing): MAXIMUM через тот же Крутой-API (dfm.ru/api/n/current)
MAXIMUM — сеть Крутой Медиа, её каналы в том же /api/n/current. Поллер расширен
на genre IN (DFM, MAXIMUM) + алиасы (maxbritpop, max80, maximum90, rockhits и т.д.).
ICY исключает MAXIMUM.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:36:48 +03:00
nk
d46020bd37 chore(stations): Love Radio -> потоки n340 (синхр. с клиентом)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:14:26 +03:00
nk
bd2cd36f1e fix(now-playing): Love Radio — ICY авторизованных n340-потоков (per-channel)
player/online кэширует один трек на все каналы. Берём трек из ICY самих потоков
(каждый поток физически разный), читая их с сессионным UID (бэкенд берёт из config).
Теперь у каждого Love-канала свой трек.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:09:01 +03:00
nk
68c919c8ba fix(now-playing): Love Radio — online?musicStreamId (per-channel), не history
history/list игнорирует musicStreamId (всегда главный эфир) → все каналы показывали
один трек. player/online?musicStreamId отдаёт верный трек канала.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:23:56 +03:00
nk
fa7742d06e feat(now-playing): Love Radio через api.loveradio.ru (ICY шлёт мусор onlinestop56k)
ICY-потоки Love Radio отдают 'onlinestop56k' вместо трека. Берём текущий трек из
их API (player/history/list?musicStreamId=N&limit=1, data[0]). Статичный маппинг
наших станций -> musicStreamId. ICY-поллер исключает genre='Love Radio'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:19:12 +03:00
nk
338f189f33 fix(enrich): не помечать done при сбое запроса iTunes (промах не застывает)
Отличаем сбой запроса (сеть/HTTP-ошибка → ретраить, оставляем pending) от чистого
'не найдено' (done). Раньше транзиентный сбой iTunes под нагрузкой навсегда лишал
трек обложки. fetchItunes теперь бросает при !res.ok.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:09:54 +03:00
nk
40a9f3968f feat(stations): корректный health-check + эндпоинт offline-ids
health-check переписан: живой = пришли заголовки 200-399 (рвём соединение сразу,
не ждём бесконечное тело аудиопотока), параллельно, прогон при старте + ежечасно.
Раньше GET висел на живых потоках до таймаута → ложный offline. Новый GET /stations/offline-ids
отдаёт station_id оффлайн-станций — клиент их скрывает.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:56:59 +03:00
nk
c2f638e1a1 chore(stations): синхр. — отключены 67 мёртвых станций
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:48:45 +03:00