diff --git a/.gitignore b/.gitignore index ce62a05e9..7c1dba2d5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /custom.xml /guide.xml /guide.xml.gz +/guide.json .secrets /guides/ /dev/ diff --git a/scripts/commands/epg/grab.ts b/scripts/commands/epg/grab.ts index eb9ee55e3..122993ffc 100644 --- a/scripts/commands/epg/grab.ts +++ b/scripts/commands/epg/grab.ts @@ -1,4 +1,3 @@ -import { loadJs, parseProxy, parseNumber, parseList } from '../../core' import { Logger, Timer, Collection, Template } from '@freearhey/core' import epgGrabber, { EPGGrabber, EPGGrabberMock } from 'epg-grabber' import { CurlBody } from 'curl-generator/dist/bodies/body' @@ -15,6 +14,14 @@ import { data, loadData } from '../../api' import dayjs, { Dayjs } from 'dayjs' import merge from 'lodash.merge' import path from 'path' +import { + parseBooleanOrString, + parseBoolean, + parseNumber, + parseProxy, + parseList, + loadJs +} from '../../core' program .addOption( @@ -55,11 +62,19 @@ program .env('MAX_CONNECTIONS') ) .addOption( - new Option('--gzip [path]', 'Create a compressed version of the guide as well').env('GZIP') + new Option('--gzip [path]', 'Create a compressed version of the guide as well') + .argParser(parseBooleanOrString) + .env('GZIP') ) - .addOption(new Option('--json [path]', 'Create a JSON version of the guide as well').env('JSON')) - .addOption(new Option('--curl', 'Display each request as CURL').env('CURL')) - .addOption(new Option('--debug', 'Enable debug mode').env('DEBUG')) + .addOption( + new Option('--json [path]', 'Create a JSON version of the guide as well') + .argParser(parseBooleanOrString) + .env('JSON') + ) + .addOption( + new Option('--curl', 'Display each request as CURL').argParser(parseBoolean).env('CURL') + ) + .addOption(new Option('--debug', 'Enable debug mode').argParser(parseBoolean).env('DEBUG')) .parse() interface GrabOptions { diff --git a/scripts/commands/workers/load.ts b/scripts/commands/workers/load.ts index 7145c2c2c..4c2aa32d4 100644 --- a/scripts/commands/workers/load.ts +++ b/scripts/commands/workers/load.ts @@ -56,7 +56,7 @@ async function main() { if (!workerConfig) { worker.setStatus('MISSING_WORKER_CONFIG') - logger.error('Unable to load "workers.json"') + logger.error('Unable to load "worker.json"') continue } diff --git a/scripts/core/utils.ts b/scripts/core/utils.ts index 2ad7673c4..e1461bf07 100644 --- a/scripts/core/utils.ts +++ b/scripts/core/utils.ts @@ -114,3 +114,23 @@ export function parseNumber(value: string): number { export function parseList(value: string): string[] { return value.split(',') } + +export function parseBoolean(value: string | boolean | undefined): boolean { + if (value === undefined) return true + if (typeof value === 'boolean') return value + if (typeof value === 'string' && value.toLowerCase() === 'true') return true + + return false +} + +export function parseBooleanOrString(value: string | boolean): string | boolean { + if (value === undefined) return true + if (typeof value === 'boolean') return value + if (typeof value === 'string') { + const normalized = value.toLowerCase() + if (normalized === 'true') return true + if (normalized === 'false') return false + } + + return value +} diff --git a/scripts/models/guide.ts b/scripts/models/guide.ts index cb68ae1f1..2dab00a65 100644 --- a/scripts/models/guide.ts +++ b/scripts/models/guide.ts @@ -44,23 +44,21 @@ export class Guide { } async save({ logger }: { logger: Logger }) { - const dir = path.dirname(this.filepath) - const storage = new Storage(dir) - const xmlFilepath = this.filepath - const xmlFilename = path.basename(xmlFilepath) - logger.info(` saving to "${xmlFilepath}"...`) + const storage = new Storage() const xmltv = this.toString() - await storage.save(xmlFilename, xmltv) + const xmlFilepath = this.filepath + logger.info(` saving to "${xmlFilepath}"...`) + await storage.save(xmlFilepath, xmltv) if (this.gzip) { const compressed = pako.gzip(xmltv) const gzFilepath = typeof this.gzip === 'string' ? this.gzip : `${this.filepath}.gz` - const gzFilename = path.basename(gzFilepath) logger.info(` saving to "${gzFilepath}"...`) - await storage.save(gzFilename, compressed) + await storage.save(gzFilepath, compressed) } if (this.json) { + const dir = path.dirname(this.filepath) const filename = path.basename(this.filepath).split('.')[0] const jsonFilepath = typeof this.json === 'string' ? this.json : path.join(dir, `${filename}.json`) diff --git a/tests/__data__/expected/guides_export/guides.json b/tests/__data__/expected/guides_export/guides.json index 1239c7edf..0c3047418 100644 --- a/tests/__data__/expected/guides_export/guides.json +++ b/tests/__data__/expected/guides_export/guides.json @@ -1 +1 @@ -[{"channel":"Channel1.us","feed":"SD","site":"example.com","site_id":"140","site_name":"Channel 1","lang":"en","sources":[]},{"channel":"Channel2.us","feed":null,"site":"example.com","site_id":"142","site_name":"Channel 2","lang":"en","sources":[]},{"channel":"Channel1.us","feed":"HD","site":"example.com","site_id":"140","site_name":"Channel 1","lang":"fr","sources":[]},{"channel":"Channel3.us","feed":"HD","site":"example2.com","site_id":"150","site_name":"Channel 3","lang":"en","sources":[]},{"channel":"Channel4.us","feed":null,"site":"example2.com","site_id":"152","site_name":"Channel 4","lang":"en","sources":[]},{"channel":"Channel1.us","feed":null,"site":"example2.com","site_id":"140","site_name":"Channel 1","lang":"fr","sources":[]}] \ No newline at end of file +[{"channel":"Channel1.us","feed":"SD","site":"example.com","site_id":"140","site_name":"Channel 1","lang":"en","sources":[{"host":"worker-9dd4.onrender.com","url":"https://worker-9dd4.onrender.com/guide.xml","format":"XML"}]},{"channel":"Channel2.us","feed":null,"site":"example.com","site_id":"142","site_name":"Channel 2","lang":"en","sources":[]},{"channel":"Channel1.us","feed":"HD","site":"example.com","site_id":"140","site_name":"Channel 1","lang":"fr","sources":[]},{"channel":"Channel3.us","feed":"HD","site":"example2.com","site_id":"150","site_name":"Channel 3","lang":"en","sources":[]},{"channel":"Channel4.us","feed":null,"site":"example2.com","site_id":"152","site_name":"Channel 4","lang":"en","sources":[]},{"channel":"Channel1.us","feed":null,"site":"example2.com","site_id":"140","site_name":"Channel 1","lang":"fr","sources":[]}] \ No newline at end of file diff --git a/tests/__data__/expected/guides_update/GUIDES.md b/tests/__data__/expected/guides_update/GUIDES.md index 2abafcce9..404494f86 100644 --- a/tests/__data__/expected/guides_update/GUIDES.md +++ b/tests/__data__/expected/guides_update/GUIDES.md @@ -10,4 +10,4 @@ -[How can I add my server to the list?](CONTRIBUTING.md#how-to-add-my-server-to-the-guides-md) +[How can I add my server to the list?](CONTRIBUTING.md#how-to-add-my-server-to-the-guidesmd) diff --git a/tests/__data__/input/data/feeds.json b/tests/__data__/input/data/feeds.json index cf0e5e34a..4a7e8ed5d 100644 --- a/tests/__data__/input/data/feeds.json +++ b/tests/__data__/input/data/feeds.json @@ -1,8 +1,8 @@ [ { "channel": "CNNInternational.us", - "id": "SD", - "name": "SD", + "id": "MENA", + "name": "MENA", "is_main": true, "broadcast_area": [ "r/INT" diff --git a/tests/__data__/input/data/workers.json b/tests/__data__/input/data/workers.json new file mode 100644 index 000000000..3fd77eb7d --- /dev/null +++ b/tests/__data__/input/data/workers.json @@ -0,0 +1 @@ +[{"host":"worker-9dd4.onrender.com","channelsPath":"channels.xml","channels":[{"xmltv_id":"Channel1.us@SD","name":"Channel 1","site":"example.com","site_id":"140","lang":"en","logo":null,"url":null,"lcn":null,"index":0}],"guideXmlPath":"guide.xml","status":"OK","lastUpdated":"2026-05-03T00:00:00.000Z"}] \ No newline at end of file diff --git a/tests/commands/channels/format.test.ts b/tests/commands/channels/format.test.ts index 503d2a608..5ba3328bb 100644 --- a/tests/commands/channels/format.test.ts +++ b/tests/commands/channels/format.test.ts @@ -2,6 +2,8 @@ import { execSync } from 'child_process' import { pathToFileURL } from 'node:url' import fs from 'fs-extra' +const ENV_VAR = 'cross-env DATA_DIR=tests/__data__/input/data' + beforeEach(() => { fs.emptyDirSync('tests/__data__/output') fs.copySync( @@ -12,7 +14,7 @@ beforeEach(() => { describe('channels:format', () => { it('can format *.channels.xml files', () => { - const cmd = 'npm run channels:format --- tests/__data__/output/example.com.channels.xml' + const cmd = `${ENV_VAR} npm run channels:format --- tests/__data__/output/example.com.channels.xml` const stdout = execSync(cmd, { encoding: 'utf8' }) if (process.env.DEBUG === 'true') console.log(cmd, stdout) diff --git a/tests/commands/epg/grab.test.ts b/tests/commands/epg/grab.test.ts index 743cb0fb7..89966c37c 100644 --- a/tests/commands/epg/grab.test.ts +++ b/tests/commands/epg/grab.test.ts @@ -113,7 +113,32 @@ describe('epg:grab', () => { expect(output).toEqual(expected) }) - it('can grab epg with gzip option enabled', () => { + it('can grab epg with GZIP environment variable', () => { + const cmd = `${ENV_VAR} GZIP=true npm run grab --- --channels=tests/__data__/input/epg_grab/sites/example2.com/example2.com.channels.xml --output="${path.resolve( + 'tests/__data__/output/guides/guide.xml' + )}"` + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + + expect(content('tests/__data__/output/guides/guide.xml')).toEqual( + content('tests/__data__/expected/epg_grab/gzip/guide.xml') + ) + + const outputString = pako.ungzip(fs.readFileSync('tests/__data__/output/guides/guide.xml.gz'), { + to: 'string' + }) + const expectedString = pako.ungzip( + fs.readFileSync('tests/__data__/expected/epg_grab/gzip/guide.xml.gz'), + { to: 'string' } + ) + + const output = new Set(outputString.split('\r\n')) + const expected = new Set(expectedString.split('\r\n')) + + expect(output).toEqual(expected) + }) + + it('can grab epg with gzip path', () => { const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/sites/example2.com/example2.com.channels.xml --output="${path.resolve( 'tests/__data__/output/guides/guide.xml' )}" --gzip="${path.resolve('tests/__data__/output/guides/custom.xml.gz')}"` @@ -157,10 +182,11 @@ describe('epg:grab', () => { ) }) - it('can grab epg with json option enabled', () => { - const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/sites/example2.com/example2.com.channels.xml --output="${path.resolve( + it('can grab epg with json path', () => { + const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/sites/example2.com/example2.com.channels.xml --output="${path.relative( + process.cwd(), 'tests/__data__/output/guides/guide.xml' - )}" --json="${path.resolve('tests/__data__/output/guides/custom.json')}"` + )}" --json="${path.relative(process.cwd(), 'tests/__data__/output/guides/custom.json')}"` const stdout = execSync(cmd, { encoding: 'utf8' }) if (process.env.DEBUG === 'true') console.log(cmd, stdout) diff --git a/tests/commands/guides/export.test.ts b/tests/commands/guides/export.test.ts index ee787e8a2..3b826e24b 100644 --- a/tests/commands/guides/export.test.ts +++ b/tests/commands/guides/export.test.ts @@ -3,7 +3,7 @@ import fs from 'fs-extra' import { pathToFileURL } from 'node:url' const ENV_VAR = - 'cross-env SITES_DIR=tests/__data__/input/guides_export/sites API_DIR=tests/__data__/output' + 'cross-env SITES_DIR=tests/__data__/input/guides_export/sites DATA_DIR=tests/__data__/input/data API_DIR=tests/__data__/output' beforeEach(() => { fs.emptyDirSync('tests/__data__/output')