diff --git a/scripts/commands/guides/update.ts b/scripts/commands/guides/update.ts new file mode 100644 index 00000000..f43b96b6 --- /dev/null +++ b/scripts/commands/guides/update.ts @@ -0,0 +1,144 @@ +import { HTMLTableRow, HTMLTableDataItem, HTMLTableColumn } from '../../types/htmlTable' +import epgGrabber, { EPGGrabber } from 'epg-grabber' +import AxiosMockAdapter from 'axios-mock-adapter' +import { Storage } from '@freearhey/storage-js' +import { Channel, Worker } from '../../models' +import { Collection } from '@freearhey/core' +import { ROOT_DIR } from '../../constants' +import { Logger } from '@freearhey/core' +import { HTMLTable } from '../../core' +import epgParser from 'epg-parser' +import axios from 'axios' + +async function main() { + const logger = new Logger({ level: process.env.NODE_ENV === 'test' ? -999 : 3 }) + const rootStorage = new Storage(ROOT_DIR) + const workers = new Map() + + logger.info('loading workers.txt...') + const workersTxt = await rootStorage.load('workers.txt') + + workersTxt.split('\r\n').forEach((host: string) => { + if (!host) return + + const worker = new Worker({ host }) + + workers.set(host, worker) + }) + + for (const worker of workers.values()) { + logger.info(`processing "${worker.host}"...`) + + const client = axios.create({ + baseURL: worker.getBaseUrl(), + timeout: 60000 + }) + + if (process.env.NODE_ENV === 'test') { + const mock = new AxiosMockAdapter(client) + if (worker.host === 'example.com') { + mock.onGet('worker.json').reply(404) + } else { + const testStorage = new Storage('tests/__data__/input/guides_update') + mock.onGet('worker.json').reply(200, await testStorage.load('worker.json')) + mock.onGet('channels.xml').reply(200, await testStorage.load('channels.xml')) + mock.onGet('guide.xml').reply(200, await testStorage.load('guide.xml')) + } + } + + const workerJson = await client + .get('worker.json') + .then(res => res.data) + .catch(err => { + worker.status = err.status + logger.error(err.message) + }) + + if (!workerJson) { + worker.status = 'MISSING_WORKER_CONFIG' + logger.error('Unable to load "workers.json"') + continue + } + + worker.channelsPath = workerJson.channels + worker.guidePath = workerJson.guide + + if (!worker.channelsPath) { + worker.status = 'MISSING_CHANNELS_PATH' + logger.error('The "channels" property is missing from the workers config') + continue + } + + if (!worker.guidePath) { + worker.status = 'MISSING_GUIDE_PATH' + logger.error('The "guide" property is missing from the workers config') + continue + } + + const channelsXml = await client + .get(worker.channelsPath) + .then(res => res.data) + .catch(err => { + worker.status = err.status + logger.error(err.message) + }) + + if (!channelsXml) continue + + const parsedChannels = EPGGrabber.parseChannelsXML(channelsXml) + worker.channels = new Collection(parsedChannels).map( + (channel: epgGrabber.Channel) => new Channel(channel.toObject()) + ) + + const guideXml = await client + .get(worker.guidePath) + .then(res => res.data) + .catch(err => { + worker.status = err.status + logger.error(err.message) + }) + + if (!guideXml) continue + + const parsedGuide = epgParser.parse(guideXml) + worker.lastUpdated = parsedGuide.date + + worker.status = 'OK' + } + + logger.info('creating guides table...') + const rows = new Collection() + workers.forEach((worker: Worker) => { + rows.add( + new Collection([ + { value: worker.host }, + { value: worker.getStatusEmoji(), align: 'center' }, + { value: worker.getChannelsCount().toString(), align: 'right' }, + { value: worker.getLastUpdated(), align: 'left' }, + { + value: + worker.status === 'OK' + ? `${worker.channelsPath}
${worker.guidePath}` + : '' + } + ]) + ) + }) + + logger.info('updating guides.md...') + const table = new HTMLTable( + rows, + new Collection([ + { name: 'Host', align: 'left' }, + { name: 'Status', align: 'left' }, + { name: 'Channels', align: 'left' }, + { name: 'Last Updated', align: 'left' }, + { name: 'Links', align: 'left' } + ]) + ) + const guidesTemplate = await new Storage().load('scripts/templates/_guides.md') + const guidesContent = guidesTemplate.replace('_TABLE_', table.toString()) + await rootStorage.save('GUIDES.md', guidesContent) +} + +main() diff --git a/scripts/models/index.ts b/scripts/models/index.ts index a05f13a8..c891f01b 100644 --- a/scripts/models/index.ts +++ b/scripts/models/index.ts @@ -3,3 +3,4 @@ export * from './issue' export * from './site' export * from './channel' export * from './program' +export * from './worker' diff --git a/scripts/models/worker.ts b/scripts/models/worker.ts new file mode 100644 index 00000000..8b625ffa --- /dev/null +++ b/scripts/models/worker.ts @@ -0,0 +1,68 @@ +import relativeTime from 'dayjs/plugin/relativeTime' +import { Collection } from '@freearhey/core' +import { Channel } from './channel' +import dayjs from 'dayjs' + +dayjs.extend(relativeTime) + +export interface WorkerData { + host: string +} + +export class Worker { + host: string + channelsPath?: string + guidePath?: string + channels?: Collection + status?: string + lastUpdated?: string + + constructor(data: WorkerData) { + this.host = data.host + } + + getBaseUrl(): string { + return `https://${this.host}` + } + + getConfigUrl(): string { + const url = new URL('worker.json', this.getBaseUrl()) + + return url.href + } + + getChannelsUrl(): string { + if (!this.channelsPath) return '' + + const url = new URL(this.channelsPath, this.getBaseUrl()) + + return url.href + } + + getGuideUrl(): string { + if (!this.guidePath) return '' + + const url = new URL(this.guidePath, this.getBaseUrl()) + + return url.href + } + + getStatusEmoji(): string { + if (!this.status) return '⚪' + if (this.status === 'OK') return '🟢' + + return '🔴' + } + + getChannelsCount(): number { + if (!this.channels) return 0 + + return this.channels.count() + } + + getLastUpdated(): string { + if (!this.lastUpdated) return '-' + + return dayjs().to(dayjs(this.lastUpdated)) + } +} diff --git a/scripts/templates/_guides.md b/scripts/templates/_guides.md new file mode 100644 index 00000000..d643a463 --- /dev/null +++ b/scripts/templates/_guides.md @@ -0,0 +1,5 @@ +# Guides + +_TABLE_ + +[How can I add my server to the list?](CONTRIBUTING.md#how-to-add-my-server-to-the-guides-md)