Merge pull request #3154 from iptv-org/patch-2026.05.3

Patch 2026.05.3
This commit is contained in:
Toha
2026-05-27 07:59:36 +07:00
committed by GitHub
12 changed files with 87 additions and 24 deletions
+1
View File
@@ -4,6 +4,7 @@
/custom.xml
/guide.xml
/guide.xml.gz
/guide.json
.secrets
/guides/
/dev/
+20 -5
View File
@@ -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 {
+1 -1
View File
@@ -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
}
+20
View File
@@ -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
}
+6 -8
View File
@@ -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`)
@@ -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>
</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)
+2 -2
View File
@@ -1,8 +1,8 @@
[
{
"channel": "CNNInternational.us",
"id": "SD",
"name": "SD",
"id": "MENA",
"name": "MENA",
"is_main": true,
"broadcast_area": [
"r/INT"
+1
View File
@@ -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"}]
+3 -1
View File
@@ -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)
+30 -4
View File
@@ -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)
+1 -1
View File
@@ -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')