Merge pull request #25504 from iptv-org/be-maintenance

[OVERALL] Maintenance release 1 - 07/2025
This commit is contained in:
Dum4G
2025-08-05 21:55:47 +03:00
committed by GitHub
19 changed files with 2499 additions and 1333 deletions

View File

@@ -5431,7 +5431,6 @@ Playlists in which channels are grouped by subdivision for which they are broadc
<tr><th align="left">Subdivision</th><th align="left">Channels</th><th align="left">Playlist</th></tr>
</thead>
<tbody>
<tr><td>Ahal</td><td align="right">16</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/tm-a.m3u</code></td></tr>
<tr><td>Balkan</td><td align="right">16</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/tm-b.m3u</code></td></tr>
<tr><td>Dasoguz</td><td align="right">16</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/tm-d.m3u</code></td></tr>
<tr><td>Lebap</td><td align="right">16</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/tm-l.m3u</code></td></tr>
@@ -5608,6 +5607,7 @@ Playlists in which channels are grouped by subdivision for which they are broadc
<tr><th align="left">Subdivision</th><th align="left">Channels</th><th align="left">Playlist</th></tr>
</thead>
<tbody>
<tr><td>Ash Shariqah</td><td align="right">86</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/ae-sh.m3u</code></td></tr>
<tr><td>'Ajman</td><td align="right">86</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/ae-aj.m3u</code></td></tr>
<tr><td>Al Fujayrah</td><td align="right">86</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/ae-fu.m3u</code></td></tr>
<tr><td>Abu Zaby</td><td align="right">86</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/ae-az.m3u</code></td></tr>
@@ -5742,6 +5742,7 @@ Playlists in which channels are grouped by subdivision for which they are broadc
<tr><td>Shefa</td><td align="right">7</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/vu-see.m3u</code></td></tr>
<tr><td>Tafea</td><td align="right">7</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/vu-tae.m3u</code></td></tr>
<tr><td>Torba</td><td align="right">7</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/vu-tob.m3u</code></td></tr>
<tr><td>Tafea</td><td align="right">7</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/vu-tae.m3u</code></td></tr>
</tbody>
</table>
</details>
@@ -5886,6 +5887,7 @@ Playlists in which channels are grouped by subdivision for which they are broadc
<tr><td>Ibb</td><td align="right">55</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/ye-ib.m3u</code></td></tr>
<tr><td>Sa'dah</td><td align="right">55</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/ye-sd.m3u</code></td></tr>
<tr><td>Ma'rib</td><td align="right">55</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/ye-ma.m3u</code></td></tr>
<tr><td>Hajjah</td><td align="right">55</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/ye-hj.m3u</code></td></tr>
<tr><td>San'a'</td><td align="right">55</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/ye-sn.m3u</code></td></tr>
<tr><td>Shabwah</td><td align="right">55</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/ye-sh.m3u</code></td></tr>
<tr><td>Ta'izz</td><td align="right">55</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/ye-ta.m3u</code></td></tr>

View File

