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>
This commit is contained in:
nk
2026-06-05 20:22:28 +03:00
parent fd26e4df57
commit cb0e401854
4 changed files with 108 additions and 16 deletions

View File

@@ -6636,7 +6636,7 @@
"name": "Радио Гусь",
"bitrate": "320",
"site": "https://radiogoose.ru/",
"stream": "http://stream.radiogoose.ru/listen/radiogoose/play\nhttp://stream4.radiogoose.ru/listen/radiogoose/play",
"stream": "https://radiogoose.ru/listen/radiogoose/play",
"type": "mp3",
"iconText": "",
"textColor": "#FF834B",
@@ -6651,7 +6651,7 @@
"name": "ГУСЬ.Бигрум",
"bitrate": "320",
"site": "https://radiogoose.ru/",
"stream": "http://stream.radiogoose.ru/listen/bigroom/play\nhttp://stream5.radiogoose.ru/listen/bigroom/play",
"stream": "https://radiogoose.ru/listen/bigroom/play",
"type": "mp3",
"iconText": "ГУСЬ\\Бигрум",
"textColor": "#FF834B",
@@ -6666,7 +6666,7 @@
"name": "ГУСЬ.Рус",
"bitrate": "320",
"site": "https://radiogoose.ru/",
"stream": "http://stream.radiogoose.ru/listen/rus/play\nhttp://stream1.radiogoose.ru/listen/rus/play",
"stream": "https://radiogoose.ru/listen/rus/play",
"type": "mp3",
"iconText": "ГУСЬ\\Рус",
"textColor": "#FF834B",
@@ -6681,7 +6681,7 @@
"name": "ГУСЬ.Олдскул",
"bitrate": "320",
"site": "https://radiogoose.ru/",
"stream": "http://stream.radiogoose.ru/listen/oldschool/play\nhttp://stream1.radiogoose.ru/listen/oldschool/play",
"stream": "https://radiogoose.ru/listen/oldschool/play",
"type": "mp3",
"iconText": "ГУСЬ\\Олдскул",
"textColor": "#FF834B",
@@ -6696,7 +6696,7 @@
"name": "ГУСЬ.Рок",
"bitrate": "320",
"site": "https://radiogoose.ru/",
"stream": "http://stream.radiogoose.ru/listen/rock/play\nhttp://stream3.radiogoose.ru/listen/rock/play",
"stream": "https://radiogoose.ru/listen/rock/play",
"type": "mp3",
"iconText": "ГУСЬ\\Рок",
"textColor": "#FF834B",
@@ -6711,7 +6711,7 @@
"name": "ГУСЬ.Мидтемпо",
"bitrate": "320",
"site": "https://radiogoose.ru/",
"stream": "http://stream.radiogoose.ru/listen/midtempo/play\nhttp://stream1.radiogoose.ru/listen/midtempo/play",
"stream": "https://radiogoose.ru/listen/midtempo/play",
"type": "mp3",
"iconText": "ГУСЬ\\Мидтемпо",
"textColor": "#FF834B",
@@ -6726,7 +6726,7 @@
"name": "ГУСЬ.Транс",
"bitrate": "320",
"site": "https://radiogoose.ru/",
"stream": "http://stream.radiogoose.ru/listen/trance/play\nhttp://stream5.radiogoose.ru/listen/trance/play",
"stream": "https://radiogoose.ru/listen/trance/play",
"type": "mp3",
"iconText": "ГУСЬ\\Транс",
"textColor": "#FF834B",
@@ -6741,7 +6741,7 @@
"name": "ГУСЬ.Фонк",
"bitrate": "320",
"site": "https://radiogoose.ru/",
"stream": "http://stream.radiogoose.ru/listen/phonk/play\nhttp://stream3.radiogoose.ru/listen/phonk/play",
"stream": "https://radiogoose.ru/listen/phonk/play",
"type": "mp3",
"iconText": "ГУСЬ\\Фонк",
"textColor": "#FF834B",
@@ -6756,7 +6756,7 @@
"name": "ГУСЬ.Электро",
"bitrate": "320",
"site": "https://radiogoose.ru/",
"stream": "http://stream.radiogoose.ru/listen/electro/play\nhttp://stream4.radiogoose.ru/listen/electro/play",
"stream": "https://radiogoose.ru/listen/electro/play",
"type": "mp3",
"iconText": "ГУСЬ\\Электро",
"textColor": "#FF834B",
@@ -6771,7 +6771,7 @@
"name": "ГУСЬ.Дипхаус",
"bitrate": "320",
"site": "https://radiogoose.ru/",
"stream": "http://stream.radiogoose.ru/listen/deep/play\nhttp://stream2.radiogoose.ru/listen/deep/play",
"stream": "https://radiogoose.ru/listen/deep/play",
"type": "mp3",
"iconText": "ГУСЬ\\Дипхаус",
"textColor": "#FF834B",
@@ -6786,7 +6786,7 @@
"name": "ГУСЬ.Драм",
"bitrate": "320",
"site": "https://radiogoose.ru/",
"stream": "http://stream.radiogoose.ru/listen/dnb/play\nhttp://stream3.radiogoose.ru/listen/dnb/play",
"stream": "https://radiogoose.ru/listen/dnb/play",
"type": "mp3",
"iconText": "ГУСЬ\\Драм",
"textColor": "#FF834B",
@@ -6801,7 +6801,7 @@
"name": "ГУСЬ.Денскор",
"bitrate": "320",
"site": "https://radiogoose.ru/",
"stream": "http://stream.radiogoose.ru/listen/dancecore/play\nhttp://stream5.radiogoose.ru/listen/dancecore/play",
"stream": "https://radiogoose.ru/listen/dancecore/play",
"type": "mp3",
"iconText": "ГУСЬ\\Денскор",
"textColor": "#FF834B",
@@ -6816,7 +6816,7 @@
"name": "ГУСЬ.Технорейв",
"bitrate": "320",
"site": "https://radiogoose.ru/",
"stream": "http://stream.radiogoose.ru/listen/harddance/play\nhttp://stream1.radiogoose.ru/listen/harddance/play",
"stream": "https://radiogoose.ru/listen/harddance/play",
"type": "mp3",
"iconText": "ГУСЬ\\Технорейв",
"textColor": "#FF834B",
@@ -6831,7 +6831,7 @@
"name": "ГУСЬ.Хардстайл",
"bitrate": "320",
"site": "https://radiogoose.ru/",
"stream": "http://stream.radiogoose.ru/listen/hardstyle/play\nhttp://stream3.radiogoose.ru/listen/hardstyle/play",
"stream": "https://radiogoose.ru/listen/hardstyle/play",
"type": "mp3",
"iconText": "ГУСЬ\\Хардстайл",
"textColor": "#FF834B",
@@ -6846,7 +6846,7 @@
"name": "ГУСЬ.Хардкор",
"bitrate": "320",
"site": "https://radiogoose.ru/",
"stream": "http://stream.radiogoose.ru/listen/hardcore/play\nhttp://stream3.radiogoose.ru/listen/hardcore/play",
"stream": "https://radiogoose.ru/listen/hardcore/play",
"type": "mp3",
"iconText": "ГУСЬ\\Хардкор",
"textColor": "#E74E5D",
@@ -6861,7 +6861,7 @@
"name": "ГУСЬ.Новогодний",
"bitrate": "320",
"site": "https://radiogoose.ru/",
"stream": "http://stream.radiogoose.ru/listen/newyear/play\nhttp://stream4.radiogoose.ru/listen/newyear/play",
"stream": "https://radiogoose.ru/listen/newyear/play",
"type": "mp3",
"iconText": "ГУСЬ\\Новогодний",
"textColor": "#4D8080",

