mirror of
https://github.com/iptv-org/iptv
synced 2025-12-15 09:56:52 -05:00
Merge pull request #29977 from iptv-org/patch-2025.12.2
Patch 2025.12.2
This commit is contained in:
@@ -90,13 +90,13 @@ streams/fr.m3u
|
||||
│ 7 │ AlpedHuezTV.fr │ https://edge.vedge.infomaniak.com/livecast/ik:adhtv/chunklist.m3u8 │ HTTP_NOT_FOUND │
|
||||
```
|
||||
|
||||
After that, all you have to do is report any broken streams you find.
|
||||
Also, if you add the `--fix` option to the command, the script will automatically remove all broken streams it finds from your local copy of playlists:
|
||||
|
||||
### How to replace a broken stream?
|
||||
```sh
|
||||
npm run playlist:test streams/fr.m3u --- --fix
|
||||
```
|
||||
|
||||
This can be done either by filling out this [form](https://github.com/iptv-org/iptv/issues/new?assignees=&labels=streams%3Aedit&projects=&template=2_streams_edit.yml&title=Edit%3A+).
|
||||
|
||||
Either by directly updating the files in the [/streams](/streams) folder and then creating a [pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests).
|
||||
After that, all you need to do is report the broken streams you found via the [form](https://github.com/iptv-org/iptv/issues/new?assignees=&labels=streams:remove&projects=&template=3_streams_report.yml&title=Broken%3A+) or create a [pull request](https://github.com/iptv-org/iptv/pulls) with updated playlists.
|
||||
|
||||
### How to remove my channel from playlist?
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@ import { ROOT_DIR, STREAMS_DIR } from '../../constants'
|
||||
import { Logger, Collection } from '@freearhey/core'
|
||||
import { program, OptionValues } from 'commander'
|
||||
import { Storage } from '@freearhey/storage-js'
|
||||
import { Stream } from '../../models'
|
||||
import { Playlist, Stream } from '../../models'
|
||||
import { truncate } from '../../utils'
|
||||
import { loadData } from '../../api'
|
||||
import { eachLimit } from 'async'
|
||||
import dns from 'node:dns'
|
||||
import chalk from 'chalk'
|
||||
import os from 'node:os'
|
||||
import { truncate } from '../../utils'
|
||||
|
||||
const LIVE_UPDATE_INTERVAL = 5000
|
||||
const LIVE_UPDATE_MAX_STREAMS = 100
|
||||
@@ -21,6 +21,7 @@ const results: { [key: string]: string } = {}
|
||||
let interval: string | number | NodeJS.Timeout | undefined
|
||||
let streams = new Collection<Stream>()
|
||||
let isLiveUpdateEnabled = true
|
||||
const errorStatusCodes = ['ENOTFOUND', 'HTTP_404_NOT_FOUND', 'HTTP_404_UNKONWN_ERROR']
|
||||
|
||||
program
|
||||
.argument('[filepath...]', 'Path to file to test')
|
||||
@@ -37,12 +38,14 @@ program
|
||||
(value: string) => parseInt(value),
|
||||
30000
|
||||
)
|
||||
.option('--fix', 'Remove all broken links found from files')
|
||||
.parse(process.argv)
|
||||
|
||||
const options: OptionValues = program.opts()
|
||||
|
||||
const logger = new Logger()
|
||||
const tester = new StreamTester({ options })
|
||||
const rootStorage = new Storage(ROOT_DIR)
|
||||
|
||||
async function main() {
|
||||
if (await isOffline()) {
|
||||
@@ -54,7 +57,6 @@ async function main() {
|
||||
await loadData()
|
||||
|
||||
logger.info('loading streams...')
|
||||
const rootStorage = new Storage(ROOT_DIR)
|
||||
const parser = new PlaylistParser({
|
||||
storage: rootStorage
|
||||
})
|
||||
@@ -94,8 +96,9 @@ async function runTest(stream: Stream) {
|
||||
|
||||
const result: StreamTesterResult = await tester.test(stream)
|
||||
|
||||
stream.statusCode = result.status.code
|
||||
|
||||
let status = ''
|
||||
const errorStatusCodes = ['ENOTFOUND', 'HTTP_404_NOT_FOUND', 'HTTP_404_UNKONWN_ERROR']
|
||||
if (result.status.ok) status = chalk.green('OK')
|
||||
else if (errorStatusCodes.includes(result.status.code)) {
|
||||
status = chalk.red(result.status.code)
|
||||
@@ -144,7 +147,21 @@ function drawTable() {
|
||||
}
|
||||
}
|
||||
|
||||
function onFinish(error: Error | null | undefined) {
|
||||
async function removeBrokenLinks() {
|
||||
const streamsGrouped = streams.groupBy((stream: Stream) => stream.filepath)
|
||||
for (const filepath of streamsGrouped.keys()) {
|
||||
let streams: Collection<Stream> = new Collection(streamsGrouped.get(filepath))
|
||||
|
||||
streams = streams.filter((stream: Stream) =>
|
||||
!stream.statusCode ? true : !errorStatusCodes.includes(stream.statusCode)
|
||||
)
|
||||
|
||||
const playlist = new Playlist(streams, { public: false })
|
||||
await rootStorage.save(filepath, playlist.toString())
|
||||
}
|
||||
}
|
||||
|
||||
async function onFinish(error: Error | null | undefined) {
|
||||
clearInterval(interval)
|
||||
|
||||
if (error) {
|
||||
@@ -152,6 +169,10 @@ function onFinish(error: Error | null | undefined) {
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (options.fix) {
|
||||
await removeBrokenLinks()
|
||||
}
|
||||
|
||||
drawTable()
|
||||
|
||||
if (errors > 0 || warnings > 0) {
|
||||
|
||||
@@ -13,6 +13,7 @@ export class Stream extends sdk.Models.Stream {
|
||||
removed: boolean = false
|
||||
tvgId?: string
|
||||
label: string | null
|
||||
statusCode?: string
|
||||
|
||||
updateWithIssue(issueData: IssueData): this {
|
||||
const data = {
|
||||
|
||||
3
tests/__data__/expected/playlist_test/ag.m3u
Normal file
3
tests/__data__/expected/playlist_test/ag.m3u
Normal file
@@ -0,0 +1,3 @@
|
||||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="ABSTV.ag",ABS TV
|
||||
https://tego-cdn2a.sibercdn.com/Live_TV-ABSTV-10/tracks-v3a1/rewind-7200.m3u8?token=e5f61e7be8363eb781b4bdfe591bf917dd529c1a-SjY3NzRTbDZQNnFQVkZaNkZja2RxV3JKc1VBa05zQkdMNStJakRGV0VTTzNrOEVGVUlIQmxta1NLV0o3bzdVdQ-1736094545-1736008145
|
||||
@@ -3,7 +3,7 @@ module.exports = {
|
||||
{
|
||||
url: 'https://query-streamlink.herokuapp.com/iptv-query?streaming-ip=https://www.twitch.tv/absliveantigua3',
|
||||
http: { referrer: '', 'user-agent': '' },
|
||||
status: { ok: false, code: 'HTTP_NOT_FOUND', message: 'HTTP 404 Not Found' }
|
||||
status: { ok: false, code: 'HTTP_404_NOT_FOUND', message: 'HTTP 404 Not Found' }
|
||||
},
|
||||
'https://tego-cdn2a.sibercdn.com/Live_TV-ABSTV-10/tracks-v3a1/rewind-7200.m3u8?token=e5f61e7be8363eb781b4bdfe591bf917dd529c1a-SjY3NzRTbDZQNnFQVkZaNkZja2RxV3JKc1VBa05zQkdMNStJakRGV0VTTzNrOEVGVUlIQmxta1NLV0o3bzdVdQ-1736094545-1736008145':
|
||||
{
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
#EXTINF:-1 tvg-id="ABSTV.ag",ABS TV
|
||||
https://tego-cdn2a.sibercdn.com/Live_TV-ABSTV-10/tracks-v3a1/rewind-7200.m3u8?token=e5f61e7be8363eb781b4bdfe591bf917dd529c1a-SjY3NzRTbDZQNnFQVkZaNkZja2RxV3JKc1VBa05zQkdMNStJakRGV0VTTzNrOEVGVUlIQmxta1NLV0o3bzdVdQ-1736094545-1736008145
|
||||
#EXTINF:-1 tvg-id="ABSTV.ag@HD",ABS TV (1080p) [Not 24/7]
|
||||
https://query-streamlink.herokuapp.com/iptv-query?streaming-ip=https://www.twitch.tv/absliveantigua3
|
||||
https://query-streamlink.herokuapp.com/iptv-query?streaming-ip=https://www.twitch.tv/absliveantigua3
|
||||
@@ -1,21 +1,62 @@
|
||||
import { execSync } from 'child_process'
|
||||
import child_process from 'node:child_process'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import { promisify } from 'node:util'
|
||||
import * as fs from 'fs-extra'
|
||||
import { glob } from 'glob'
|
||||
|
||||
const exec = promisify(child_process.exec)
|
||||
|
||||
type ExecError = {
|
||||
status: number
|
||||
stdout: string
|
||||
}
|
||||
|
||||
const ENV_VAR = 'cross-env ROOT_DIR=tests/__data__/input DATA_DIR=tests/__data__/input/data'
|
||||
const ENV_VAR = 'cross-env DATA_DIR=tests/__data__/input/data ROOT_DIR=tests/__data__/output'
|
||||
|
||||
beforeEach(() => {
|
||||
fs.emptyDirSync('tests/__data__/output')
|
||||
fs.copySync('tests/__data__/input/playlist_test/streams', 'tests/__data__/output/streams')
|
||||
})
|
||||
|
||||
describe('playlist:test', () => {
|
||||
it('shows an error if the playlist contains a broken link', () => {
|
||||
const cmd = `${ENV_VAR} npm run playlist:test playlist_test/ag.m3u`
|
||||
it('shows an error if the playlist contains a broken link', async () => {
|
||||
const cmd = `${ENV_VAR} npm run playlist:test streams/ag.m3u`
|
||||
|
||||
try {
|
||||
execSync(cmd, { encoding: 'utf8' })
|
||||
await exec(cmd, { encoding: 'utf8' })
|
||||
if (process.env.DEBUG === 'true') console.log(cmd)
|
||||
process.exit(0)
|
||||
} catch (error) {
|
||||
if (process.env.DEBUG === 'true') console.log(cmd, error)
|
||||
expect((error as ExecError).stdout).toContain('playlist_test/ag.m3u')
|
||||
expect((error as ExecError).stdout).toContain('streams/ag.m3u')
|
||||
expect((error as ExecError).stdout).toContain('2 problems (1 errors, 1 warnings)')
|
||||
}
|
||||
})
|
||||
|
||||
it('it can remove all broken links from the playlist', async () => {
|
||||
const cmd = `${ENV_VAR} npm run playlist:test streams/ag.m3u --- --fix`
|
||||
try {
|
||||
await exec(cmd, { encoding: 'utf8' })
|
||||
if (process.env.DEBUG === 'true') console.log(cmd)
|
||||
process.exit(0)
|
||||
} catch (error) {
|
||||
if (process.env.DEBUG === 'true') console.log(cmd, error)
|
||||
const files = glob.sync('tests/__data__/expected/playlist_test/*.m3u').map(filepath => {
|
||||
const fileUrl = pathToFileURL(filepath).toString()
|
||||
const pathToRemove = pathToFileURL('tests/__data__/expected/playlist_test/').toString()
|
||||
|
||||
return fileUrl.replace(pathToRemove, '')
|
||||
})
|
||||
|
||||
files.forEach(filepath => {
|
||||
expect(content(`tests/__data__/output/streams/${filepath}`)).toBe(
|
||||
content(`tests/__data__/expected/playlist_test/${filepath}`)
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
function content(filepath: string) {
|
||||
return fs.readFileSync(pathToFileURL(filepath), { encoding: 'utf8' })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user