12 files changed
+87
-24
No files matched your search
@@ -4,6 +4,7 @@
|
|||||||
/custom.xml
|
/custom.xml
|
||||||
/guide.xml
|
/guide.xml
|
||||||
/guide.xml.gz
|
/guide.xml.gz
|
||||||
|
/guide.json
|
||||||
.secrets
|
.secrets
|
||||||
/guides/
|
/guides/
|
||||||
/dev/
|
/dev/
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { loadJs, parseProxy, parseNumber, parseList } from '../../core'
|
|
||||||
import { Logger, Timer, Collection, Template } from '@freearhey/core'
|
import { Logger, Timer, Collection, Template } from '@freearhey/core'
|
||||||
import epgGrabber, { EPGGrabber, EPGGrabberMock } from 'epg-grabber'
|
import epgGrabber, { EPGGrabber, EPGGrabberMock } from 'epg-grabber'
|
||||||
import { CurlBody } from 'curl-generator/dist/bodies/body'
|
import { CurlBody } from 'curl-generator/dist/bodies/body'
|
||||||
@@ -15,6 +14,14 @@ import { data, loadData } from '../../api'
|
|||||||
import dayjs, { Dayjs } from 'dayjs'
|
import dayjs, { Dayjs } from 'dayjs'
|
||||||
import merge from 'lodash.merge'
|
import merge from 'lodash.merge'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import {
|
||||||
|
parseBooleanOrString,
|
||||||
|
parseBoolean,
|
||||||
|
parseNumber,
|
||||||
|
parseProxy,
|
||||||
|
parseList,
|
||||||
|
loadJs
|
||||||
|
} from '../../core'
|
||||||
|
|
||||||
program
|
program
|
||||||
.addOption(
|
.addOption(
|
||||||
@@ -55,11 +62,19 @@ program
|
|||||||
.env('MAX_CONNECTIONS')
|
.env('MAX_CONNECTIONS')
|
||||||
)
|
)
|
||||||
.addOption(
|
.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(
|
||||||
.addOption(new Option('--curl', 'Display each request as CURL').env('CURL'))
|
new Option('--json [path]', 'Create a JSON version of the guide as well')
|
||||||
.addOption(new Option('--debug', 'Enable debug mode').env('DEBUG'))
|
.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()
|
.parse()
|
||||||
|
|
||||||
interface GrabOptions {
|
interface GrabOptions {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ async function main() {
|
|||||||
|
|
||||||
if (!workerConfig) {
|
if (!workerConfig) {
|
||||||
worker.setStatus('MISSING_WORKER_CONFIG')
|
worker.setStatus('MISSING_WORKER_CONFIG')
|
||||||
logger.error('Unable to load "workers.json"')
|
logger.error('Unable to load "worker.json"')
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,3 +114,23 @@ export function parseNumber(value: string): number {
|
|||||||
export function parseList(value: string): string[] {
|
export function parseList(value: string): string[] {
|
||||||
return value.split(',')
|
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
|
||||||
|
}
|
||||||
@@ -44,23 +44,21 @@ export class Guide {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async save({ logger }: { logger: Logger }) {
|
async save({ logger }: { logger: Logger }) {
|
||||||
const dir = path.dirname(this.filepath)
|
const storage = new Storage()
|
||||||
const storage = new Storage(dir)
|
|
||||||
const xmlFilepath = this.filepath
|
|
||||||
const xmlFilename = path.basename(xmlFilepath)
|
|
||||||
logger.info(` saving to "${xmlFilepath}"...`)
|
|
||||||
const xmltv = this.toString()
|
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) {
|
if (this.gzip) {
|
||||||
const compressed = pako.gzip(xmltv)
|
const compressed = pako.gzip(xmltv)
|
||||||
const gzFilepath = typeof this.gzip === 'string' ? this.gzip : `${this.filepath}.gz`
|
const gzFilepath = typeof this.gzip === 'string' ? this.gzip : `${this.filepath}.gz`
|
||||||
const gzFilename = path.basename(gzFilepath)
|
|
||||||
logger.info(` saving to "${gzFilepath}"...`)
|
logger.info(` saving to "${gzFilepath}"...`)
|
||||||
await storage.save(gzFilename, compressed)
|
await storage.save(gzFilepath, compressed)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.json) {
|
if (this.json) {
|
||||||
|
const dir = path.dirname(this.filepath)
|
||||||
const filename = path.basename(this.filepath).split('.')[0]
|
const filename = path.basename(this.filepath).split('.')[0]
|
||||||
const jsonFilepath =
|
const jsonFilepath =
|
||||||
typeof this.json === 'string' ? this.json : path.join(dir, `${filename}.json`)
|
typeof this.json === 'string' ? this.json : path.join(dir, `${filename}.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":[]}]
|
[{"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":[]}]
|
||||||
@@ -10,4 +10,4 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
[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)
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"channel": "CNNInternational.us",
|
"channel": "CNNInternational.us",
|
||||||
"id": "SD",
|
"id": "MENA",
|
||||||
"name": "SD",
|
"name": "MENA",
|
||||||
"is_main": true,
|
"is_main": true,
|
||||||
"broadcast_area": [
|
"broadcast_area": [
|
||||||
"r/INT"
|
"r/INT"
|
||||||
|
|||||||
@@ -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"}]
|
||||||
@@ -2,6 +2,8 @@ import { execSync } from 'child_process'
|
|||||||
import { pathToFileURL } from 'node:url'
|
import { pathToFileURL } from 'node:url'
|
||||||
import fs from 'fs-extra'
|
import fs from 'fs-extra'
|
||||||
|
|
||||||
|
const ENV_VAR = 'cross-env DATA_DIR=tests/__data__/input/data'
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fs.emptyDirSync('tests/__data__/output')
|
fs.emptyDirSync('tests/__data__/output')
|
||||||
fs.copySync(
|
fs.copySync(
|
||||||
@@ -12,7 +14,7 @@ beforeEach(() => {
|
|||||||
|
|
||||||
describe('channels:format', () => {
|
describe('channels:format', () => {
|
||||||
it('can format *.channels.xml files', () => {
|
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' })
|
const stdout = execSync(cmd, { encoding: 'utf8' })
|
||||||
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
|
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,32 @@ describe('epg:grab', () => {
|
|||||||
expect(output).toEqual(expected)
|
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(
|
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'
|
'tests/__data__/output/guides/guide.xml'
|
||||||
)}" --gzip="${path.resolve('tests/__data__/output/guides/custom.xml.gz')}"`
|
)}" --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', () => {
|
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.resolve(
|
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'
|
'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' })
|
const stdout = execSync(cmd, { encoding: 'utf8' })
|
||||||
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
|
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import fs from 'fs-extra'
|
|||||||
import { pathToFileURL } from 'node:url'
|
import { pathToFileURL } from 'node:url'
|
||||||
|
|
||||||
const ENV_VAR =
|
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(() => {
|
beforeEach(() => {
|
||||||
fs.emptyDirSync('tests/__data__/output')
|
fs.emptyDirSync('tests/__data__/output')
|
||||||
|
|||||||
Reference in new issue
Block a user