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:
nk
2026-06-06 09:29:41 +03:00
parent c4c475544a
commit cc30422d8d
4 changed files with 107 additions and 3 deletions

View 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;
}
}