Merge pull request #2653 from iptv-org/patch-2025.01.7

This commit is contained in:
Alstruit 2025-01-31 22:58:53 -06:00 committed by GitHub
commit f7e1c1558c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 311 additions and 90 deletions

View file

@ -3,6 +3,5 @@
channels_changed="$(git diff --staged --name-only --diff-filter=ACMR -- 'sites/**/*.channels.xml' | sed 's| |\\ |g')"
if [ ! -z "$channels_changed" ]; then
echo "npx eslint $channels_changed"
npm run channels:lint -- $channels_changed
fi

7
.husky/channels_validate.sh Executable file
View file

@ -0,0 +1,7 @@
#!/bin/sh
channels_changed="$(git diff --staged --name-only --diff-filter=ACMR -- 'sites/**/*.channels.xml' | sed 's| |\\ |g')"
if [ ! -z "$channels_changed" ]; then
npm run channels:validate -- $channels_changed
fi

View file

@ -1,2 +1,3 @@
.husky/check_scripts.sh
.husky/check_channels.sh
.husky/scripts_lint.sh
.husky/channels_lint.sh
.husky/channels_validate.sh

View file

@ -3,6 +3,5 @@
scripts_changed="$(git diff --staged --name-only --diff-filter=ACMR -- 'tests/**/*.ts' 'tests/**/*.js' 'scripts/**/*.ts' 'scripts/**/*.mts' 'scripts/**/*.js' 'sites/**/*.js' 'sites/**/*.ts' | sed 's| |\\ |g')"
if [ ! -z "$scripts_changed" ]; then
echo "npx eslint $scripts_changed"
npx eslint $scripts_changed
fi

View file

@ -58,6 +58,7 @@ Options:
-l, --lang <code> Filter channels by language (ISO 639-2 code)
-t, --timeout <milliseconds> Override the default timeout for each request
-d, --delay <milliseconds> Override the default delay between request
-x, --proxy <url> Use the specified proxy (example: "socks5://username:password@127.0.0.1:1234")
--days <days> Override the number of days for which the program will be loaded
(defaults to the value from the site config)
--maxConnections <number> Limit on the number of concurrent requests (default: 1)

147
package-lock.json generated
View file