View File

@@ -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';
// Ответ AzuraCast https://radiogoose.ru/api/nowplaying/{slug}
interface AzuraNowPlaying {
is_online?: boolean;
now_playing?: {
song?: {
artist?: string;
title?: string;
art?: string;
};
};
}
/**
* Now-playing для сети ГУСЬ (radiogoose.ru) — это AzuraCast, у него штатный
* публичный API текущего трека с обложкой:
* GET https://radiogoose.ru/api/nowplaying/{slug} → now_playing.song {artist,title,art}.
* slug = сегмент потока /listen/{slug}/play. is_online из ответа поправляет
* ошибочный offline-флаг (health-check ранее спотыкался о многострочный URL).
*/
@Injectable()
export class GooseNowPlayingService {
private readonly logger = new Logger(GooseNowPlayingService.name);
private readonly headers = { 'User-Agent': 'Mozilla/5.0' };
constructor(
private readonly prisma: PrismaService,
private readonly nowPlayingService: NowPlayingService,
) {}
@Interval(30000)
async pollGooseNowPlaying() {
const stations = await this.prisma.station.findMany({
where: { streamUrl: { contains: 'radiogoose.ru' } },
});
if (stations.length === 0) return;
let updated = 0;
await Promise.allSettled(
stations.map(async (station) => {
const slug = this.extractSlug(station.streamUrl);
if (!slug) return;
const res = await fetch(
`https://radiogoose.ru/api/nowplaying/${slug}`,
{ headers: this.headers },
);
if (!res.ok) return;
const data = (await res.json()) as AzuraNowPlaying;
if (data.is_online === false) return;
const song = data.now_playing?.song;
const artist = (song?.artist ?? '').trim();
const title = (song?.title ?? '').trim();
if (!artist || !title) return;
await this.nowPlayingService.ingest({
stationDbId: station.id,
stationNumericId: station.stationId,
artist,
song: title,
coverUrl: song?.art?.trim() || null,
});
if (!station.isOnline) {
await this.prisma.station.update({
where: { id: station.id },
data: { isOnline: true },
});
}
updated++;
}),
);
this.logger.log(`Goose poll: ${updated}/${stations.length} обновлено`);
}
// https://radiogoose.ru/listen/bigroom/play → bigroom
private extractSlug(streamUrl: string): string | null {
const m = streamUrl.match(/\/listen\/([a-z0-9]+)\/play/i);
return m ? m[1].toLowerCase() : null;
}
}

View File

@@ -38,6 +38,7 @@ export class IcyNowPlayingService {
'Radio ROKS',
'Unistar',
'Zaicev FM',
'Гусь',
],
},
NOT: { streamUrl: { contains: 'emgsound.ru' } },

View File

@@ -10,6 +10,7 @@ import { LoveNowPlayingService } from './love-now-playing.service';
import { RoksNowPlayingService } from './roks-now-playing.service';
import { UnistarNowPlayingService } from './unistar-now-playing.service';
import { ZaycevNowPlayingService } from './zaycev-now-playing.service';
import { GooseNowPlayingService } from './goose-now-playing.service';
import { ChartsModule } from '../charts/charts.module';
@Module({
@@ -26,6 +27,7 @@ import { ChartsModule } from '../charts/charts.module';
RoksNowPlayingService,
UnistarNowPlayingService,
ZaycevNowPlayingService,
GooseNowPlayingService,
],
exports: [NowPlayingService],
})