From fa7742d06e1f448f1a851c04a791d8ed49f557b1 Mon Sep 17 00:00:00 2001 From: nk Date: Wed, 3 Jun 2026 18:19:12 +0300 Subject: [PATCH] =?UTF-8?q?feat(now-playing):=20Love=20Radio=20=D1=87?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=B7=20api.loveradio.ru=20(ICY=20=D1=88?= =?UTF-8?q?=D0=BB=D1=91=D1=82=20=D0=BC=D1=83=D1=81=D0=BE=D1=80=20onlinesto?= =?UTF-8?q?p56k)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/now-playing/icy-now-playing.service.ts | 2 +- src/now-playing/love-now-playing.service.ts | 89 +++++++++++++++++++++ src/now-playing/now-playing.module.ts | 2 + 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 src/now-playing/love-now-playing.service.ts diff --git a/src/now-playing/icy-now-playing.service.ts b/src/now-playing/icy-now-playing.service.ts index 262f8c7..c97531b 100644 --- a/src/now-playing/icy-now-playing.service.ts +++ b/src/now-playing/icy-now-playing.service.ts @@ -29,7 +29,7 @@ export class IcyNowPlayingService { const where = { recordStationId: null, isOnline: true, - genre: { not: 'DFM' }, + genre: { notIn: ['DFM', 'Love Radio'] }, NOT: { streamUrl: { contains: 'emgsound.ru' } }, }; const total = await this.prisma.station.count({ where }); diff --git a/src/now-playing/love-now-playing.service.ts b/src/now-playing/love-now-playing.service.ts new file mode 100644 index 0000000..7f4e44e --- /dev/null +++ b/src/now-playing/love-now-playing.service.ts @@ -0,0 +1,89 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { Interval } from '@nestjs/schedule'; +import { PrismaService } from '../prisma/prisma.service'; +import { NowPlayingService } from './now-playing.service'; + +interface LoveHistoryItem { + artistTitle?: string | null; + songTitle?: string | null; +} + +/** + * Now-playing для Love Radio. В ICY-потоках вместо трека приходит «onlinestop56k» + * (имя мута), поэтому берём текущий трек из их API: + * api.loveradio.ru/api/v1/love-radio/player/history/list?musicStreamId={id}&limit=1 + * (data[0] = последний/текущий). Обложек API не даёт — их подставит наше обогащение. + */ +@Injectable() +export class LoveNowPlayingService { + private readonly logger = new Logger(LoveNowPlayingService.name); + private readonly headers = { + 'User-Agent': 'Mozilla/5.0', + Origin: 'https://www.loveradio.ru', + Referer: 'https://www.loveradio.ru/', + }; + + // Имя нашей станции -> musicStreamId в API Love Radio + private readonly streamId: Record = { + 'Love Radio': 28, + 'Love RnB': 2, + 'Love Top40': 3, + 'Love Dance': 4, + 'Love Chill': 5, + 'Love Gold': 6, + 'Love Russian': 7, + 'Love KPOP': 10, + 'Love Power': 11, + 'Love Summer': 1, + }; + + constructor( + private readonly prisma: PrismaService, + private readonly nowPlayingService: NowPlayingService, + ) {} + + @Interval(30000) + async pollLoveNowPlaying() { + const stations = await this.prisma.station.findMany({ + where: { genre: 'Love Radio' }, + }); + if (stations.length === 0) return; + + let updated = 0; + await Promise.allSettled( + stations.map(async (station) => { + const id = this.streamId[station.name]; + if (!id) return; + + const url = + `https://api.loveradio.ru/api/v1/love-radio/player/history/list` + + `?musicStreamId=${id}&limit=1`; + const res = await fetch(url, { headers: this.headers }); + if (!res.ok) return; + + const json = (await res.json()) as { data?: LoveHistoryItem[] }; + const cur = json.data?.[0]; + const artist = (cur?.artistTitle ?? '').trim(); + const song = (cur?.songTitle ?? '').trim(); + if (!artist || !song) return; + + await this.nowPlayingService.ingest({ + stationDbId: station.id, + stationNumericId: station.stationId, + artist, + song, + coverUrl: null, + }); + if (!station.isOnline) { + await this.prisma.station.update({ + where: { id: station.id }, + data: { isOnline: true }, + }); + } + updated++; + }), + ); + + this.logger.log(`Love poll: ${updated}/${stations.length} обновлено`); + } +} diff --git a/src/now-playing/now-playing.module.ts b/src/now-playing/now-playing.module.ts index ddc3b42..3b7671a 100644 --- a/src/now-playing/now-playing.module.ts +++ b/src/now-playing/now-playing.module.ts @@ -6,6 +6,7 @@ import { RecordStationSyncService } from './record-station-sync.service'; import { IcyNowPlayingService } from './icy-now-playing.service'; import { EmgNowPlayingService } from './emg-now-playing.service'; import { DfmNowPlayingService } from './dfm-now-playing.service'; +import { LoveNowPlayingService } from './love-now-playing.service'; import { ChartsModule } from '../charts/charts.module'; @Module({ @@ -18,6 +19,7 @@ import { ChartsModule } from '../charts/charts.module'; IcyNowPlayingService, EmgNowPlayingService, DfmNowPlayingService, + LoveNowPlayingService, ], exports: [NowPlayingService], })