From 63262842e737f01c1340d5df83dd9c3448cd72de Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Sat, 23 Aug 2025 17:47:03 +0300 Subject: [PATCH] Update scripts --- scripts/commands/api/load.ts | 3 +- scripts/core/dataLoader.ts | 9 +- scripts/core/dataProcessor.ts | 111 +++++++++++------ scripts/generators/countriesGenerator.ts | 12 -- scripts/generators/indexCountryGenerator.ts | 20 +-- scripts/generators/indexRegionGenerator.ts | 20 +-- scripts/generators/regionsGenerator.ts | 22 ---- scripts/models/broadcastArea.ts | 102 +++++++++++++++- scripts/models/city.ts | 78 ++++++++++++ scripts/models/country.ts | 17 ++- scripts/models/feed.ts | 127 ++++---------------- scripts/models/index.ts | 1 + scripts/models/region.ts | 55 +++++++-- scripts/models/stream.ts | 4 - scripts/models/subdivision.ts | 40 +++++- scripts/types/city.d.ts | 20 +++ scripts/types/dataLoader.d.ts | 1 + scripts/types/dataProcessor.d.ts | 3 + scripts/types/feed.d.ts | 8 +- scripts/types/region.d.ts | 5 + scripts/types/subdivision.d.ts | 4 + 21 files changed, 417 insertions(+), 245 deletions(-) create mode 100644 scripts/models/city.ts create mode 100644 scripts/types/city.d.ts diff --git a/scripts/commands/api/load.ts b/scripts/commands/api/load.ts index e4d89120a2..39cf0a2e8b 100644 --- a/scripts/commands/api/load.ts +++ b/scripts/commands/api/load.ts @@ -18,7 +18,8 @@ async function main() { loader.download('logos.json'), loader.download('timezones.json'), loader.download('guides.json'), - loader.download('streams.json') + loader.download('streams.json'), + loader.download('cities.json') ]) } diff --git a/scripts/core/dataLoader.ts b/scripts/core/dataLoader.ts index d23264769a..89b45c00c0 100644 --- a/scripts/core/dataLoader.ts +++ b/scripts/core/dataLoader.ts @@ -57,7 +57,8 @@ export class DataLoader { logos, timezones, guides, - streams + streams, + cities ] = await Promise.all([ this.storage.json('countries.json'), this.storage.json('regions.json'), @@ -70,7 +71,8 @@ export class DataLoader { this.storage.json('logos.json'), this.storage.json('timezones.json'), this.storage.json('guides.json'), - this.storage.json('streams.json') + this.storage.json('streams.json'), + this.storage.json('cities.json') ]) return { @@ -85,7 +87,8 @@ export class DataLoader { logos, timezones, guides, - streams + streams, + cities } } diff --git a/scripts/core/dataProcessor.ts b/scripts/core/dataProcessor.ts index d36569fd53..dfb796ba70 100644 --- a/scripts/core/dataProcessor.ts +++ b/scripts/core/dataProcessor.ts @@ -1,3 +1,4 @@ +import { DataProcessorData } from '../types/dataProcessor' import { DataLoaderData } from '../types/dataLoader' import { Collection } from '@freearhey/core' import { @@ -11,14 +12,16 @@ import { Region, Stream, Guide, + City, Feed, Logo } from '../models' export class DataProcessor { - constructor() {} + process(data: DataLoaderData): DataProcessorData { + let regions = new Collection(data.regions).map(data => new Region(data)) + let regionsKeyByCode = regions.keyBy((region: Region) => region.code) - process(data: DataLoaderData) { const categories = new Collection(data.categories).map(data => new Category(data)) const categoriesKeyById = categories.keyBy((category: Category) => category.id) @@ -26,25 +29,23 @@ export class DataProcessor { const languagesKeyByCode = languages.keyBy((language: Language) => language.code) let subdivisions = new Collection(data.subdivisions).map(data => new Subdivision(data)) - const subdivisionsKeyByCode = subdivisions.keyBy((subdivision: Subdivision) => subdivision.code) - const subdivisionsGroupedByCountryCode = subdivisions.groupBy( + let subdivisionsKeyByCode = subdivisions.keyBy((subdivision: Subdivision) => subdivision.code) + let subdivisionsGroupedByCountryCode = subdivisions.groupBy( (subdivision: Subdivision) => subdivision.countryCode ) - let regions = new Collection(data.regions).map(data => new Region(data)) - const regionsKeyByCode = regions.keyBy((region: Region) => region.code) + let countries = new Collection(data.countries).map(data => new Country(data)) + let countriesKeyByCode = countries.keyBy((country: Country) => country.code) - const countries = new Collection(data.countries).map(data => - new Country(data) + const cities = new Collection(data.cities).map(data => + new City(data) .withRegions(regions) - .withLanguage(languagesKeyByCode) - .withSubdivisions(subdivisionsGroupedByCountryCode) - ) - const countriesKeyByCode = countries.keyBy((country: Country) => country.code) - - subdivisions = subdivisions.map((subdivision: Subdivision) => - subdivision.withCountry(countriesKeyByCode) + .withCountry(countriesKeyByCode) + .withSubdivision(subdivisionsKeyByCode) ) + const citiesKeyByCode = cities.keyBy((city: City) => city.code) + const citiesGroupedByCountryCode = cities.groupBy((city: City) => city.countryCode) + const citiesGroupedBySubdivisionCode = cities.groupBy((city: City) => city.subdivisionCode) const timezones = new Collection(data.timezones).map(data => new Timezone(data).withCountries(countriesKeyByCode) @@ -56,27 +57,12 @@ export class DataProcessor { (blocklistRecord: BlocklistRecord) => blocklistRecord.channelId ) - let channels = new Collection(data.channels).map(data => - new Channel(data) - .withCategories(categoriesKeyById) - .withCountry(countriesKeyByCode) - .withSubdivision(subdivisionsKeyByCode) - .withCategories(categoriesKeyById) - ) + let channels = new Collection(data.channels).map(data => new Channel(data)) + let channelsKeyById = channels.keyBy((channel: Channel) => channel.id) - const channelsKeyById = channels.keyBy((channel: Channel) => channel.id) - - const feeds = new Collection(data.feeds).map(data => - new Feed(data) - .withChannel(channelsKeyById) - .withLanguages(languagesKeyByCode) - .withTimezones(timezonesKeyById) - .withBroadcastCountries(countriesKeyByCode, regionsKeyByCode, subdivisionsKeyByCode) - .withBroadcastRegions(regions) - .withBroadcastSubdivisions(subdivisionsKeyByCode) - ) - const feedsGroupedByChannelId = feeds.groupBy((feed: Feed) => feed.channelId) - const feedsGroupedById = feeds.groupBy((feed: Feed) => feed.id) + let feeds = new Collection(data.feeds).map(data => new Feed(data)) + let feedsGroupedByChannelId = feeds.groupBy((feed: Feed) => feed.channelId) + let feedsGroupedById = feeds.groupBy((feed: Feed) => feed.id) const logos = new Collection(data.logos).map(data => new Logo(data).withFeed(feedsGroupedById)) const logosGroupedByChannelId = logos.groupBy((logo: Logo) => logo.channelId) @@ -90,11 +76,60 @@ export class DataProcessor { const guides = new Collection(data.guides).map(data => new Guide(data)) const guidesGroupedByStreamId = guides.groupBy((guide: Guide) => guide.getStreamId()) - regions = regions.map((region: Region) => region.withCountries(countriesKeyByCode)) + regions = regions.map((region: Region) => + region + .withCountries(countriesKeyByCode) + .withRegions(regions) + .withSubdivisions(subdivisions) + .withCities(cities) + ) + regionsKeyByCode = regions.keyBy((region: Region) => region.code) + + countries = countries.map((country: Country) => + country + .withCities(citiesGroupedByCountryCode) + .withSubdivisions(subdivisionsGroupedByCountryCode) + .withRegions(regions) + .withLanguage(languagesKeyByCode) + ) + countriesKeyByCode = countries.keyBy((country: Country) => country.code) + + subdivisions = subdivisions.map((subdivision: Subdivision) => + subdivision + .withCities(citiesGroupedBySubdivisionCode) + .withCountry(countriesKeyByCode) + .withRegions(regions) + ) + subdivisionsKeyByCode = subdivisions.keyBy((subdivision: Subdivision) => subdivision.code) + subdivisionsGroupedByCountryCode = subdivisions.groupBy( + (subdivision: Subdivision) => subdivision.countryCode + ) channels = channels.map((channel: Channel) => - channel.withFeeds(feedsGroupedByChannelId).withLogos(logosGroupedByChannelId) + channel + .withFeeds(feedsGroupedByChannelId) + .withLogos(logosGroupedByChannelId) + .withCategories(categoriesKeyById) + .withCountry(countriesKeyByCode) + .withSubdivision(subdivisionsKeyByCode) + .withCategories(categoriesKeyById) ) + channelsKeyById = channels.keyBy((channel: Channel) => channel.id) + + feeds = feeds.map((feed: Feed) => + feed + .withChannel(channelsKeyById) + .withLanguages(languagesKeyByCode) + .withTimezones(timezonesKeyById) + .withBroadcastArea( + citiesKeyByCode, + subdivisionsKeyByCode, + countriesKeyByCode, + regionsKeyByCode + ) + ) + feedsGroupedByChannelId = feeds.groupBy((feed: Feed) => feed.channelId) + feedsGroupedById = feeds.groupBy((feed: Feed) => feed.id) return { blocklistRecordsGroupedByChannelId, @@ -111,6 +146,7 @@ export class DataProcessor { regionsKeyByCode, blocklistRecords, channelsKeyById, + citiesKeyByCode, subdivisions, categories, countries, @@ -119,6 +155,7 @@ export class DataProcessor { channels, regions, streams, + cities, guides, feeds, logos diff --git a/scripts/generators/countriesGenerator.ts b/scripts/generators/countriesGenerator.ts index 4cd539deaf..2cd5cab760 100644 --- a/scripts/generators/countriesGenerator.ts +++ b/scripts/generators/countriesGenerator.ts @@ -40,17 +40,5 @@ export class CountriesGenerator implements Generator { JSON.stringify({ type: 'country', filepath, count: playlist.streams.count() }) + EOL ) }) - - const undefinedStreams = streams.filter((stream: Stream) => !stream.hasBroadcastArea()) - const undefinedPlaylist = new Playlist(undefinedStreams, { public: true }) - const undefinedFilepath = 'countries/undefined.m3u' - await this.storage.save(undefinedFilepath, undefinedPlaylist.toString()) - this.logFile.append( - JSON.stringify({ - type: 'country', - filepath: undefinedFilepath, - count: undefinedPlaylist.streams.count() - }) + EOL - ) } } diff --git a/scripts/generators/indexCountryGenerator.ts b/scripts/generators/indexCountryGenerator.ts index 43e55c2e28..c7e5e2f848 100644 --- a/scripts/generators/indexCountryGenerator.ts +++ b/scripts/generators/indexCountryGenerator.ts @@ -26,18 +26,7 @@ export class IndexCountryGenerator implements Generator { .orderBy((stream: Stream) => stream.getTitle()) .filter((stream: Stream) => stream.isSFW()) .forEach((stream: Stream) => { - if (!stream.hasBroadcastArea()) { - const streamClone = stream.clone() - streamClone.groupTitle = 'Undefined' - groupedStreams.add(streamClone) - return - } - - if (stream.isInternational()) { - const streamClone = stream.clone() - streamClone.groupTitle = 'International' - groupedStreams.add(streamClone) - } + if (!stream.hasBroadcastArea()) return stream.getBroadcastCountries().forEach((country: Country) => { const streamClone = stream.clone() @@ -46,12 +35,7 @@ export class IndexCountryGenerator implements Generator { }) }) - groupedStreams = groupedStreams.orderBy((stream: Stream) => { - if (stream.groupTitle === 'International') return 'ZZ' - if (stream.groupTitle === 'Undefined') return 'ZZZ' - - return stream.groupTitle - }) + groupedStreams = groupedStreams.orderBy((stream: Stream) => stream.groupTitle) const playlist = new Playlist(groupedStreams, { public: true }) const filepath = 'index.country.m3u' diff --git a/scripts/generators/indexRegionGenerator.ts b/scripts/generators/indexRegionGenerator.ts index 3155d37d30..43775ec91e 100644 --- a/scripts/generators/indexRegionGenerator.ts +++ b/scripts/generators/indexRegionGenerator.ts @@ -28,19 +28,7 @@ export class IndexRegionGenerator implements Generator { .orderBy((stream: Stream) => stream.getTitle()) .filter((stream: Stream) => stream.isSFW()) .forEach((stream: Stream) => { - if (stream.isInternational()) { - const streamClone = stream.clone() - streamClone.groupTitle = 'International' - groupedStreams.push(streamClone) - return - } - - if (!stream.hasBroadcastArea()) { - const streamClone = stream.clone() - streamClone.groupTitle = 'Undefined' - groupedStreams.push(streamClone) - return - } + if (!stream.hasBroadcastArea()) return stream.getBroadcastRegions().forEach((region: Region) => { const streamClone = stream.clone() @@ -49,11 +37,7 @@ export class IndexRegionGenerator implements Generator { }) }) - groupedStreams = groupedStreams.orderBy((stream: Stream) => { - if (stream.groupTitle === 'International') return 'ZZ' - if (stream.groupTitle === 'Undefined') return 'ZZZ' - return stream.groupTitle - }) + groupedStreams = groupedStreams.orderBy((stream: Stream) => stream.groupTitle) const playlist = new Playlist(groupedStreams, { public: true }) const filepath = 'index.region.m3u' diff --git a/scripts/generators/regionsGenerator.ts b/scripts/generators/regionsGenerator.ts index 6711c67ff8..02112974ed 100644 --- a/scripts/generators/regionsGenerator.ts +++ b/scripts/generators/regionsGenerator.ts @@ -28,8 +28,6 @@ export class RegionsGenerator implements Generator { .filter((stream: Stream) => stream.isSFW()) this.regions.forEach(async (region: Region) => { - if (region.isWorldwide()) return - const regionStreams = streams.filter((stream: Stream) => stream.isBroadcastInRegion(region)) const playlist = new Playlist(regionStreams, { public: true }) @@ -39,25 +37,5 @@ export class RegionsGenerator implements Generator { JSON.stringify({ type: 'region', filepath, count: playlist.streams.count() }) + EOL ) }) - - const internationalStreams = streams.filter((stream: Stream) => stream.isInternational()) - const internationalPlaylist = new Playlist(internationalStreams, { public: true }) - const internationalFilepath = 'regions/int.m3u' - await this.storage.save(internationalFilepath, internationalPlaylist.toString()) - this.logFile.append( - JSON.stringify({ - type: 'region', - filepath: internationalFilepath, - count: internationalPlaylist.streams.count() - }) + EOL - ) - - const undefinedStreams = streams.filter((stream: Stream) => !stream.hasBroadcastArea()) - const playlist = new Playlist(undefinedStreams, { public: true }) - const filepath = 'regions/undefined.m3u' - await this.storage.save(filepath, playlist.toString()) - this.logFile.append( - JSON.stringify({ type: 'region', filepath, count: playlist.streams.count() }) + EOL - ) } } diff --git a/scripts/models/broadcastArea.ts b/scripts/models/broadcastArea.ts index 2b96b7f91f..8949b375dd 100644 --- a/scripts/models/broadcastArea.ts +++ b/scripts/models/broadcastArea.ts @@ -1,11 +1,101 @@ -type BroadcastAreaProps = { - code: string -} +import { Collection, Dictionary } from '@freearhey/core' +import { City, Subdivision, Region, Country } from './' export class BroadcastArea { - code: string + codes: Collection + citiesIncluded: Collection + subdivisionsIncluded: Collection + countriesIncluded: Collection + regionsIncluded: Collection - constructor(data: BroadcastAreaProps) { - this.code = data.code + constructor(codes: Collection) { + this.codes = codes + } + + withLocations( + citiesKeyByCode: Dictionary, + subdivisionsKeyByCode: Dictionary, + countriesKeyByCode: Dictionary, + regionsKeyByCode: Dictionary + ): this { + let citiesIncluded = new Collection() + let subdivisionsIncluded = new Collection() + let countriesIncluded = new Collection() + let regionsIncluded = new Collection() + + this.codes.forEach((value: string) => { + const [type, code] = value.split('/') + + switch (type) { + case 'ct': { + const city: City = citiesKeyByCode.get(code) + if (!city) return + citiesIncluded.add(city) + } + case 's': { + const subdivision: Subdivision = subdivisionsKeyByCode.get(code) + if (!subdivision) return + citiesIncluded = citiesIncluded.concat(subdivision.getCities()) + subdivisionsIncluded.add(subdivision) + } + case 'c': { + const country: Country = countriesKeyByCode.get(code) + if (!country) return + citiesIncluded = citiesIncluded.concat(country.getCities()) + subdivisionsIncluded = subdivisionsIncluded.concat(country.getSubdivisions()) + countriesIncluded.add(country) + regionsIncluded = regionsIncluded.concat(country.getRegions()) + } + case 'r': { + const region: Region = regionsKeyByCode.get(code) + if (!region) return + countriesIncluded = countriesIncluded.concat(region.getCountries()) + regionsIncluded = regionsIncluded.concat(region.getRegions()) + } + } + }) + + this.citiesIncluded = citiesIncluded.uniqBy((city: City) => city.code) + this.subdivisionsIncluded = subdivisionsIncluded.uniqBy( + (subdivision: Subdivision) => subdivision.code + ) + this.countriesIncluded = countriesIncluded.uniqBy((country: Country) => country.code) + this.regionsIncluded = regionsIncluded.uniqBy((region: Region) => region.code) + + return this + } + + getCountries(): Collection { + return this.countriesIncluded || new Collection() + } + + getSubdivisions(): Collection { + return this.subdivisionsIncluded || new Collection() + } + + getCities(): Collection { + return this.citiesIncluded || new Collection() + } + + getRegions(): Collection { + return this.regionsIncluded || new Collection() + } + + includesCountry(country: Country): boolean { + return this.getCountries().includes((_country: Country) => _country.code === country.code) + } + + includesSubdivision(subdivision: Subdivision): boolean { + return this.getSubdivisions().includes( + (_subdivision: Subdivision) => _subdivision.code === subdivision.code + ) + } + + includesRegion(region: Region): boolean { + return this.getRegions().includes((_region: Region) => _region.code === region.code) + } + + includesCity(city: City): boolean { + return this.getCities().includes((_city: City) => _city.code === city.code) } } diff --git a/scripts/models/city.ts b/scripts/models/city.ts new file mode 100644 index 0000000000..6ce9173ac9 --- /dev/null +++ b/scripts/models/city.ts @@ -0,0 +1,78 @@ +import { Collection, Dictionary } from '@freearhey/core' +import { Country, Region, Subdivision } from '.' +import type { CityData, CitySerializedData } from '../types/city' + +export class City { + code: string + name: string + countryCode: string + country?: Country + subdivisionCode?: string + subdivision?: Subdivision + wikidataId: string + regions?: Collection + + constructor(data?: CityData) { + if (!data) return + + this.code = data.code + this.name = data.name + this.countryCode = data.country + this.subdivisionCode = data.subdivision || undefined + this.wikidataId = data.wikidata_id + } + + withCountry(countriesKeyByCode: Dictionary): this { + this.country = countriesKeyByCode.get(this.countryCode) + + return this + } + + withSubdivision(subdivisionsKeyByCode: Dictionary): this { + if (!this.subdivisionCode) return this + + this.subdivision = subdivisionsKeyByCode.get(this.subdivisionCode) + + return this + } + + withRegions(regions: Collection): this { + this.regions = regions.filter((region: Region) => + region.countryCodes.includes(this.countryCode) + ) + + return this + } + + getRegions(): Collection { + if (!this.regions) return new Collection() + + return this.regions + } + + serialize(): CitySerializedData { + return { + code: this.code, + name: this.name, + countryCode: this.countryCode, + country: this.country ? this.country.serialize() : undefined, + subdivisionCode: this.subdivisionCode || null, + subdivision: this.subdivision ? this.subdivision.serialize() : undefined, + wikidataId: this.wikidataId + } + } + + deserialize(data: CitySerializedData): this { + this.code = data.code + this.name = data.name + this.countryCode = data.countryCode + this.country = data.country ? new Country().deserialize(data.country) : undefined + this.subdivisionCode = data.subdivisionCode || undefined + this.subdivision = data.subdivision + ? new Subdivision().deserialize(data.subdivision) + : undefined + this.wikidataId = data.wikidataId + + return this + } +} diff --git a/scripts/models/country.ts b/scripts/models/country.ts index 780c4413f1..b9699f7235 100644 --- a/scripts/models/country.ts +++ b/scripts/models/country.ts @@ -12,6 +12,7 @@ export class Country { language?: Language subdivisions?: Collection regions?: Collection + cities?: Collection constructor(data?: CountryData) { if (!data) return @@ -23,15 +24,19 @@ export class Country { } withSubdivisions(subdivisionsGroupedByCountryCode: Dictionary): this { - this.subdivisions = subdivisionsGroupedByCountryCode.get(this.code) || new Collection() + this.subdivisions = new Collection(subdivisionsGroupedByCountryCode.get(this.code)) return this } withRegions(regions: Collection): this { - this.regions = regions.filter( - (region: Region) => region.code !== 'INT' && region.includesCountryCode(this.code) - ) + this.regions = regions.filter((region: Region) => region.includesCountryCode(this.code)) + + return this + } + + withCities(citiesGroupedByCountryCode: Dictionary): this { + this.cities = new Collection(citiesGroupedByCountryCode.get(this.code)) return this } @@ -54,6 +59,10 @@ export class Country { return this.subdivisions || new Collection() } + getCities(): Collection { + return this.cities || new Collection() + } + serialize(): CountrySerializedData { return { code: this.code, diff --git a/scripts/models/feed.ts b/scripts/models/feed.ts index f42c4af916..7515fdc652 100644 --- a/scripts/models/feed.ts +++ b/scripts/models/feed.ts @@ -1,4 +1,4 @@ -import { Country, Language, Region, Channel, Subdivision } from './index' +import { Country, Language, Region, Channel, Subdivision, BroadcastArea } from './index' import { Collection, Dictionary } from '@freearhey/core' import type { FeedData } from '../types/feed' @@ -9,12 +9,7 @@ export class Feed { name: string isMain: boolean broadcastAreaCodes: Collection - broadcastCountryCodes: Collection - broadcastCountries?: Collection - broadcastRegionCodes: Collection - broadcastRegions?: Collection - broadcastSubdivisionCodes: Collection - broadcastSubdivisions?: Collection + broadcastArea?: BroadcastArea languageCodes: Collection languages?: Collection timezoneIds: Collection @@ -32,25 +27,6 @@ export class Feed { this.languageCodes = new Collection(data.languages) this.timezoneIds = new Collection(data.timezones) this.videoFormat = data.video_format - this.broadcastCountryCodes = new Collection() - this.broadcastRegionCodes = new Collection() - this.broadcastSubdivisionCodes = new Collection() - - this.broadcastAreaCodes.forEach((areaCode: string) => { - const [type, code] = areaCode.split('/') - - switch (type) { - case 'c': - this.broadcastCountryCodes.add(code) - break - case 'r': - this.broadcastRegionCodes.add(code) - break - case 's': - this.broadcastSubdivisionCodes.add(code) - break - } - }) } withChannel(channelsKeyById: Dictionary): this { @@ -93,76 +69,36 @@ export class Feed { return this } - withBroadcastSubdivisions(subdivisionsKeyByCode: Dictionary): this { - this.broadcastSubdivisions = this.broadcastSubdivisionCodes.map((code: string) => - subdivisionsKeyByCode.get(code) - ) - - return this - } - - withBroadcastCountries( + withBroadcastArea( + citiesKeyByCode: Dictionary, + subdivisionsKeyByCode: Dictionary, countriesKeyByCode: Dictionary, - regionsKeyByCode: Dictionary, - subdivisionsKeyByCode: Dictionary + regionsKeyByCode: Dictionary ): this { - const broadcastCountries = new Collection() - - if (this.isInternational()) { - this.broadcastCountries = broadcastCountries - return this - } - - this.broadcastCountryCodes.forEach((code: string) => { - broadcastCountries.add(countriesKeyByCode.get(code)) - }) - - this.broadcastRegionCodes.forEach((code: string) => { - const region: Region = regionsKeyByCode.get(code) - if (region) { - region.countryCodes.forEach((countryCode: string) => { - broadcastCountries.add(countriesKeyByCode.get(countryCode)) - }) - } - }) - - this.broadcastSubdivisionCodes.forEach((code: string) => { - const subdivision: Subdivision = subdivisionsKeyByCode.get(code) - if (subdivision) { - broadcastCountries.add(countriesKeyByCode.get(subdivision.countryCode)) - } - }) - - this.broadcastCountries = broadcastCountries.uniq().filter(Boolean) - - return this - } - - withBroadcastRegions(regions: Collection): this { - if (!this.broadcastCountries) return this - const countriesCodes = this.broadcastCountries.map((country: Country) => country.code) - - this.broadcastRegions = regions.filter((region: Region) => { - if (region.code === 'INT') return false - const intersected = region.countryCodes.intersects(countriesCodes) - return intersected.notEmpty() - }) + this.broadcastArea = new BroadcastArea(this.broadcastAreaCodes).withLocations( + citiesKeyByCode, + subdivisionsKeyByCode, + countriesKeyByCode, + regionsKeyByCode + ) return this } hasBroadcastArea(): boolean { - return ( - this.isInternational() || (!!this.broadcastCountries && this.broadcastCountries.notEmpty()) - ) + return !!this.broadcastArea } getBroadcastCountries(): Collection { - return this.broadcastCountries || new Collection() + if (!this.broadcastArea) return new Collection() + + return this.broadcastArea.getCountries() } getBroadcastRegions(): Collection { - return this.broadcastRegions || new Collection() + if (!this.broadcastArea) return new Collection() + + return this.broadcastArea.getRegions() } getTimezones(): Collection { @@ -184,35 +120,22 @@ export class Feed { ) } - isInternational(): boolean { - return this.broadcastAreaCodes.includes('r/INT') - } - isBroadcastInSubdivision(subdivision: Subdivision): boolean { - if (this.isInternational()) return false - if (this.broadcastSubdivisionCodes.includes(subdivision.code)) return true - if ( - this.broadcastSubdivisionCodes.isEmpty() && - subdivision.country && - this.isBroadcastInCountry(subdivision.country) - ) - return true + if (!this.broadcastArea) return false - return false + return this.broadcastArea.includesSubdivision(subdivision) } isBroadcastInCountry(country: Country): boolean { - if (this.isInternational()) return false + if (!this.broadcastArea) return false - return this.getBroadcastCountries().includes( - (_country: Country) => _country.code === country.code - ) + return this.broadcastArea?.includesCountry(country) } isBroadcastInRegion(region: Region): boolean { - if (this.isInternational()) return false + if (!this.broadcastArea) return false - return this.getBroadcastRegions().includes((_region: Region) => _region.code === region.code) + return this.broadcastArea?.includesRegion(region) } getGuides(): Collection { diff --git a/scripts/models/index.ts b/scripts/models/index.ts index 4e11d28b97..e8fe346289 100644 --- a/scripts/models/index.ts +++ b/scripts/models/index.ts @@ -2,6 +2,7 @@ export * from './blocklistRecord' export * from './broadcastArea' export * from './category' export * from './channel' +export * from './city' export * from './country' export * from './feed' export * from './guide' diff --git a/scripts/models/region.ts b/scripts/models/region.ts index ace44bc52f..35e0baa78a 100644 --- a/scripts/models/region.ts +++ b/scripts/models/region.ts @@ -1,15 +1,18 @@ import { Collection, Dictionary } from '@freearhey/core' -import { Country, Subdivision } from '.' +import { City, Country, Subdivision } from '.' import type { RegionData, RegionSerializedData } from '../types/region' import { CountrySerializedData } from '../types/country' import { SubdivisionSerializedData } from '../types/subdivision' +import { CitySerializedData } from '../types/city' export class Region { code: string name: string countryCodes: Collection - countries: Collection = new Collection() - subdivisions: Collection = new Collection() + countries?: Collection + subdivisions?: Collection + cities?: Collection + regions?: Collection constructor(data?: RegionData) { if (!data) return @@ -33,30 +36,61 @@ export class Region { return this } + withCities(cities: Collection): this { + this.cities = cities.filter((city: City) => this.countryCodes.indexOf(city.countryCode) > -1) + + return this + } + + withRegions(regions: Collection): this { + this.regions = regions.filter( + (region: Region) => !region.countryCodes.intersects(this.countryCodes).isEmpty() + ) + + return this + } + getSubdivisions(): Collection { + if (!this.subdivisions) return new Collection() + return this.subdivisions } getCountries(): Collection { + if (!this.countries) return new Collection() + return this.countries } + getCities(): Collection { + if (!this.cities) return new Collection() + + return this.cities + } + + getRegions(): Collection { + if (!this.regions) return new Collection() + + return this.regions + } + includesCountryCode(code: string): boolean { return this.countryCodes.includes((countryCode: string) => countryCode === code) } - isWorldwide(): boolean { - return this.code === 'INT' - } - serialize(): RegionSerializedData { return { code: this.code, name: this.name, countryCodes: this.countryCodes.all(), - countries: this.countries.map((country: Country) => country.serialize()).all(), - subdivisions: this.subdivisions + countries: this.getCountries() + .map((country: Country) => country.serialize()) + .all(), + subdivisions: this.getSubdivisions() .map((subdivision: Subdivision) => subdivision.serialize()) + .all(), + cities: this.getCities() + .map((city: City) => city.serialize()) .all() } } @@ -71,6 +105,9 @@ export class Region { this.subdivisions = new Collection(data.subdivisions).map((data: SubdivisionSerializedData) => new Subdivision().deserialize(data) ) + this.cities = new Collection(data.cities).map((data: CitySerializedData) => + new City().deserialize(data) + ) return this } diff --git a/scripts/models/stream.ts b/scripts/models/stream.ts index cd734493b8..395e179207 100644 --- a/scripts/models/stream.ts +++ b/scripts/models/stream.ts @@ -342,10 +342,6 @@ export class Stream { return this.feed ? this.feed.isBroadcastInRegion(region) : false } - isInternational(): boolean { - return this.feed ? this.feed.isInternational() : false - } - getLogos(): Collection { function format(logo: Logo): number { const levelByFormat = { SVG: 0, PNG: 3, APNG: 1, WebP: 1, AVIF: 1, JPEG: 2, GIF: 1 } diff --git a/scripts/models/subdivision.ts b/scripts/models/subdivision.ts index b43d1c88d7..92cfdc9d61 100644 --- a/scripts/models/subdivision.ts +++ b/scripts/models/subdivision.ts @@ -1,12 +1,15 @@ import { SubdivisionData, SubdivisionSerializedData } from '../types/subdivision' -import { Dictionary } from '@freearhey/core' -import { Country } from '.' +import { Dictionary, Collection } from '@freearhey/core' +import { Country, Region } from '.' export class Subdivision { code: string name: string countryCode: string country?: Country + parentCode?: string + regions?: Collection + cities?: Collection constructor(data?: SubdivisionData) { if (!data) return @@ -14,6 +17,7 @@ export class Subdivision { this.code = data.code this.name = data.name this.countryCode = data.country + this.parentCode = data.parent || undefined } withCountry(countriesKeyByCode: Dictionary): this { @@ -22,12 +26,39 @@ export class Subdivision { return this } + withRegions(regions: Collection): this { + this.regions = regions.filter((region: Region) => + region.countryCodes.includes(this.countryCode) + ) + + return this + } + + withCities(citiesGroupedBySubdivisionCode: Dictionary): this { + this.cities = new Collection(citiesGroupedBySubdivisionCode.get(this.code)) + + return this + } + + getRegions(): Collection { + if (!this.regions) return new Collection() + + return this.regions + } + + getCities(): Collection { + if (!this.cities) return new Collection() + + return this.cities + } + serialize(): SubdivisionSerializedData { return { code: this.code, name: this.name, - countryCode: this.code, - country: this.country ? this.country.serialize() : undefined + countryCode: this.countryCode, + country: this.country ? this.country.serialize() : undefined, + parentCode: this.parentCode || null } } @@ -36,6 +67,7 @@ export class Subdivision { this.name = data.name this.countryCode = data.countryCode this.country = data.country ? new Country().deserialize(data.country) : undefined + this.parentCode = data.parentCode || undefined return this } diff --git a/scripts/types/city.d.ts b/scripts/types/city.d.ts new file mode 100644 index 0000000000..5c33ba5a9e --- /dev/null +++ b/scripts/types/city.d.ts @@ -0,0 +1,20 @@ +import { CountrySerializedData } from './country' +import { SubdivisionSerializedData } from './subdivision' + +export type CitySerializedData = { + code: string + name: string + countryCode: string + country?: CountrySerializedData + subdivisionCode: string | null + subdivision?: SubdivisionSerializedData + wikidataId: string +} + +export type CityData = { + code: string + name: string + country: string + subdivision: string | null + wikidata_id: string +} diff --git a/scripts/types/dataLoader.d.ts b/scripts/types/dataLoader.d.ts index ee7ab0f937..708361de99 100644 --- a/scripts/types/dataLoader.d.ts +++ b/scripts/types/dataLoader.d.ts @@ -17,4 +17,5 @@ export type DataLoaderData = { timezones: object | object[] guides: object | object[] streams: object | object[] + cities: object | object[] } diff --git a/scripts/types/dataProcessor.d.ts b/scripts/types/dataProcessor.d.ts index 25f21d1aac..bc76dc28b4 100644 --- a/scripts/types/dataProcessor.d.ts +++ b/scripts/types/dataProcessor.d.ts @@ -15,6 +15,7 @@ export type DataProcessorData = { regionsKeyByCode: Dictionary blocklistRecords: Collection channelsKeyById: Dictionary + citiesKeyByCode: Dictionary subdivisions: Collection categories: Collection countries: Collection @@ -23,6 +24,8 @@ export type DataProcessorData = { channels: Collection regions: Collection streams: Collection + cities: Collection guides: Collection feeds: Collection + logos: Collection } diff --git a/scripts/types/feed.d.ts b/scripts/types/feed.d.ts index 5c6722dde2..ef4aea4669 100644 --- a/scripts/types/feed.d.ts +++ b/scripts/types/feed.d.ts @@ -1,12 +1,10 @@ -import { Collection } from '@freearhey/core' - export type FeedData = { channel: string id: string name: string is_main: boolean - broadcast_area: Collection - languages: Collection - timezones: Collection + broadcast_area: string[] + languages: string[] + timezones: string[] video_format: string } diff --git a/scripts/types/region.d.ts b/scripts/types/region.d.ts index e6773429ee..798224ee7f 100644 --- a/scripts/types/region.d.ts +++ b/scripts/types/region.d.ts @@ -1,9 +1,14 @@ +import { CitySerializedData } from './city' +import { CountrySerializedData } from './country' +import { SubdivisionSerializedData } from './subdivision' + export type RegionSerializedData = { code: string name: string countryCodes: string[] countries?: CountrySerializedData[] subdivisions?: SubdivisionSerializedData[] + cities?: CitySerializedData[] } export type RegionData = { diff --git a/scripts/types/subdivision.d.ts b/scripts/types/subdivision.d.ts index bf46831f72..b2a25982dd 100644 --- a/scripts/types/subdivision.d.ts +++ b/scripts/types/subdivision.d.ts @@ -1,12 +1,16 @@ +import { CountrySerializedData } from './country' + export type SubdivisionSerializedData = { code: string name: string countryCode: string country?: CountrySerializedData + parentCode: string | null } export type SubdivisionData = { code: string name: string country: string + parent: string | null }