From cb0e4018543f388d39a4bbe7699d4722d9015e40 Mon Sep 17 00:00:00 2001 From: nk Date: Fri, 5 Jun 2026 20:22:28 +0300 Subject: [PATCH] =?UTF-8?q?feat(now-playing):=20=D0=93=D0=A3=D0=A1=D0=AC?= =?UTF-8?q?=20(radiogoose)=20=E2=80=94=20now-playing=20+=20=D0=BE=D0=B1?= =?UTF-8?q?=D0=BB=D0=BE=D0=B6=D0=BA=D0=B8,=20=D0=BF=D0=BE=D1=87=D0=B8?= =?UTF-8?q?=D0=BD=D0=BA=D0=B0=20=D0=BF=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Сеть ГУСЬ (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 --- prisma/stations.json | 32 +++---- src/now-playing/goose-now-playing.service.ts | 89 ++++++++++++++++++++ src/now-playing/icy-now-playing.service.ts | 1 + src/now-playing/now-playing.module.ts | 2 + 4 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 src/now-playing/goose-now-playing.service.ts diff --git a/prisma/stations.json b/prisma/stations.json index 81a41ca..41bce57 100644 --- a/prisma/stations.json +++ b/prisma/stations.json @@ -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", diff --git a/src/now-playing/goose-now-playing.service.ts b/src/now-playing/goose-now-playing.service.ts new file mode 100644 index 0000000..3091a11 --- /dev/null +++ b/src/now-playing/goose-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'; + +// Ответ 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; + } +} diff --git a/src/now-playing/icy-now-playing.service.ts b/src/now-playing/icy-now-playing.service.ts index 4736d79..538c102 100644 --- a/src/now-playing/icy-now-playing.service.ts +++ b/src/now-playing/icy-now-playing.service.ts @@ -38,6 +38,7 @@ export class IcyNowPlayingService { 'Radio ROKS', 'Unistar', 'Zaicev FM', + 'Гусь', ], }, NOT: { streamUrl: { contains: 'emgsound.ru' } }, diff --git a/src/now-playing/now-playing.module.ts b/src/now-playing/now-playing.module.ts index dacddb4..1e09789 100644 --- a/src/now-playing/now-playing.module.ts +++ b/src/now-playing/now-playing.module.ts @@ -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], })