@@ -4,6 +4,7 @@ import tsParser from '@typescript-eslint/parser'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import js from '@eslint/js'
import stylistic from '@stylistic/eslint-plugin'
import { FlatCompat } from '@eslint/eslintrc'
const __filename = fileURLToPath(import.meta.url)
@@ -18,7 +19,8 @@ export default [
...compat.extends('eslint:recommended', 'plugin:@typescript-eslint/recommended'),
{
plugins: {
'@typescript-eslint': typescriptEslint
'@typescript-eslint': typescriptEslint,
'@stylistic': stylistic
},
languageOptions: {
@@ -42,7 +44,7 @@ export default [
}
],
'linebreak-style': ['error', 'windows'],
'@stylistic/linebreak-style': ['error', 'windows'],
quotes: ['error', 'single'],
semi: ['error', 'never']
}

3623
package-lock.json generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -38,48 +38,40 @@
"private": true,
"license": "MIT",
"dependencies": {
"@eslint/eslintrc": "^3.3.0",
"@eslint/js": "^9.21.0",
"@freearhey/core": "^0.9.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.32.0",
"@freearhey/core": "^0.10.2",
"@freearhey/search-js": "^0.1.2",
"@inquirer/prompts": "^7.4.1",
"@octokit/core": "^6.1.4",
"@octokit/plugin-paginate-rest": "^11.4.3",
"@octokit/plugin-rest-endpoint-methods": "^7.1.3",
"@octokit/types": "^11.1.0",
"@swc/jest": "^0.2.38",
"@types/cli-progress": "^3.11.3",
"@inquirer/prompts": "^7.8.0",
"@octokit/core": "^7.0.3",
"@octokit/plugin-paginate-rest": "^13.1.1",
"@octokit/plugin-rest-endpoint-methods": "^16.0.0",
"@octokit/types": "^14.1.0",
"@stylistic/eslint-plugin": "^5.2.2",
"@swc/jest": "^0.2.39",
"@types/cli-progress": "^3.11.6",
"@types/fs-extra": "^11.0.4",
"@types/jest": "^29.5.14",
"@types/lodash": "^4.14.198",
"@types/numeral": "^2.0.3",
"@typescript-eslint/eslint-plugin": "^8.18.1",
"@typescript-eslint/parser": "^8.18.1",
"@types/jest": "^30.0.0",
"@types/lodash.uniqueid": "^4.0.9",
"@typescript-eslint/eslint-plugin": "^8.38.0",
"@typescript-eslint/parser": "^8.38.0",
"async-es": "^3.2.6",
"axios": "^1.7.9",
"chalk": "^4.1.2",
"axios": "^1.11.0",
"chalk": "^5.4.1",
"cli-progress": "^3.12.0",
"command-exists": "^1.2.9",
"commander": "^8.3.0",
"console-table-printer": "^2.12.1",
"eslint": "^9.17.0",
"glob": "^11.0.2",
"globals": "^16.0.0",
"commander": "^14.0.0",
"console-table-printer": "^2.14.6",
"cross-env": "^10.0.0",
"eslint": "^9.32.0",
"glob": "^11.0.3",
"globals": "^16.3.0",
"iptv-checker": "^0.29.1",
"iptv-playlist-parser": "^0.15.0",
"jest": "^29.7.0",
"jest": "^30.0.5",
"jest-expect-message": "^1.1.3",
"lodash": "^4.17.21",
"lodash.uniqueid": "^4.0.1",
"m3u-linter": "^0.4.2",
"markdown-include": "^0.4.3",
"node-cleanup": "^2.1.2",
"numeral": "^2.0.6",
"tsx": "^4.6.2",
"valid-url": "^1.0.9"
},
"overrides": {
"jest": {
"glob": "11.0.2"
}
"tsx": "^4.20.3"
}
}

View File