@ -11,8 +11,8 @@
"@alex_neo/jest-expect-message": "^1.0.5",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.17.0",
"@freearhey/core": "^0.5.0",
"@ntlab/sfetch": "^1.0.0",
"@freearhey/core": "^0.5.1",
"@ntlab/sfetch": "^1.2.0",
"@octokit/core": "^6.1.3",
"@octokit/plugin-paginate-rest": "^11.3.6",
"@octokit/plugin-rest-endpoint-methods": "^13.2.6",
@ -38,7 +38,7 @@
"csv-parser": "^3.0.0",
"cwait": "^1.1.2",
"dayjs": "^1.11.10",
"epg-grabber": "^0.37.4",
"epg-grabber": "^0.38.0",
"epg-parser": "^0.3.1",
"eslint": "^9.17.0",
"eslint-config-prettier": "^9.0.0",
@ -69,6 +69,7 @@
"serve": "^14.2.4",
"signale": "^1.4.0",
"skip-postinstall": "^1.0.0",
"socks-proxy-agent": "^8.0.5",
"srcset": "^4.0.0",
"table2array": "^0.0.2",
"tabletojson": "^2.0.7",
@ -1203,9 +1204,9 @@
}
},
"node_modules/@freearhey/core": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@freearhey/core/-/core-0.5.0.tgz",
"integrity": "sha512-FcA5Pv9RvFvLYAwNmD/2vlSR49Rx+kihJ+xbIUgIACHY6lBUptfbNznm00DQoUyWRJG/cfT3dkYCwIxSUsdP+w==",
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/@freearhey/core/-/core-0.5.1.tgz",
"integrity": "sha512-UDKIOyrtcUXaiAeIvjNFTI6DlempiOQaRB83CqHNF1VPRHNBiNhGhERWyInHE2cjLp/cc0CA/IykOYS39kBK7Q==",
"dependencies": {
"@types/fs-extra": "^11.0.2",
"@types/lodash": "^4.14.198",
@ -1863,9 +1864,9 @@
}
},
"node_modules/@ntlab/sfetch": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@ntlab/sfetch/-/sfetch-1.0.0.tgz",
"integrity": "sha512-AWrC43z1TncvB7S7dl9Wn8xZpCqdKFBfXqaN3BXPfJeS3gxV9Fm86eAsW95YdXTOgPWbCC/GAgVuXi6Aot6DkQ==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ntlab/sfetch/-/sfetch-1.2.0.tgz",
"integrity": "sha512-9SE4NnqWo8l6mG0rnAkgng6ozSamIpF3EC+GOTQGGa6eAC0tNJvzrylMz6YRjjEGH6mOfn7ZBAuKj5WIZUul6A==",
"dependencies": {
"axios": "^1.7.9"
}
@ -4192,9 +4193,9 @@
}
},
"node_modules/epg-grabber": {
"version": "0.37.4",
"resolved": "https://registry.npmjs.org/epg-grabber/-/epg-grabber-0.37.4.tgz",
"integrity": "sha512-PS104bH9tHRa9kivSwx47AKMkfHwKy51XQTx+GO6sIXvIp2Z4LBpwMEXGcfPoAsdIGxgs2Wrl0dZ/QGL+7x6YQ==",
"version": "0.38.0",
"resolved": "https://registry.npmjs.org/epg-grabber/-/epg-grabber-0.38.0.tgz",
"integrity": "sha512-jbwTgi6G7e+zrb2oNC0C7mcQYoRkFnvhXCurexeICaEy4avRB6WS5rD/yfqYoiqaXOM3x1BNBpCKFYoS7Ob5YA==",
"dependencies": {
"axios": "^1.6.1",
"axios-cache-interceptor": "^0.10.3",
@ -4209,6 +4210,7 @@
"http-cookie-agent": "^6.0.8",
"lodash": "^4.17.21",
"node-gzip": "^1.1.2",
"socks-proxy-agent": "^8.0.5",
"tough-cookie": "^5.0.0",
"winston": "^3.3.3",
"xml-js": "^1.6.11"
@ -5247,6 +5249,23 @@
"node": ">=8"
}
},
"node_modules/ip-address": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
"integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
"dependencies": {
"jsbn": "1.1.0",
"sprintf-js": "^1.1.3"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/ip-address/node_modules/sprintf-js": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="
},
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@ -6141,6 +6160,11 @@
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/jsbn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="
},
"node_modules/jsesc": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
@ -7641,6 +7665,41 @@
"node": ">=8"
}
},
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/socks": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
"integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==",
"dependencies": {
"ip-address": "^9.0.5",
"smart-buffer": "^4.2.0"
},
"engines": {
"node": ">= 10.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/socks-proxy-agent": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
"integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "^4.3.4",
"socks": "^2.8.3"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -9172,9 +9231,9 @@
}
},
"@freearhey/core": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@freearhey/core/-/core-0.5.0.tgz",
"integrity": "sha512-FcA5Pv9RvFvLYAwNmD/2vlSR49Rx+kihJ+xbIUgIACHY6lBUptfbNznm00DQoUyWRJG/cfT3dkYCwIxSUsdP+w==",
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/@freearhey/core/-/core-0.5.1.tgz",
"integrity": "sha512-UDKIOyrtcUXaiAeIvjNFTI6DlempiOQaRB83CqHNF1VPRHNBiNhGhERWyInHE2cjLp/cc0CA/IykOYS39kBK7Q==",
"requires": {
"@types/fs-extra": "^11.0.2",
"@types/lodash": "^4.14.198",
@ -9661,9 +9720,9 @@
}
},
"@ntlab/sfetch": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@ntlab/sfetch/-/sfetch-1.0.0.tgz",
"integrity": "sha512-AWrC43z1TncvB7S7dl9Wn8xZpCqdKFBfXqaN3BXPfJeS3gxV9Fm86eAsW95YdXTOgPWbCC/GAgVuXi6Aot6DkQ==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ntlab/sfetch/-/sfetch-1.2.0.tgz",
"integrity": "sha512-9SE4NnqWo8l6mG0rnAkgng6ozSamIpF3EC+GOTQGGa6eAC0tNJvzrylMz6YRjjEGH6mOfn7ZBAuKj5WIZUul6A==",
"requires": {
"axios": "^1.7.9"
}
@ -11280,9 +11339,9 @@
"integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA=="
},
"epg-grabber": {
"version": "0.37.4",
"resolved": "https://registry.npmjs.org/epg-grabber/-/epg-grabber-0.37.4.tgz",
"integrity": "sha512-PS104bH9tHRa9kivSwx47AKMkfHwKy51XQTx+GO6sIXvIp2Z4LBpwMEXGcfPoAsdIGxgs2Wrl0dZ/QGL+7x6YQ==",
"version": "0.38.0",
"resolved": "https://registry.npmjs.org/epg-grabber/-/epg-grabber-0.38.0.tgz",
"integrity": "sha512-jbwTgi6G7e+zrb2oNC0C7mcQYoRkFnvhXCurexeICaEy4avRB6WS5rD/yfqYoiqaXOM3x1BNBpCKFYoS7Ob5YA==",
"requires": {
"axios": "^1.6.1",
"axios-cache-interceptor": "^0.10.3",
@ -11297,6 +11356,7 @@
"http-cookie-agent": "^6.0.8",
"lodash": "^4.17.21",
"node-gzip": "^1.1.2",
"socks-proxy-agent": "^8.0.5",
"tough-cookie": "^5.0.0",
"winston": "^3.3.3",
"xml-js": "^1.6.11"
@ -12006,6 +12066,22 @@
}
}
},
"ip-address": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
"integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
"requires": {
"jsbn": "1.1.0",
"sprintf-js": "^1.1.3"
},
"dependencies": {
"sprintf-js": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="
}
}
},
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@ -12650,6 +12726,11 @@
"esprima": "^4.0.0"
}
},
"jsbn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="
},
"jsesc": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
@ -13776,6 +13857,30 @@
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="
},
"smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="
},
"socks": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
"integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==",
"requires": {
"ip-address": "^9.0.5",
"smart-buffer": "^4.2.0"
}
},
"socks-proxy-agent": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
"integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
"requires": {
"agent-base": "^7.1.2",
"debug": "^4.3.4",
"socks": "^2.8.3"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",

View file

@ -40,8 +40,8 @@
"@alex_neo/jest-expect-message": "^1.0.5",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.17.0",
"@freearhey/core": "^0.5.0",
"@ntlab/sfetch": "^1.0.0",
"@freearhey/core": "^0.5.1",
"@ntlab/sfetch": "^1.2.0",
"@octokit/core": "^6.1.3",
"@octokit/plugin-paginate-rest": "^11.3.6",
"@octokit/plugin-rest-endpoint-methods": "^13.2.6",
@ -67,7 +67,7 @@
"csv-parser": "^3.0.0",
"cwait": "^1.1.2",
"dayjs": "^1.11.10",
"epg-grabber": "^0.37.4",
"epg-grabber": "^0.38.0",
"epg-parser": "^0.3.1",
"eslint": "^9.17.0",
"eslint-config-prettier": "^9.0.0",
@ -98,6 +98,7 @@
"serve": "^14.2.4",
"signale": "^1.4.0",
"skip-postinstall": "^1.0.0",
"socks-proxy-agent": "^8.0.5",
"srcset": "^4.0.0",
"table2array": "^0.0.2",
"tabletojson": "^2.0.7",

View file

@ -26,7 +26,7 @@ async function main() {
const logger = new Logger()
const file = new File(options.config)
const dir = file.dirname()
const config = (await import(pathToFileURL(options.config))).default
const config = (await import(pathToFileURL(options.config).toString())).default
const outputFilepath = options.output || `${dir}/${config.site}.channels.xml`
let channels = new Collection()

View file

@ -16,6 +16,7 @@ program
.option('-l, --lang <code>', 'Filter channels by language (ISO 639-2 code)')
.option('-t, --timeout <milliseconds>', 'Override the default timeout for each request')
.option('-d, --delay <milliseconds>', 'Override the default delay between request')
.option('-x, --proxy <url>', 'Use the specified proxy')
.option(
'--days <days>',
'Override the number of days for which the program will be loaded (defaults to the value from the site config)',
@ -42,6 +43,7 @@ export type GrabOptions = {
lang?: string
days?: number
cron?: string
proxy?: string
}
const options: GrabOptions = program.opts()

View file

@ -40,19 +40,20 @@ async function main() {
const data = new Collection()
sites.forEach((site: Site) => {
data.add([
`<a href="sites/${site.domain}">${site.domain}</a>`,
`${site.totalChannels} / ${site.markedChannels}`,
site.getStatus().emoji,
site.getIssues().all().join(', ')
{ value: `<a href="sites/${site.domain}">${site.domain}</a>` },
{ value: site.totalChannels, align: 'right' },
{ value: site.markedChannels, align: 'right' },
{ value: site.getStatus().emoji, align: 'center' },
{ value: site.getIssues().all().join(', ') }
])
})
logger.info('updating sites.md...')
const table = new HTMLTable(data.all(), [
{ name: 'Site' },
{ name: 'Channels *', align: 'center' },
{ name: 'Status' },
{ name: 'Notes' }
{ name: 'Site', align: 'left' },
{ name: 'Channels<br>(total / with xmltv-id)', colspan: 2, align: 'left' },
{ name: 'Status', align: 'left' },
{ name: 'Notes', align: 'left' }
])
const rootStorage = new Storage(ROOT_DIR)
const sitesTemplate = await new Storage().load('scripts/templates/_sites.md')

View file

@ -7,7 +7,7 @@ export class ConfigLoader {
const fileUrl = pathToFileURL(filepath).toString()
const config = (await import(fileUrl)).default
const defaultConfig = {
days: 2,
days: 1,
delay: 0,
output: 'guide.xml',
request: {

View file

@ -1,8 +1,9 @@
import { EPGGrabber, GrabCallbackData, EPGGrabberMock, SiteConfig, Channel } from 'epg-grabber'
import { Logger, Collection } from '@freearhey/core'
import { Queue } from './'
import { Queue, ProxyParser } from './'
import { GrabOptions } from '../commands/epg/grab'
import { TaskQueue, PromisyClass } from 'cwait'
import { SocksProxyAgent } from 'socks-proxy-agent'
type GrabberProps = {
logger: Logger
@ -14,6 +15,7 @@ export class Grabber {
logger: Logger
queue: Queue
options: GrabOptions
grabber: EPGGrabber | EPGGrabberMock
constructor({ logger, queue, options }: GrabberProps) {
this.logger = logger
@ -23,6 +25,7 @@ export class Grabber {
}
async grab(): Promise<{ channels: Collection; programs: Collection }> {
const proxyParser = new ProxyParser()
const taskQueue = new TaskQueue(Promise as PromisyClass, this.options.maxConnections)
const total = this.queue.size()
@ -49,6 +52,24 @@ export class Grabber {
config.delay = delay
}
if (this.options.proxy !== undefined) {
const proxy = proxyParser.parse(this.options.proxy)
if (
proxy.protocol &&
['socks', 'socks5', 'socks5h', 'socks4', 'socks4a'].includes(String(proxy.protocol))
) {
const socksProxyAgent = new SocksProxyAgent(this.options.proxy)
config.request = {
...config.request,
...{ httpAgent: socksProxyAgent, httpsAgent: socksProxyAgent }
}
} else {
config.request = { ...config.request, ...{ proxy } }
}
}
const _programs = await this.grabber.grab(
channel,
date,

View file

@ -2,9 +2,15 @@ type Column = {
name: string
nowrap?: boolean
align?: string
colspan?: number
}
type DataItem = string[]
type DataItem = {
value: string
nowrap?: boolean
align?: string
colspan?: number
}[]
export class HTMLTable {
data: DataItem[]
@ -20,20 +26,23 @@ export class HTMLTable {
output += ' <thead>\r\n <tr>'
for (const column of this.columns) {
output += `<th align="left">${column.name}</th>`
const nowrap = column.nowrap ? ' nowrap' : ''
const align = column.align ? ` align="${column.align}"` : ''
const colspan = column.colspan ? ` colspan="${column.colspan}"` : ''
output += `<th${align}${nowrap}${colspan}>${column.name}</th>`
}
output += '</tr>\r\n </thead>\r\n'
output += ' <tbody>\r\n'
for (const item of this.data) {
for (const row of this.data) {
output += ' <tr>'
let i = 0
for (const prop in item) {
const column = this.columns[i]
const nowrap = column.nowrap ? ' nowrap' : ''
const align = column.align ? ` align="${column.align}"` : ''
output += `<td${align}${nowrap}>${item[prop]}</td>`
i++
for (const item of row) {
const nowrap = item.nowrap ? ' nowrap' : ''
const align = item.align ? ` align="${item.align}"` : ''
const colspan = item.colspan ? ` colspan="${item.colspan}"` : ''
output += `<td${align}${nowrap}${colspan}>${item.value}</td>`
}
output += '</tr>\r\n'
}

View file

@ -13,3 +13,4 @@ export * from './queueCreator'
export * from './issueLoader'
export * from './issueParser'
export * from './htmlTable'
export * from './proxyParser'

View file

@ -0,0 +1,27 @@
import { URL } from 'node:url'
type ProxyParserResult = {
protocol: string | null
auth: {
username: string | null
password: string | null
}
host: string
port: number | null
}
export class ProxyParser {
parse(_url: string): ProxyParserResult {
const parsed = new URL(_url)
return {
protocol: parsed.protocol.replace(':', '') || null,
auth: {
username: parsed.username || null,
password: parsed.password || null
},
host: parsed.hostname,
port: parsed.port ? parseInt(parsed.port) : null
}
}
}

View file

@ -1,5 +1,3 @@
# Sites
\* Total number of channels / with a valid `xmltv-id`
_TABLE_

View file

@ -6,7 +6,7 @@ dayjs.extend(customParseFormat)
dayjs.extend(utc)
const date = dayjs.utc('2025-01-12', 'YYYY-MM-DD').startOf('d')
const channel = { site_id: 'bbc1', xmltv_id: 'BBCOne.uk' }
const channel = { site_id: 'bbc1' }
it('can generate valid url', () => {
expect(url({ channel, date })).toBe('https://example.com/api/bbc1/2025-01-12')
@ -32,11 +32,7 @@ it('can parse response', () => {
})
it('can handle empty guide', () => {
const result = parser({
date,
channel,
content: ''
})
const results = parser({ content: '' })
expect(result).toMatchObject([])
expect(results).toMatchObject([])
})

View file

@ -1,14 +1,12 @@
# Sites
\* Total number of channels / with a valid `xmltv-id`
<table>
<thead>
<tr><th align="left">Site</th><th align="left">Channels *</th><th align="left">Status</th><th align="left">Notes</th></tr>
<tr><th align="left">Site</th><th align="left" colspan="2">Channels<br>(total / with xmltv-id)</th><th align="left">Status</th><th align="left">Notes</th></tr>
</thead>
<tbody>
<tr><td><a href="sites/iltalehti.fi">iltalehti.fi</a></td><td align="center">142 / 44</td><td>🟡</td><td>https://github.com/iptv-org/epg/issues/2396</td></tr>
<tr><td><a href="sites/indihometv.com">indihometv.com</a></td><td align="center">130 / 124</td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/kan.org.il">kan.org.il</a></td><td align="center">6 / 6</td><td>🔴</td><td>https://github.com/iptv-org/epg/issues/2273</td></tr>
<tr><td><a href="sites/iltalehti.fi">iltalehti.fi</a></td><td align="right">142</td><td align="right">44</td><td align="center">🟡</td><td>https://github.com/iptv-org/epg/issues/2396</td></tr>
<tr><td><a href="sites/indihometv.com">indihometv.com</a></td><td align="right">130</td><td align="right">124</td><td align="center">🟢</td><td></td></tr>
<tr><td><a href="sites/kan.org.il">kan.org.il</a></td><td align="right">6</td><td align="right">6</td><td align="center">🔴</td><td>https://github.com/iptv-org/epg/issues/2273</td></tr>
</tbody>
</table>

View file

@ -9,9 +9,6 @@
<programme start="20221019044000 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example2.com)</title></programme>
<programme start="20221020043000 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
<programme start="20221020043000 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221020044000 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example2.com)</title></programme>
<programme start="20221019043000 +0000" stop="20221019071000 +0000" channel="Channel3.us"><title lang="en">Program1 (example2.com)</title></programme>
<programme start="20221020043000 +0000" stop="20221020071000 +0000" channel="Channel3.us"><title lang="en">Program1 (example2.com)</title></programme>
<programme start="20221019043000 +0000" stop="20221019071000 +0000" channel="Channel4.us"><title lang="en">Program1 (example2.com)</title></programme>
<programme start="20221020043000 +0000" stop="20221020071000 +0000" channel="Channel4.us"><title lang="en">Program1 (example2.com)</title></programme>
</tv>

View file

@ -9,9 +9,6 @@
<programme start="20221019044000 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example2.com)</title></programme>
<programme start="20221020043000 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
<programme start="20221020043000 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221020044000 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example2.com)</title></programme>
<programme start="20221019043000 +0000" stop="20221019071000 +0000" channel="Channel3.us"><title lang="en">Program1 (example2.com)</title></programme>
<programme start="20221020043000 +0000" stop="20221020071000 +0000" channel="Channel3.us"><title lang="en">Program1 (example2.com)</title></programme>
<programme start="20221019043000 +0000" stop="20221019071000 +0000" channel="Channel4.us"><title lang="en">Program1 (example2.com)</title></programme>
<programme start="20221020043000 +0000" stop="20221020071000 +0000" channel="Channel4.us"><title lang="en">Program1 (example2.com)</title></programme>
</tv>

Binary file not shown.

View file

@ -6,7 +6,7 @@ dayjs.extend(customParseFormat)
dayjs.extend(utc)
const date = dayjs.utc('2025-01-12', 'YYYY-MM-DD').startOf('d')
const channel = { site_id: 'bbc1', xmltv_id: 'BBCOne.uk' }
const channel = { site_id: 'bbc1' }
it('can generate valid url', () => {
expect(url({ channel, date })).toBe('https://example.com/api/bbc1/2025-01-12')
@ -32,11 +32,7 @@ it('can parse response', () => {
})
it('can handle empty guide', () => {
const result = parser({
date,
channel,
content: ''
})
const results = parser({ content: '' })
expect(result).toMatchObject([])
expect(results).toMatchObject([])
})

View file

@ -97,6 +97,30 @@ describe('epg:grab', () => {
expect(stdout).toContain('ERR: Connection timeout')
})
it('can grab epg via https proxy', () => {
const cmd = `${ENV_VAR} npm run grab --- --site=example.com --proxy=https://bob:123456@proxy.com:1234 --output="${path.resolve(
'tests/__data__/output/guide.xml'
)}"`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/guide.xml')).toEqual(
content('tests/__data__/expected/guide2.xml')
)
})
it('can grab epg via socks5 proxy', () => {
const cmd = `${ENV_VAR} npm run grab --- --site=example.com --proxy=socks5://bob:123456@proxy.com:1234 --output="${path.resolve(
'tests/__data__/output/guide.xml'
)}"`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/guide.xml')).toEqual(
content('tests/__data__/expected/guide2.xml')
)
})
})
function content(filepath: string) {

View file

@ -396,10 +396,10 @@
dependencies:
levn "^0.4.1"
"@freearhey/core@^0.5.0":
version "0.5.0"
resolved "https://registry.npmjs.org/@freearhey/core/-/core-0.5.0.tgz"
integrity sha512-FcA5Pv9RvFvLYAwNmD/2vlSR49Rx+kihJ+xbIUgIACHY6lBUptfbNznm00DQoUyWRJG/cfT3dkYCwIxSUsdP+w==
"@freearhey/core@^0.5.1":
version "0.5.1"
resolved "https://registry.npmjs.org/@freearhey/core/-/core-0.5.1.tgz"
integrity sha512-UDKIOyrtcUXaiAeIvjNFTI6DlempiOQaRB83CqHNF1VPRHNBiNhGhERWyInHE2cjLp/cc0CA/IykOYS39kBK7Q==
dependencies:
"@types/fs-extra" "^11.0.2"
"@types/lodash" "^4.14.198"
@ -741,10 +741,10 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@ntlab/sfetch@^1.0.0":
version "1.0.0"
resolved "https://registry.npmjs.org/@ntlab/sfetch/-/sfetch-1.0.0.tgz"
integrity sha512-AWrC43z1TncvB7S7dl9Wn8xZpCqdKFBfXqaN3BXPfJeS3gxV9Fm86eAsW95YdXTOgPWbCC/GAgVuXi6Aot6DkQ==
"@ntlab/sfetch@^1.2.0":
version "1.2.0"
resolved "https://registry.npmjs.org/@ntlab/sfetch/-/sfetch-1.2.0.tgz"
integrity sha512-9SE4NnqWo8l6mG0rnAkgng6ozSamIpF3EC+GOTQGGa6eAC0tNJvzrylMz6YRjjEGH6mOfn7ZBAuKj5WIZUul6A==
dependencies:
axios "^1.7.9"
@ -1271,7 +1271,7 @@ acorn-walk@^8.1.1:
resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz"
integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==
agent-base@^7.1.3:
agent-base@^7.1.2, agent-base@^7.1.3:
version "7.1.3"
resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz"
integrity sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==
@ -2158,10 +2158,10 @@ entities@^4.2.0, entities@^4.3.0, entities@^4.4.0:
resolved "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz"
integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==
epg-grabber@^0.37.4:
version "0.37.4"
resolved "https://registry.npmjs.org/epg-grabber/-/epg-grabber-0.37.4.tgz"
integrity sha512-PS104bH9tHRa9kivSwx47AKMkfHwKy51XQTx+GO6sIXvIp2Z4LBpwMEXGcfPoAsdIGxgs2Wrl0dZ/QGL+7x6YQ==
epg-grabber@^0.38.0:
version "0.38.0"
resolved "https://registry.npmjs.org/epg-grabber/-/epg-grabber-0.38.0.tgz"
integrity sha512-jbwTgi6G7e+zrb2oNC0C7mcQYoRkFnvhXCurexeICaEy4avRB6WS5rD/yfqYoiqaXOM3x1BNBpCKFYoS7Ob5YA==
dependencies:
axios "^1.6.1"
axios-cache-interceptor "^0.10.3"
@ -2176,6 +2176,7 @@ epg-grabber@^0.37.4:
http-cookie-agent "^6.0.8"
lodash "^4.17.21"
node-gzip "^1.1.2"
socks-proxy-agent "^8.0.5"
tough-cookie "^5.0.0"
winston "^3.3.3"
xml-js "^1.6.11"
@ -2835,6 +2836,14 @@ inquirer@^8.2.6:
through "^2.3.6"
wrap-ansi "^6.0.1"
ip-address@^9.0.5:
version "9.0.5"
resolved "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz"
integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==
dependencies:
jsbn "1.1.0"
sprintf-js "^1.1.3"
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz"
@ -3373,6 +3382,11 @@ js-yaml@^4.1.0:
dependencies:
argparse "^2.0.1"
jsbn@1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz"
integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==
jsesc@^2.5.1:
version "2.5.2"
resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz"
@ -4393,6 +4407,28 @@ slash@^3.0.0:
resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
smart-buffer@^4.2.0:
version "4.2.0"
resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz"
integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
socks-proxy-agent@^8.0.5:
version "8.0.5"
resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz"
integrity sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==
dependencies:
agent-base "^7.1.2"
debug "^4.3.4"
socks "^2.8.3"
socks@^2.8.3:
version "2.8.3"
resolved "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz"
integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==
dependencies:
ip-address "^9.0.5"
smart-buffer "^4.2.0"
source-map-support@0.5.13:
version "0.5.13"
resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz"
@ -4406,6 +4442,11 @@ source-map@^0.6.0, source-map@^0.6.1:
resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
sprintf-js@^1.1.3:
version "1.1.3"
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz"
integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"