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>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -44,6 +44,9 @@ export class IcyNowPlayingService {
|
||||
'Орфей',
|
||||
'Радио Ваня',
|
||||
'Русская Волна',
|
||||
'Comedy Radio',
|
||||
'Радио Energy',
|
||||
'Radio 7',
|
||||
],
|
||||
},
|
||||
NOT: { streamUrl: { contains: 'emgsound.ru' } },
|
||||
|
||||
@@ -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],
|
||||
|
||||
84
src/now-playing/radio101-now-playing.service.ts
Normal file
84
src/now-playing/radio101-now-playing.service.ts
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user