@@ -4,7 +4,7 @@ import { DATA_DIR, LOGS_DIR, STREAMS_DIR } from '../../constants'
import type { DataLoaderData } from '../../types/dataLoader'
import { Logger, Storage, File } from '@freearhey/core'
import { Stream } from '../../models'
import { uniqueId } from 'lodash'
import uniqueId from 'lodash.uniqueid'
import {
IndexCategoryGenerator,
IndexLanguageGenerator,

View File

@@ -4,8 +4,8 @@ import { PlaylistParser, StreamTester, CliTable, DataProcessor, DataLoader } fro
import { Stream } from '../../models'
import { program } from 'commander'
import { eachLimit } from 'async-es'
import commandExists from 'command-exists'
import chalk from 'chalk'
import child_process from 'node:child_process'
import os from 'node:os'
import dns from 'node:dns'
import type { DataLoaderData } from '../../types/dataLoader'
@@ -46,10 +46,12 @@ async function main() {
return
}
if (!commandExists.sync('ffprobe')) {
try {
child_process.execSync('ffprobe -version', { stdio: 'ignore' })
} catch {
logger.error(
chalk.red(
'For the script to work, the ffprobe library must be installed (https://ffmpeg.org/download.html)'
'For the script to work, the "ffprobe" library must be installed (https://ffmpeg.org/download.html)'
)
)

View File

@@ -1,9 +1,16 @@
import { ApiClient } from './apiClient'
import { Storage } from '@freearhey/core'
import cliProgress, { MultiBar } from 'cli-progress'
import numeral from 'numeral'
import type { DataLoaderProps, DataLoaderData } from '../types/dataLoader'
const formatBytes = (bytes: number) => {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
}
export class DataLoader {
client: ApiClient
storage: Storage
@@ -21,8 +28,8 @@ export class DataLoader {
const filename = payload.filename.padEnd(18, ' ')
const barsize = options.barsize || 40
const percent = (params.progress * 100).toFixed(2)
const speed = payload.speed ? numeral(payload.speed).format('0.0 b') + '/s' : 'N/A'
const total = numeral(params.total).format('0.0 b')
const speed = payload.speed ? formatBytes(payload.speed) + '/s' : 'N/A'
const total = formatBytes(params.total)
const completeSize = Math.round(params.progress * barsize)
const incompleteSize = barsize - completeSize
const bar =

View File

@@ -1,4 +1,5 @@
import markdownInclude from 'markdown-include'
import fs from 'fs'
import path from 'path'
export class Markdown {
filepath: string
@@ -8,6 +9,33 @@ export class Markdown {
}
compile() {
markdownInclude.compileFiles(this.filepath)
const config = JSON.parse(fs.readFileSync(this.filepath, 'utf8'))
const workingDir = process.cwd()
config.files.forEach((templateFile: string) => {
const templatePath = path.resolve(workingDir, templateFile)
const content = fs.readFileSync(templatePath, 'utf8')
const processedContent = this.processIncludes(content, workingDir)
if (config.build) {
const outputPath = path.resolve(workingDir, config.build)
fs.writeFileSync(outputPath, processedContent, 'utf8')
}
})
}
private processIncludes(content: string, baseDir: string): string {
const includeRegex = /#include\s+"([^"]+)"/g
return content.replace(includeRegex, (match, includePath) => {
try {
const fullPath = path.resolve(baseDir, includePath)
const includeContent = fs.readFileSync(fullPath, 'utf8')
return this.processIncludes(includeContent, baseDir)
} catch (error) {
console.warn(`Warning: Could not include file ${includePath}: ${error}`)
return match
}
})
}
}

View File

@@ -1 +0,0 @@
declare module 'markdown-include'

View File

@@ -1,5 +1,8 @@
import validUrl from 'valid-url'
export function isURI(string: string) {
return validUrl.isUri(encodeURI(string))
export function isURI(string: string): boolean {
try {
new URL(string)
return true
} catch {
return false
}
}

View File

@@ -1,14 +1,9 @@
import { pathToFileURL } from 'node:url'
import { execSync } from 'child_process'
import fs from 'fs-extra'
import os from 'os'
let ENV_VAR =
'DATA_DIR=tests/__data__/input/data STREAMS_DIR=tests/__data__/input/api_generate API_DIR=tests/__data__/output/.api'
if (os.platform() === 'win32') {
ENV_VAR =
'SET "DATA_DIR=tests/__data__/input/data" && SET "STREAMS_DIR=tests/__data__/input/api_generate" && SET "API_DIR=tests/__data__/output/.api" &&'
}
const ENV_VAR =
'cross-env DATA_DIR=tests/__data__/input/data STREAMS_DIR=tests/__data__/input/api_generate API_DIR=tests/__data__/output/.api'
beforeEach(() => {
fs.emptyDirSync('tests/__data__/output')

View File

@@ -1,16 +1,12 @@
import { execSync } from 'child_process'
import fs from 'fs-extra'
import os from 'os'
type ExecError = {
status: number
stdout: string
}
let ENV_VAR = 'DATA_DIR=tests/__data__/input/data'
if (os.platform() === 'win32') {
ENV_VAR = 'SET "DATA_DIR=tests/__data__/input/data" &&'
}
const ENV_VAR = 'cross-env DATA_DIR=tests/__data__/input/data'
beforeEach(() => {
fs.emptyDirSync('tests/__data__/output')

View File

@@ -2,13 +2,8 @@ import { pathToFileURL } from 'node:url'
import { execSync } from 'child_process'
import * as fs from 'fs-extra'
import { glob } from 'glob'
import os from 'os'
let ENV_VAR = 'STREAMS_DIR=tests/__data__/output/streams DATA_DIR=tests/__data__/input/data'
if (os.platform() === 'win32') {
ENV_VAR =
'SET "STREAMS_DIR=tests/__data__/output/streams" && SET "DATA_DIR=tests/__data__/input/data" &&'
}
const ENV_VAR = 'cross-env STREAMS_DIR=tests/__data__/output/streams DATA_DIR=tests/__data__/input/data'
beforeEach(() => {
fs.emptyDirSync('tests/__data__/output')
@@ -29,7 +24,7 @@ describe('playlist:format', () => {
})
files.forEach(filepath => {
expect(content(`tests/__data__/output/streams/${filepath}`), filepath).toBe(
expect(content(`tests/__data__/output/streams/${filepath}`)).toBe(
content(`tests/__data__/expected/playlist_format/${filepath}`)
)
})

View File

@@ -1,15 +1,10 @@
import { pathToFileURL } from 'node:url'
import { execSync } from 'child_process'
import os, { EOL } from 'node:os'
import { EOL } from 'node:os'
import * as fs from 'fs-extra'
import * as glob from 'glob'
let ENV_VAR =
'STREAMS_DIR=tests/__data__/input/playlist_generate DATA_DIR=tests/__data__/input/data PUBLIC_DIR=tests/__data__/output/.gh-pages LOGS_DIR=tests/__data__/output/logs'
if (os.platform() === 'win32') {
ENV_VAR =
'SET "STREAMS_DIR=tests/__data__/input/playlist_generate" && SET "DATA_DIR=tests/__data__/input/data" && SET "PUBLIC_DIR=tests/__data__/output/.gh-pages" && SET "LOGS_DIR=tests/__data__/output/logs" &&'
}
const ENV_VAR = 'cross-env STREAMS_DIR=tests/__data__/input/playlist_generate DATA_DIR=tests/__data__/input/data PUBLIC_DIR=tests/__data__/output/.gh-pages LOGS_DIR=tests/__data__/output/logs'
beforeEach(() => {
fs.emptyDirSync('tests/__data__/output')
@@ -31,7 +26,7 @@ describe('playlist:generate', () => {
})
playlists.forEach((filepath: string) => {
expect(content(`tests/__data__/output/${filepath}`), filepath).toBe(
expect(content(`tests/__data__/output/${filepath}`)).toBe(
content(`tests/__data__/expected/playlist_generate/${filepath}`)
)
})

View File

@@ -1,15 +1,11 @@
import { execSync } from 'child_process'
import os from 'node:os'
type ExecError = {
status: number
stdout: string
}
let ENV_VAR = 'ROOT_DIR=tests/__data__/input DATA_DIR=tests/__data__/input/data'
if (os.platform() === 'win32') {
ENV_VAR = 'SET "ROOT_DIR=tests/__data__/input" && SET "DATA_DIR=tests/__data__/input/data" &&'
}
const ENV_VAR = 'cross-env ROOT_DIR=tests/__data__/input DATA_DIR=tests/__data__/input/data'
describe('playlist:test', () => {
it('shows an error if the playlist contains a broken link', () => {

View File

@@ -2,13 +2,8 @@ import { pathToFileURL } from 'node:url'
import { execSync } from 'child_process'
import * as fs from 'fs-extra'
import { glob } from 'glob'
import os from 'os'
let ENV_VAR = 'DATA_DIR=tests/__data__/input/data STREAMS_DIR=tests/__data__/output/streams'
if (os.platform() === 'win32') {
ENV_VAR =
'SET "DATA_DIR=tests/__data__/input/data" && SET "STREAMS_DIR=tests/__data__/output/streams" &&'
}
const ENV_VAR = 'cross-env DATA_DIR=tests/__data__/input/data STREAMS_DIR=tests/__data__/output/streams'
beforeEach(() => {
fs.emptyDirSync('tests/__data__/output')
@@ -29,7 +24,7 @@ describe('playlist:update', () => {
})
files.forEach(filepath => {
expect(content(`tests/__data__/output/streams/${filepath}`), filepath).toBe(
expect(content(`tests/__data__/output/streams/${filepath}`)).toBe(
content(`tests/__data__/expected/playlist_update/${filepath}`)
)
})

View File

@@ -1,16 +1,11 @@
import { execSync } from 'child_process'
import os from 'os'
type ExecError = {
status: number
stdout: string
}
let ENV_VAR = 'DATA_DIR=tests/__data__/input/data ROOT_DIR=tests/__data__/input/playlist_validate'
if (os.platform() === 'win32') {
ENV_VAR =
'SET "DATA_DIR=tests/__data__/input/data" && SET "ROOT_DIR=tests/__data__/input/playlist_validate" &&'
}
const ENV_VAR = 'cross-env DATA_DIR=tests/__data__/input/data ROOT_DIR=tests/__data__/input/playlist_validate'
describe('playlist:validate', () => {
it('show an error if channel id in the blocklist', () => {

View File

@@ -1,14 +1,8 @@
import { pathToFileURL } from 'node:url'
import { execSync } from 'child_process'
import fs from 'fs-extra'
import os from 'os'
let ENV_VAR =
'DATA_DIR=tests/__data__/input/data LOGS_DIR=tests/__data__/input/readme_update README_DIR=tests/__data__/output/.readme'
if (os.platform() === 'win32') {
ENV_VAR =
'SET "DATA_DIR=tests/__data__/input/data" && SET "LOGS_DIR=tests/__data__/input/readme_update" && SET "README_DIR=tests/__data__/output/.readme" &&'
}
const ENV_VAR = 'cross-env DATA_DIR=tests/__data__/input/data LOGS_DIR=tests/__data__/input/readme_update README_DIR=tests/__data__/output/.readme'
beforeEach(() => {
fs.emptyDirSync('tests/__data__/output')

View File

@@ -1,11 +1,6 @@
import { execSync } from 'child_process'
import os from 'os'
let ENV_VAR = 'DATA_DIR=tests/__data__/input/data STREAMS_DIR=tests/__data__/input/report_create'
if (os.platform() === 'win32') {
ENV_VAR =
'SET "DATA_DIR=tests/__data__/input/data" && SET "STREAMS_DIR=tests/__data__/input/report_create" &&'
}
const ENV_VAR = 'cross-env DATA_DIR=tests/__data__/input/data STREAMS_DIR=tests/__data__/input/report_create'
describe('report:create', () => {
it('can create report', () => {