diff --git a/src/charts/charts.service.ts b/src/charts/charts.service.ts index 53b4b80..8e5af83 100644 --- a/src/charts/charts.service.ts +++ b/src/charts/charts.service.ts @@ -2,6 +2,18 @@ import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { EnrichmentService } from '../enrich/enrichment.service'; +// Жанры, исключённые из чарта: разговорные/шуточные/без названий треков. +// Их «треки» — это названия передач/реприз/спектаклей, не музыка. +const EXCLUDED_CHART_GENRES = [ + 'Станция Кассиопея', + 'Юмор ФМ', + 'Рассказы', + 'Радио Вера', + 'Comedy Radio', + 'ВГТРК', + 'Старое радио', +]; + // Период чарта export type ChartPeriod = 'day' | 'week' | 'month' | 'all'; @@ -109,6 +121,35 @@ export class ChartsService { return this.stationNames; } + // Кэш id станций исключённых жанров (разговорные/шуточные — не в чарт) + private excludedStationIds = new Set(); + private excludedStationIdsAt = 0; + + private async getExcludedStationIds(): Promise> { + const now = Date.now(); + if (now - this.excludedStationIdsAt > 10 * 60 * 1000 || this.excludedStationIds.size === 0) { + const rows = await this.prisma.station.findMany({ + where: { genre: { in: EXCLUDED_CHART_GENRES } }, + select: { id: true }, + }); + this.excludedStationIds = new Set(rows.map((r) => r.id)); + this.excludedStationIdsAt = now; + } + return this.excludedStationIds; + } + + // Мусорный «трек»: хекс-плейсхолдер, URL, числовой код, или artist == song + // (целый тайтл без нормального разбиения на исполнителя/название). + private isJunkTrack(artist: string, song: string): boolean { + const a = artist.trim(); + const s = song.trim(); + if (a.toLowerCase() === s.toLowerCase()) return true; + const hex = (v: string) => /^[0-9a-f]{6,}$/i.test(v) && /[0-9]/.test(v); + const code = (v: string) => /^[0-9]+-[0-9]+/.test(v); + const url = (v: string) => /\.(ru|fm|by|com|ua)$/i.test(v) || /^https?:/i.test(v); + return [a, s].some((v) => hex(v) || code(v) || url(v)); + } + // Записывает факт смены трека на станции (вызывается из NowPlayingService) async recordPlay(params: RecordPlayParams): Promise { try { @@ -119,6 +160,11 @@ export class ChartsService { // Отсекаем не-музыкальные записи: пустые поля, либо когда артист/песня // совпадает с названием станции (это джинглы, шоу, сетевые промо). if (!artist || !song) return; + // Разговорные/шуточные станции — не в чарт. + const excluded = await this.getExcludedStationIds(); + if (excluded.has(stationDbId)) return; + // Мусорные названия (хекс/URL/код/artist==song) — не в чарт. + if (this.isJunkTrack(artist, song)) return; const stationNames = await this.getStationNames(); if ( stationNames.has(artist.toLowerCase()) ||