diff --git a/scripts/commands/playlist/generate.ts b/scripts/commands/playlist/generate.ts index 7165c4b980..58677a59c7 100644 --- a/scripts/commands/playlist/generate.ts +++ b/scripts/commands/playlist/generate.ts @@ -16,6 +16,7 @@ import { LanguagesGenerator, RegionsGenerator, SourcesGenerator, + CitiesGenerator, IndexGenerator, RawGenerator } from '../../generators' @@ -36,7 +37,8 @@ async function main() { subdivisions, categories, countries, - regions + regions, + cities }: DataProcessorData = processor.process(data) logger.info('loading streams...') @@ -90,6 +92,13 @@ async function main() { logFile }).generate() + logger.info('generating cities/...') + await new CitiesGenerator({ + cities, + streams, + logFile + }).generate() + logger.info('generating regions/...') await new RegionsGenerator({ streams, diff --git a/scripts/commands/readme/update.ts b/scripts/commands/readme/update.ts index 86eddfe365..d47f6ba48a 100644 --- a/scripts/commands/readme/update.ts +++ b/scripts/commands/readme/update.ts @@ -1,32 +1,47 @@ -import { Logger } from '@freearhey/core' -import { - CategoryTable, - CountryTable, - LanguageTable, - RegionTable, - SubdivisionTable -} from '../../tables' -import { Markdown } from '../../core' -import { README_DIR } from '../../constants' -import path from 'path' +import { CategoriesTable, CountriesTable, LanguagesTable, RegionsTable } from '../../tables' +import { DataLoader, DataProcessor, Markdown } from '../../core' +import { DataProcessorData } from '../../types/dataProcessor' +import { DataLoaderData } from '../../types/dataLoader' +import { README_DIR, DATA_DIR, ROOT_DIR } from '../../constants' +import { Logger, Storage } from '@freearhey/core' async function main() { const logger = new Logger() + const dataStorage = new Storage(DATA_DIR) + const processor = new DataProcessor() + const loader = new DataLoader({ storage: dataStorage }) + const data: DataLoaderData = await loader.load() + const { + subdivisionsKeyByCode, + languagesKeyByCode, + countriesKeyByCode, + categoriesKeyById, + subdivisions, + countries, + regions, + cities + }: DataProcessorData = processor.process(data) logger.info('creating category table...') - await new CategoryTable().make() + await new CategoriesTable({ categoriesKeyById }).make() logger.info('creating language table...') - await new LanguageTable().make() - logger.info('creating country table...') - await new CountryTable().make() - logger.info('creating subdivision table...') - await new SubdivisionTable().make() + await new LanguagesTable({ languagesKeyByCode }).make() + logger.info('creating countires table...') + await new CountriesTable({ + countriesKeyByCode, + subdivisionsKeyByCode, + subdivisions, + countries, + cities + }).make() logger.info('creating region table...') - await new RegionTable().make() + await new RegionsTable({ regions }).make() logger.info('updating playlists.md...') - const configPath = path.join(README_DIR, 'config.json') - const playlists = new Markdown(configPath) + const playlists = new Markdown({ + build: `${ROOT_DIR}/PLAYLISTS.md`, + template: `${README_DIR}/template.md` + }) playlists.compile() } diff --git a/scripts/core/markdown.ts b/scripts/core/markdown.ts index 36834d8b7b..e229999409 100644 --- a/scripts/core/markdown.ts +++ b/scripts/core/markdown.ts @@ -1,33 +1,37 @@ import fs from 'fs' import path from 'path' -export class Markdown { - filepath: string +type MarkdownConfig = { + build: string + template: string +} - constructor(filepath: string) { - this.filepath = filepath +export class Markdown { + build: string + template: string + + constructor(config: MarkdownConfig) { + this.build = config.build + this.template = config.template } compile() { - const config = JSON.parse(fs.readFileSync(this.filepath, 'utf8')) - const workingDir = process.cwd() - - config.files.forEach((templateFile: string) => { - const templatePath = path.resolve(workingDir, templateFile) - const content = fs.readFileSync(templatePath, 'utf8') - const processedContent = this.processIncludes(content, workingDir) - - if (config.build) { - const outputPath = path.resolve(workingDir, config.build) - fs.writeFileSync(outputPath, processedContent, 'utf8') - } - }) + const workingDir = process.cwd() + + const templatePath = path.resolve(workingDir, this.template) + const template = fs.readFileSync(templatePath, 'utf8') + const processedContent = this.processIncludes(template, workingDir) + + if (this.build) { + const outputPath = path.resolve(workingDir, this.build) + fs.writeFileSync(outputPath, processedContent, 'utf8') + } } - private processIncludes(content: string, baseDir: string): string { + private processIncludes(template: string, baseDir: string): string { const includeRegex = /#include\s+"([^"]+)"/g - - return content.replace(includeRegex, (match, includePath) => { + + return template.replace(includeRegex, (match, includePath) => { try { const fullPath = path.resolve(baseDir, includePath) const includeContent = fs.readFileSync(fullPath, 'utf8') diff --git a/scripts/generators/citiesGenerator.ts b/scripts/generators/citiesGenerator.ts new file mode 100644 index 0000000000..7d0891fbc8 --- /dev/null +++ b/scripts/generators/citiesGenerator.ts @@ -0,0 +1,43 @@ +import { City, Stream, Playlist } from '../models' +import { Collection, Storage, File } from '@freearhey/core' +import { PUBLIC_DIR, EOL } from '../constants' +import { Generator } from './generator' + +type CitiesGeneratorProps = { + streams: Collection + cities: Collection + logFile: File +} + +export class CitiesGenerator implements Generator { + streams: Collection + cities: Collection + storage: Storage + logFile: File + + constructor({ streams, cities, logFile }: CitiesGeneratorProps) { + this.streams = streams.clone() + this.cities = cities + this.storage = new Storage(PUBLIC_DIR) + this.logFile = logFile + } + + async generate(): Promise { + const streams = this.streams + .orderBy((stream: Stream) => stream.getTitle()) + .filter((stream: Stream) => stream.isSFW()) + + this.cities.forEach(async (city: City) => { + const cityStreams = streams.filter((stream: Stream) => stream.isBroadcastInCity(city)) + + if (cityStreams.isEmpty()) return + + const playlist = new Playlist(cityStreams, { public: true }) + const filepath = `cities/${city.code.toLowerCase()}.m3u` + await this.storage.save(filepath, playlist.toString()) + this.logFile.append( + JSON.stringify({ type: 'city', filepath, count: playlist.streams.count() }) + EOL + ) + }) + } +} diff --git a/scripts/generators/countriesGenerator.ts b/scripts/generators/countriesGenerator.ts index 4cd539deaf..39d7612582 100644 --- a/scripts/generators/countriesGenerator.ts +++ b/scripts/generators/countriesGenerator.ts @@ -41,6 +41,18 @@ export class CountriesGenerator implements Generator { ) }) + const internationalStreams = streams.filter((stream: Stream) => stream.isInternational()) + const internationalPlaylist = new Playlist(internationalStreams, { public: true }) + const internationalFilepath = 'countries/int.m3u' + await this.storage.save(internationalFilepath, internationalPlaylist.toString()) + this.logFile.append( + JSON.stringify({ + type: 'country', + filepath: internationalFilepath, + count: internationalPlaylist.streams.count() + }) + EOL + ) + const undefinedStreams = streams.filter((stream: Stream) => !stream.hasBroadcastArea()) const undefinedPlaylist = new Playlist(undefinedStreams, { public: true }) const undefinedFilepath = 'countries/undefined.m3u' diff --git a/scripts/generators/index.ts b/scripts/generators/index.ts index e01127ba12..7f09a6715b 100644 --- a/scripts/generators/index.ts +++ b/scripts/generators/index.ts @@ -1,4 +1,5 @@ export * from './categoriesGenerator' +export * from './citiesGenerator' export * from './countriesGenerator' export * from './indexCategoryGenerator' export * from './indexCountryGenerator' diff --git a/scripts/generators/indexCountryGenerator.ts b/scripts/generators/indexCountryGenerator.ts index 7384dd8836..b476d828a8 100644 --- a/scripts/generators/indexCountryGenerator.ts +++ b/scripts/generators/indexCountryGenerator.ts @@ -26,6 +26,13 @@ export class IndexCountryGenerator 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.add(streamClone) + return + } + if (!stream.hasBroadcastArea()) { const streamClone = stream.clone() streamClone.groupTitle = 'Undefined' @@ -41,6 +48,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 diff --git a/scripts/generators/indexRegionGenerator.ts b/scripts/generators/indexRegionGenerator.ts index 43775ec91e..27a9ae0fe5 100644 --- a/scripts/generators/indexRegionGenerator.ts +++ b/scripts/generators/indexRegionGenerator.ts @@ -31,6 +31,8 @@ export class IndexRegionGenerator implements Generator { if (!stream.hasBroadcastArea()) return stream.getBroadcastRegions().forEach((region: Region) => { + if (region.isWorldwide()) return + const streamClone = stream.clone() streamClone.groupTitle = region.name groupedStreams.push(streamClone) diff --git a/scripts/generators/regionsGenerator.ts b/scripts/generators/regionsGenerator.ts index 02112974ed..60dab3d614 100644 --- a/scripts/generators/regionsGenerator.ts +++ b/scripts/generators/regionsGenerator.ts @@ -28,6 +28,8 @@ 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 }) diff --git a/scripts/models/broadcastArea.ts b/scripts/models/broadcastArea.ts index c7392984b1..fa5b43f0e5 100644 --- a/scripts/models/broadcastArea.ts +++ b/scripts/models/broadcastArea.ts @@ -31,18 +31,19 @@ export class BroadcastArea { const city: City = citiesKeyByCode.get(code) if (!city) return citiesIncluded.add(city) - if (city.subdivision) subdivisionsIncluded.add(city.subdivision) + regionsIncluded = regionsIncluded.concat(city.getRegions()) } case 's': { const subdivision: Subdivision = subdivisionsKeyByCode.get(code) if (!subdivision) return - citiesIncluded = citiesIncluded.concat(subdivision.getCities()) subdivisionsIncluded.add(subdivision) + regionsIncluded = regionsIncluded.concat(subdivision.getRegions()) } case 'c': { const country: Country = countriesKeyByCode.get(code) if (!country) return countriesIncluded.add(country) + regionsIncluded = regionsIncluded.concat(country.getRegions()) } case 'r': { const region: Region = regionsKeyByCode.get(code) diff --git a/scripts/models/feed.ts b/scripts/models/feed.ts index 7515fdc652..a6713e265a 100644 --- a/scripts/models/feed.ts +++ b/scripts/models/feed.ts @@ -1,4 +1,4 @@ -import { Country, Language, Region, Channel, Subdivision, BroadcastArea } from './index' +import { Country, Language, Region, Channel, Subdivision, BroadcastArea, City } from './index' import { Collection, Dictionary } from '@freearhey/core' import type { FeedData } from '../types/feed' @@ -120,6 +120,12 @@ export class Feed { ) } + isBroadcastInCity(city: City): boolean { + if (!this.broadcastArea) return false + + return this.broadcastArea.includesCity(city) + } + isBroadcastInSubdivision(subdivision: Subdivision): boolean { if (!this.broadcastArea) return false @@ -129,13 +135,19 @@ export class Feed { isBroadcastInCountry(country: Country): boolean { if (!this.broadcastArea) return false - return this.broadcastArea?.includesCountry(country) + return this.broadcastArea.includesCountry(country) } isBroadcastInRegion(region: Region): boolean { if (!this.broadcastArea) return false - return this.broadcastArea?.includesRegion(region) + return this.broadcastArea.includesRegion(region) + } + + isInternational(): boolean { + if (!this.broadcastArea) return false + + return this.broadcastArea.codes.join(',').includes('r/') } getGuides(): Collection { diff --git a/scripts/models/region.ts b/scripts/models/region.ts index 35e0baa78a..5fe52ad5a9 100644 --- a/scripts/models/region.ts +++ b/scripts/models/region.ts @@ -78,6 +78,10 @@ export class Region { return this.countryCodes.includes((countryCode: string) => countryCode === code) } + isWorldwide(): boolean { + return ['INT', 'WW'].includes(this.code) + } + serialize(): RegionSerializedData { return { code: this.code, diff --git a/scripts/models/stream.ts b/scripts/models/stream.ts index 395e179207..a465081ea6 100644 --- a/scripts/models/stream.ts +++ b/scripts/models/stream.ts @@ -1,4 +1,14 @@ -import { Feed, Channel, Category, Region, Subdivision, Country, Language, Logo } from './index' +import { + Feed, + Channel, + Category, + Region, + Subdivision, + Country, + Language, + Logo, + City +} from './index' import { URL, Collection, Dictionary } from '@freearhey/core' import type { StreamData } from '../types/stream' import parser from 'iptv-playlist-parser' @@ -330,6 +340,10 @@ export class Stream { return this.feed ? this.feed.broadcastAreaCodes : new Collection() } + isBroadcastInCity(city: City): boolean { + return this.feed ? this.feed.isBroadcastInCity(city) : false + } + isBroadcastInSubdivision(subdivision: Subdivision): boolean { return this.feed ? this.feed.isBroadcastInSubdivision(subdivision) : false } @@ -342,6 +356,10 @@ 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/tables/categoryTable.ts b/scripts/tables/categoriesTable.ts similarity index 61% rename from scripts/tables/categoryTable.ts rename to scripts/tables/categoriesTable.ts index f82f3ffd4a..0b763e4162 100644 --- a/scripts/tables/categoryTable.ts +++ b/scripts/tables/categoriesTable.ts @@ -1,32 +1,35 @@ -import { Storage, Collection, File } from '@freearhey/core' +import { Storage, Collection, File, Dictionary } from '@freearhey/core' import { HTMLTable, LogParser, LogItem } from '../core' +import { LOGS_DIR, README_DIR } from '../constants' import { Category } from '../models' -import { DATA_DIR, LOGS_DIR, README_DIR } from '../constants' import { Table } from './table' -export class CategoryTable implements Table { - constructor() {} +type CategoriesTableProps = { + categoriesKeyById: Dictionary +} + +export class CategoriesTable implements Table { + categoriesKeyById: Dictionary + + constructor({ categoriesKeyById }: CategoriesTableProps) { + this.categoriesKeyById = categoriesKeyById + } async make() { - const dataStorage = new Storage(DATA_DIR) - const categoriesContent = await dataStorage.json('categories.json') - const categories = new Collection(categoriesContent).map(data => new Category(data)) - const categoriesGroupedById = categories.keyBy((category: Category) => category.id) - const parser = new LogParser() const logsStorage = new Storage(LOGS_DIR) const generatorsLog = await logsStorage.load('generators.log') - let data = new Collection() + let items = new Collection() parser .parse(generatorsLog) .filter((logItem: LogItem) => logItem.type === 'category') .forEach((logItem: LogItem) => { const file = new File(logItem.filepath) const categoryId = file.name() - const category: Category = categoriesGroupedById.get(categoryId) + const category: Category = this.categoriesKeyById.get(categoryId) - data.add([ + items.add([ category ? category.name : 'ZZ', category ? category.name : 'Undefined', logItem.count, @@ -34,14 +37,14 @@ export class CategoryTable implements Table { ]) }) - data = data + items = items .orderBy(item => item[0]) .map(item => { item.shift() return item }) - const table = new HTMLTable(data.all(), [ + const table = new HTMLTable(items.all(), [ { name: 'Category' }, { name: 'Channels', align: 'right' }, { name: 'Playlist', nowrap: true } diff --git a/scripts/tables/countriesTable.ts b/scripts/tables/countriesTable.ts new file mode 100644 index 0000000000..0be11c1b3d --- /dev/null +++ b/scripts/tables/countriesTable.ts @@ -0,0 +1,189 @@ +import { Storage, Collection, Dictionary } from '@freearhey/core' +import { City, Country, Subdivision } from '../models' +import { LOGS_DIR, README_DIR } from '../constants' +import { LogParser, LogItem } from '../core' +import { Table } from './table' + +type CountriesTableProps = { + countriesKeyByCode: Dictionary + subdivisionsKeyByCode: Dictionary + countries: Collection + subdivisions: Collection + cities: Collection +} + +export class CountriesTable implements Table { + countriesKeyByCode: Dictionary + subdivisionsKeyByCode: Dictionary + countries: Collection + subdivisions: Collection + cities: Collection + + constructor({ + countriesKeyByCode, + subdivisionsKeyByCode, + countries, + subdivisions, + cities + }: CountriesTableProps) { + this.countriesKeyByCode = countriesKeyByCode + this.subdivisionsKeyByCode = subdivisionsKeyByCode + this.countries = countries + this.subdivisions = subdivisions + this.cities = cities + } + + async make() { + const parser = new LogParser() + const logsStorage = new Storage(LOGS_DIR) + const generatorsLog = await logsStorage.load('generators.log') + const parsed = parser.parse(generatorsLog) + const logCountries = parsed.filter((logItem: LogItem) => logItem.type === 'country') + const logSubdivisions = parsed.filter((logItem: LogItem) => logItem.type === 'subdivision') + const logCities = parsed.filter((logItem: LogItem) => logItem.type === 'city') + + let items = new Collection() + this.countries.forEach((country: Country) => { + const countriesLogItem = logCountries.find( + (logItem: LogItem) => logItem.filepath === `countries/${country.code.toLowerCase()}.m3u` + ) + + let countryItem = { + index: country.name, + count: 0, + link: `https://iptv-org.github.io/iptv/countries/${country.code.toLowerCase()}.m3u`, + name: `${country.flag} ${country.name}`, + children: new Collection() + } + + if (countriesLogItem) { + countryItem.count = countriesLogItem.count + } + + const countrySubdivisions = this.subdivisions.filter( + (subdivision: Subdivision) => subdivision.countryCode === country.code + ) + const countryCities = this.cities.filter((city: City) => city.countryCode === country.code) + if (countrySubdivisions.notEmpty()) { + this.subdivisions.forEach((subdivision: Subdivision) => { + if (subdivision.countryCode !== country.code) return + const subdivisionCities = countryCities.filter( + (city: City) => + (city.subdivisionCode && city.subdivisionCode === subdivision.code) || + city.countryCode === subdivision.countryCode + ) + const subdivisionsLogItem = logSubdivisions.find( + (logItem: LogItem) => + logItem.filepath === `subdivisions/${subdivision.code.toLowerCase()}.m3u` + ) + + let subdivisionItem = { + index: subdivision.name, + name: subdivision.name, + count: 0, + link: `https://iptv-org.github.io/iptv/subdivisions/${subdivision.code.toLowerCase()}.m3u`, + children: new Collection() + } + + if (subdivisionsLogItem) { + subdivisionItem.count = subdivisionsLogItem.count + } + + subdivisionCities.forEach((city: City) => { + if (city.countryCode !== country.code || city.subdivisionCode !== subdivision.code) + return + const citiesLogItem = logCities.find( + (logItem: LogItem) => logItem.filepath === `cities/${city.code.toLowerCase()}.m3u` + ) + + if (!citiesLogItem) return + + subdivisionItem.children.add({ + index: city.name, + name: city.name, + count: citiesLogItem.count, + link: `https://iptv-org.github.io/iptv/${citiesLogItem.filepath}` + }) + }) + + if (subdivisionItem.count > 0 || subdivisionItem.children.notEmpty()) { + countryItem.children.add(subdivisionItem) + } + }) + } else if (countryCities.notEmpty()) { + countryCities.forEach((city: City) => { + const citiesLogItem = logCities.find( + (logItem: LogItem) => logItem.filepath === `cities/${city.code.toLowerCase()}.m3u` + ) + + if (!citiesLogItem) return + + countryItem.children.add({ + index: city.name, + name: city.name, + count: citiesLogItem.count, + link: `https://iptv-org.github.io/iptv/${citiesLogItem.filepath}`, + children: new Collection() + }) + }) + } + + if (countryItem.count > 0 || countryItem.children.notEmpty()) { + items.add(countryItem) + } + }) + + const internationalLogItem = logCountries.find( + (logItem: LogItem) => logItem.filepath === 'countries/int.m3u' + ) + + if (internationalLogItem) { + items.push({ + index: 'ZZ', + name: '🌐 International', + count: internationalLogItem.count, + link: `https://iptv-org.github.io/iptv/${internationalLogItem.filepath}`, + children: new Collection() + }) + } + + const undefinedLogItem = logCountries.find( + (logItem: LogItem) => logItem.filepath === `countries/undefined.m3u` + ) + + if (undefinedLogItem) { + items.push({ + index: 'ZZZ', + name: 'Undefined', + count: undefinedLogItem.count, + link: `https://iptv-org.github.io/iptv/${undefinedLogItem.filepath}`, + children: new Collection() + }) + } + + items = items.orderBy(item => item.index) + + const output = items + .map(item => { + let row = `- ${item.name} ${item.link}` + + item.children + .orderBy(item => item.index) + .forEach(item => { + row += `\r\n\ - ${item.name} ${item.link}` + + item.children + .orderBy(item => item.index) + .forEach(item => { + row += `\r\n\ - ${item.name} ${item.link}` + }) + }) + + return row + }) + .join('\r\n') + + const readmeStorage = new Storage(README_DIR) + await readmeStorage.save('_countries.md', output) + } +} diff --git a/scripts/tables/countryTable.ts b/scripts/tables/countryTable.ts deleted file mode 100644 index d0075e62cd..0000000000 --- a/scripts/tables/countryTable.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Storage, Collection, File } from '@freearhey/core' -import { HTMLTable, LogParser, LogItem } from '../core' -import { Country } from '../models' -import { DATA_DIR, LOGS_DIR, README_DIR } from '../constants' -import { Table } from './table' - -export class CountryTable implements Table { - constructor() {} - - async make() { - const dataStorage = new Storage(DATA_DIR) - - const countriesContent = await dataStorage.json('countries.json') - const countries = new Collection(countriesContent).map(data => new Country(data)) - const countriesGroupedByCode = countries.keyBy((country: Country) => country.code) - - const parser = new LogParser() - const logsStorage = new Storage(LOGS_DIR) - const generatorsLog = await logsStorage.load('generators.log') - const parsed = parser.parse(generatorsLog) - - let data = new Collection() - - parsed - .filter((logItem: LogItem) => logItem.type === 'country') - .forEach((logItem: LogItem) => { - const file = new File(logItem.filepath) - const code = file.name().toUpperCase() - const [countryCode] = code.split('-') || ['', ''] - const country = countriesGroupedByCode.get(countryCode) - - if (country) { - data.add([ - country.name, - `${country.flag} ${country.name}`, - logItem.count, - `https://iptv-org.github.io/iptv/${logItem.filepath}` - ]) - } else { - data.add([ - 'ZZ', - 'Undefined', - logItem.count, - `https://iptv-org.github.io/iptv/${logItem.filepath}` - ]) - } - }) - - data = data - .orderBy(item => item[0]) - .map(item => { - item.shift() - return item - }) - - const table = new HTMLTable(data.all(), [ - { name: 'Country' }, - { name: 'Channels', align: 'right' }, - { name: 'Playlist', nowrap: true } - ]) - - const readmeStorage = new Storage(README_DIR) - await readmeStorage.save('_countries.md', table.toString()) - } -} diff --git a/scripts/tables/index.ts b/scripts/tables/index.ts index 6da33e8221..f25c0a3a27 100644 --- a/scripts/tables/index.ts +++ b/scripts/tables/index.ts @@ -1,5 +1,4 @@ -export * from './categoryTable' -export * from './countryTable' -export * from './languageTable' -export * from './regionTable' -export * from './subdivisionTable' +export * from './categoriesTable' +export * from './countriesTable' +export * from './languagesTable' +export * from './regionsTable' diff --git a/scripts/tables/languageTable.ts b/scripts/tables/languagesTable.ts similarity index 68% rename from scripts/tables/languageTable.ts rename to scripts/tables/languagesTable.ts index 2014ba6760..7621907453 100644 --- a/scripts/tables/languageTable.ts +++ b/scripts/tables/languagesTable.ts @@ -1,18 +1,21 @@ -import { Storage, Collection, File } from '@freearhey/core' +import { Storage, Collection, File, Dictionary } from '@freearhey/core' import { HTMLTable, LogParser, LogItem } from '../core' +import { LOGS_DIR, README_DIR } from '../constants' import { Language } from '../models' -import { DATA_DIR, LOGS_DIR, README_DIR } from '../constants' import { Table } from './table' -export class LanguageTable implements Table { - constructor() {} +type LanguagesTableProps = { + languagesKeyByCode: Dictionary +} + +export class LanguagesTable implements Table { + languagesKeyByCode: Dictionary + + constructor({ languagesKeyByCode }: LanguagesTableProps) { + this.languagesKeyByCode = languagesKeyByCode + } async make() { - const dataStorage = new Storage(DATA_DIR) - const languagesContent = await dataStorage.json('languages.json') - const languages = new Collection(languagesContent).map(data => new Language(data)) - const languagesGroupedByCode = languages.keyBy((language: Language) => language.code) - const parser = new LogParser() const logsStorage = new Storage(LOGS_DIR) const generatorsLog = await logsStorage.load('generators.log') @@ -24,7 +27,7 @@ export class LanguageTable implements Table { .forEach((logItem: LogItem) => { const file = new File(logItem.filepath) const languageCode = file.name() - const language: Language = languagesGroupedByCode.get(languageCode) + const language: Language = this.languagesKeyByCode.get(languageCode) data.add([ language ? language.name : 'ZZ', diff --git a/scripts/tables/regionTable.ts b/scripts/tables/regionTable.ts deleted file mode 100644 index 84eeaaa4a2..0000000000 --- a/scripts/tables/regionTable.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { Storage, Collection, File } from '@freearhey/core' -import { HTMLTable, LogParser, LogItem } from '../core' -import { Region } from '../models' -import { DATA_DIR, LOGS_DIR, README_DIR } from '../constants' -import { Table } from './table' - -export class RegionTable implements Table { - constructor() {} - - async make() { - const dataStorage = new Storage(DATA_DIR) - const regionsContent = await dataStorage.json('regions.json') - const regions = new Collection(regionsContent).map(data => new Region(data)) - const regionsGroupedByCode = regions.keyBy((region: Region) => region.code) - - const parser = new LogParser() - const logsStorage = new Storage(LOGS_DIR) - const generatorsLog = await logsStorage.load('generators.log') - - let data = new Collection() - parser - .parse(generatorsLog) - .filter((logItem: LogItem) => logItem.type === 'region') - .forEach((logItem: LogItem) => { - const file = new File(logItem.filepath) - const regionCode = file.name().toUpperCase() - const region: Region = regionsGroupedByCode.get(regionCode) - - if (region) { - data.add([ - region.name, - region.name, - logItem.count, - `https://iptv-org.github.io/iptv/${logItem.filepath}` - ]) - } else { - data.add([ - 'ZZZ', - 'Undefined', - logItem.count, - `https://iptv-org.github.io/iptv/${logItem.filepath}` - ]) - } - }) - - data = data - .orderBy(item => item[0]) - .map(item => { - item.shift() - return item - }) - - const table = new HTMLTable(data.all(), [ - { name: 'Region', align: 'left' }, - { name: 'Channels', align: 'right' }, - { name: 'Playlist', align: 'left', nowrap: true } - ]) - - const readmeStorage = new Storage(README_DIR) - await readmeStorage.save('_regions.md', table.toString()) - } -} diff --git a/scripts/tables/regionsTable.ts b/scripts/tables/regionsTable.ts new file mode 100644 index 0000000000..25f2e71bcb --- /dev/null +++ b/scripts/tables/regionsTable.ts @@ -0,0 +1,52 @@ +import { Storage, Collection, File } from '@freearhey/core' +import { HTMLTable, LogParser, LogItem } from '../core' +import { LOGS_DIR, README_DIR } from '../constants' +import { Region } from '../models' +import { Table } from './table' + +type RegionsTableProps = { + regions: Collection +} + +export class RegionsTable implements Table { + regions: Collection + + constructor({ regions }: RegionsTableProps) { + this.regions = regions + } + + async make() { + const parser = new LogParser() + const logsStorage = new Storage(LOGS_DIR) + const generatorsLog = await logsStorage.load('generators.log') + const parsed = parser.parse(generatorsLog) + const logRegions = parsed.filter((logItem: LogItem) => logItem.type === 'region') + + let items = new Collection() + this.regions.forEach((region: Region) => { + const logItem = logRegions.find( + (logItem: LogItem) => logItem.filepath === `regions/${region.code.toLowerCase()}.m3u` + ) + + if (!logItem) return + + items.add({ + index: region.name, + name: region.name, + count: logItem.count, + link: `https://iptv-org.github.io/iptv/${logItem.filepath}` + }) + }) + + items = items.orderBy(item => item.index) + + const output = items + .map(item => { + return `- ${item.name} ${item.link}` + }) + .join('\r\n') + + const readmeStorage = new Storage(README_DIR) + await readmeStorage.save('_regions.md', output) + } +} diff --git a/scripts/tables/subdivisionTable.ts b/scripts/tables/subdivisionTable.ts deleted file mode 100644 index 925d9094e3..0000000000 --- a/scripts/tables/subdivisionTable.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Storage, Collection, File } from '@freearhey/core' -import { HTMLTable, LogParser, LogItem } from '../core' -import { Country, Subdivision } from '../models' -import { DATA_DIR, LOGS_DIR, README_DIR } from '../constants' -import { Table } from './table' - -export class SubdivisionTable implements Table { - constructor() {} - - async make() { - const dataStorage = new Storage(DATA_DIR) - - const countriesContent = await dataStorage.json('countries.json') - const countries = new Collection(countriesContent).map(data => new Country(data)) - const countriesGroupedByCode = countries.keyBy((country: Country) => country.code) - const subdivisionsContent = await dataStorage.json('subdivisions.json') - const subdivisions = new Collection(subdivisionsContent).map(data => new Subdivision(data)) - const subdivisionsGroupedByCode = subdivisions.keyBy( - (subdivision: Subdivision) => subdivision.code - ) - - const parser = new LogParser() - const logsStorage = new Storage(LOGS_DIR) - const generatorsLog = await logsStorage.load('generators.log') - const parsed = parser.parse(generatorsLog) - const parsedSubdivisions = parsed.filter((logItem: LogItem) => logItem.type === 'subdivision') - - let output = '' - countries.forEach((country: Country) => { - const parsedCountrySubdivisions = parsedSubdivisions.filter((logItem: LogItem) => - logItem.filepath.includes(`subdivisions/${country.code.toLowerCase()}`) - ) - - if (!parsedCountrySubdivisions.length) return - - output += `\r\n
\r\n${country.name}\r\n` - - const data = new Collection() - - parsedCountrySubdivisions.forEach((logItem: LogItem) => { - const file = new File(logItem.filepath) - const code = file.name().toUpperCase() - const [countryCode, subdivisionCode] = code.split('-') || ['', ''] - const country = countriesGroupedByCode.get(countryCode) - - if (country && subdivisionCode) { - const subdivision = subdivisionsGroupedByCode.get(code) - if (subdivision) { - data.add([ - subdivision.name, - logItem.count, - `https://iptv-org.github.io/iptv/${logItem.filepath}` - ]) - } - } - }) - - const table = new HTMLTable(data.all(), [ - { name: 'Subdivision' }, - { name: 'Channels', align: 'right' }, - { name: 'Playlist', nowrap: true } - ]) - - output += table.toString() - - output += '\r\n
' - }) - - const readmeStorage = new Storage(README_DIR) - await readmeStorage.save('_subdivisions.md', output.trim()) - } -}