diff --git a/src/now-playing/emg-now-playing.service.ts b/src/now-playing/emg-now-playing.service.ts index 1a44fdc..4f2975e 100644 --- a/src/now-playing/emg-now-playing.service.ts +++ b/src/now-playing/emg-now-playing.service.ts @@ -35,8 +35,14 @@ export class EmgNowPlayingService { @Interval(30000) async pollEmgNowPlaying() { // Не фильтруем по isOnline: health-check ошибочно метит HLS-потоки offline. + // Radio 7 — тоже ЕМГ, но на старых мейнах radio7.hostingradio.ru (slug в meta). const stations = await this.prisma.station.findMany({ - where: { streamUrl: { contains: 'emgsound.ru' } }, + where: { + OR: [ + { streamUrl: { contains: 'emgsound.ru' } }, + { streamUrl: { contains: 'radio7.hostingradio.ru' } }, + ], + }, }); if (stations.length === 0) return; @@ -93,9 +99,18 @@ export class EmgNowPlayingService { } // hls-01-europaplus-kpop.emgsound.ru → europaplus-kpop + // radio7.hostingradio.ru:8040/radio7_love64.mp3 → radio7-love (strip bitrate, _→-) private extractSlug(streamUrl: string): string | null { - const m = streamUrl.match(/hls-\d+-([a-z0-9-]+)\.emgsound\.ru/i); - return m ? m[1].toLowerCase() : null; + const emg = streamUrl.match(/hls-\d+-([a-z0-9-]+)\.emgsound\.ru/i); + if (emg) return emg[1].toLowerCase(); + const r7 = streamUrl.match(/radio7\.hostingradio\.ru[:0-9]*\/([a-z0-9_]+)/i); + if (r7) { + return r7[1] + .toLowerCase() + .replace(/\d+$/, '') + .replace(/_/g, '-'); + } + return null; } // Дата и окно времени по Москве (контейнер может быть в UTC) diff --git a/src/now-playing/icy-now-playing.service.ts b/src/now-playing/icy-now-playing.service.ts index b861378..87f22fc 100644 --- a/src/now-playing/icy-now-playing.service.ts +++ b/src/now-playing/icy-now-playing.service.ts @@ -44,6 +44,9 @@ export class IcyNowPlayingService { 'Орфей', 'Радио Ваня', 'Русская Волна', + 'Comedy Radio', + 'Радио Energy', + 'Radio 7', ], }, NOT: { streamUrl: { contains: 'emgsound.ru' } }, diff --git a/src/now-playing/now-playing.module.ts b/src/now-playing/now-playing.module.ts index 53ed99c..2e519cb 100644 --- a/src/now-playing/now-playing.module.ts +++ b/src/now-playing/now-playing.module.ts @@ -14,6 +14,7 @@ import { GooseNowPlayingService } from './goose-now-playing.service'; import { NovoeByNowPlayingService } from './novoeby-now-playing.service'; import { SpbRadioNowPlayingService } from './spb-radio-now-playing.service'; import { VolnaNowPlayingService } from './volna-now-playing.service'; +import { Radio101NowPlayingService } from './radio101-now-playing.service'; import { OrpheusNowPlayingService } from './orpheus-now-playing.service'; import { ChartsModule } from '../charts/charts.module'; @@ -35,6 +36,7 @@ import { ChartsModule } from '../charts/charts.module'; NovoeByNowPlayingService, SpbRadioNowPlayingService, VolnaNowPlayingService, + Radio101NowPlayingService, OrpheusNowPlayingService, ], exports: [NowPlayingService], diff --git a/src/now-playing/radio101-now-playing.service.ts b/src/now-playing/radio101-now-playing.service.ts new file mode 100644 index 0000000..c80bce3 --- /dev/null +++ b/src/now-playing/radio101-now-playing.service.ts @@ -0,0 +1,84 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { Interval } from '@nestjs/schedule'; +import { PrismaService } from '../prisma/prisma.service'; +import { NowPlayingService } from './now-playing.service'; + +interface Radio101Resp { + result?: { + short?: { + titleTrack?: string; + titleExecutorFull?: string; + cover?: { coverOriginal?: string }; + }; + }; +} + +/** + * Now-playing для станций на платформе 101.ru (Comedy Radio, Радио Energy/NRJ). + * Потоки pub*.101.ru/stream/.../{channelId} — id канала = последний сегмент. + * Трек: `https://101.ru/api/channel/getTrackOnAir/{id}/?dataFormat=json&idcity=1` + * → result.short {titleExecutorFull, titleTrack, cover.coverOriginal}. Обложка — + * cdn0.101.ru + coverOriginal. (Comedy: «комик - реприза» — это их «в эфире».) + */ +@Injectable() +export class Radio101NowPlayingService { + private readonly logger = new Logger(Radio101NowPlayingService.name); + private readonly coverBase = 'https://cdn0.101.ru'; + private readonly headers = { 'User-Agent': 'Mozilla/5.0' }; + + constructor( + private readonly prisma: PrismaService, + private readonly nowPlayingService: NowPlayingService, + ) {} + + @Interval(30000) + async pollRadio101NowPlaying() { + const stations = await this.prisma.station.findMany({ + where: { streamUrl: { contains: '.101.ru' } }, + }); + if (stations.length === 0) return; + + let updated = 0; + await Promise.allSettled( + stations.map(async (station) => { + const id = this.extractChannelId(station.streamUrl); + if (!id) return; + + const res = await fetch( + `https://101.ru/api/channel/getTrackOnAir/${id}/?dataFormat=json&idcity=1`, + { headers: this.headers, redirect: 'follow' }, + ); + if (!res.ok) return; + const short = ((await res.json()) as Radio101Resp).result?.short; + const artist = (short?.titleExecutorFull ?? '').trim(); + const song = (short?.titleTrack ?? '').trim(); + if (!artist || !song) return; + + const coverPath = short?.cover?.coverOriginal; + const coverUrl = coverPath ? this.coverBase + coverPath : null; + + await this.nowPlayingService.ingest({ + stationDbId: station.id, + stationNumericId: station.stationId, + artist, + song, + coverUrl, + }); + if (!station.isOnline) { + await this.prisma.station.update({ + where: { id: station.id }, + data: { isOnline: true }, + }); + } + updated++; + }), + ); + this.logger.log(`Radio101 poll: ${updated}/${stations.length} обновлено`); + } + + // https://pub0302.101.ru:8000/stream/pro/aac/64/446 → 446 + private extractChannelId(streamUrl: string): string | null { + const m = streamUrl.match(/\/(\d+)\/?$/); + return m ? m[1] : null; + } +}