Merge pull request #30055 from iptv-org/patch-2025.12.3
Patch 2025.12.3
This commit is contained in:
@@ -77,17 +77,17 @@ This command will run an automatic check of all links in the playlist and displa
|
|||||||
npm run playlist:test streams/fr.m3u
|
npm run playlist:test streams/fr.m3u
|
||||||
|
|
||||||
streams/fr.m3u
|
streams/fr.m3u
|
||||||
┌─────┬───────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────┬───────────────────────────┐
|
┌─────┬───────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────┬────────────────┬───────────────────────────┐
|
||||||
│ │ tvg-id │ url │ status │
|
│ │ tvg-id │ url │ label │ status │
|
||||||
├─────┼───────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────┤
|
├─────┼───────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┼────────────────┼───────────────────────────┤
|
||||||
│ 0 │ 6ter.fr │ https://origin-caf900c010ea8046.live.6cloud.fr/out/v1/29c7a579af3348b48230f76cd75699a5/dash_short... │ LOADING... │
|
│ 0 │ 6ter.fr │ https://origin-caf900c010ea8046.live.6cloud.fr/out/v1/29c7a579af3348b48230f76cd75699a5/dash_short... │ │ LOADING... │
|
||||||
│ 1 │ 20MinutesTV.fr │ https://lives.digiteka.com/stream/86d3e867-a272-496b-8412-f59aa0104771/index.m3u8 │ FFMPEG_STREAMS_NOT_FOUND │
|
│ 1 │ 20MinutesTV.fr │ https://lives.digiteka.com/stream/86d3e867-a272-496b-8412-f59aa0104771/index.m3u8 │ │ FFMPEG_STREAMS_NOT_FOUND │
|
||||||
│ 2 │ │ https://video1.getstreamhosting.com:1936/8420/8420/playlist.m3u8 │ OK │
|
│ 2 │ │ https://video1.getstreamhosting.com:1936/8420/8420/playlist.m3u8 │ │ OK │
|
||||||
│ 3 │ ADNTVPlus.fr │ https://samsunguk-adn-samsung-fre-qfrlc.amagi.tv/playlist/samsunguk-adn-samsung-fre/playlist.m3u8 │ HTTP_FORBIDDEN │
|
│ 3 │ ADNTVPlus.fr │ https://samsunguk-adn-samsung-fre-qfrlc.amagi.tv/playlist/samsunguk-adn-samsung-fre/playlist.m3u8 │ Geo-blocked │ HTTP_FORBIDDEN │
|
||||||
│ 4 │ Africa24.fr │ https://edge12.vedge.infomaniak.com/livecast/ik:africa24/manifest.m3u8 │ OK │
|
│ 4 │ Africa24.fr │ https://edge12.vedge.infomaniak.com/livecast/ik:africa24/manifest.m3u8 │ │ OK │
|
||||||
│ 5 │ Africa24English.fr │ https://edge17.vedge.infomaniak.com/livecast/ik:africa24sport/manifest.m3u8 │ OK │
|
│ 5 │ Africa24English.fr │ https://edge17.vedge.infomaniak.com/livecast/ik:africa24sport/manifest.m3u8 │ │ OK │
|
||||||
│ 6 │ AfricanewsEnglish.fr │ https://37c774660687468c821a51190046facf.mediatailor.us-east-1.amazonaws.com/v1/master/04fd913bb2... │ HTTP_GATEWAY_TIMEOUT │
|
│ 6 │ AfricanewsEnglish.fr │ https://37c774660687468c821a51190046facf.mediatailor.us-east-1.amazonaws.com/v1/master/04fd913bb2... │ │ HTTP_GATEWAY_TIMEOUT │
|
||||||
│ 7 │ AlpedHuezTV.fr │ https://edge.vedge.infomaniak.com/livecast/ik:adhtv/chunklist.m3u8 │ HTTP_NOT_FOUND │
|
│ 7 │ AlpedHuezTV.fr │ https://edge.vedge.infomaniak.com/livecast/ik:adhtv/chunklist.m3u8 │ Not 24/7 │ HTTP_NOT_FOUND │
|
||||||
```
|
```
|
||||||
|
|
||||||
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:
|
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:
|
||||||
|
|||||||
@@ -17,11 +17,18 @@ const LIVE_UPDATE_MAX_STREAMS = 100
|
|||||||
|
|
||||||
let errors = 0
|
let errors = 0
|
||||||
let warnings = 0
|
let warnings = 0
|
||||||
const results: { [key: string]: string } = {}
|
|
||||||
let interval: string | number | NodeJS.Timeout | undefined
|
let interval: string | number | NodeJS.Timeout | undefined
|
||||||
let streams = new Collection<Stream>()
|
let streams = new Collection<Stream>()
|
||||||
let isLiveUpdateEnabled = true
|
let isLiveUpdateEnabled = true
|
||||||
const errorStatusCodes = ['ENOTFOUND', 'HTTP_404_NOT_FOUND', 'HTTP_404_UNKONWN_ERROR']
|
const errorStatusCodes = [
|
||||||
|
'ENOTFOUND',
|
||||||
|
'ENETUNREACH',
|
||||||
|
'ECONNREFUSED',
|
||||||
|
'HTTP_404_NOT_FOUND',
|
||||||
|
'HTTP_404_UNKONWN_ERROR',
|
||||||
|
'HTTP_403_FORBIDDEN',
|
||||||
|
'HTTP_401_UNAUTHORIZED'
|
||||||
|
]
|
||||||
|
|
||||||
program
|
program
|
||||||
.argument('[filepath...]', 'Path to file to test')
|
.argument('[filepath...]', 'Path to file to test')
|
||||||
@@ -91,24 +98,16 @@ async function main() {
|
|||||||
main()
|
main()
|
||||||
|
|
||||||
async function runTest(stream: Stream) {
|
async function runTest(stream: Stream) {
|
||||||
const key = stream.getUniqKey()
|
stream.statusCode = 'LOADING...'
|
||||||
results[key] = chalk.white('LOADING...')
|
|
||||||
|
|
||||||
const result: StreamTesterResult = await tester.test(stream)
|
const result: StreamTesterResult = await tester.test(stream)
|
||||||
|
|
||||||
stream.statusCode = result.status.code
|
stream.statusCode = result.status.code
|
||||||
|
|
||||||
let status = ''
|
if (stream.statusCode === 'OK') return
|
||||||
if (result.status.ok) status = chalk.green('OK')
|
if (errorStatusCodes.includes(stream.statusCode) && !stream.label) {
|
||||||
else if (errorStatusCodes.includes(result.status.code)) {
|
|
||||||
status = chalk.red(result.status.code)
|
|
||||||
errors++
|
errors++
|
||||||
} else {
|
} else {
|
||||||
status = chalk.yellow(result.status.code)
|
|
||||||
warnings++
|
warnings++
|
||||||
}
|
}
|
||||||
|
|
||||||
results[key] = status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawTable() {
|
function drawTable() {
|
||||||
@@ -124,19 +123,24 @@ function drawTable() {
|
|||||||
{ name: '', alignment: 'center', minLen: 3, maxLen: 3 },
|
{ name: '', alignment: 'center', minLen: 3, maxLen: 3 },
|
||||||
{ name: 'tvg-id', alignment: 'left', color: 'green', minLen: 25, maxLen: 25 },
|
{ name: 'tvg-id', alignment: 'left', color: 'green', minLen: 25, maxLen: 25 },
|
||||||
{ name: 'url', alignment: 'left', color: 'green', minLen: 100, maxLen: 100 },
|
{ name: 'url', alignment: 'left', color: 'green', minLen: 100, maxLen: 100 },
|
||||||
|
{ name: 'label', alignment: 'left', color: 'yellow', minLen: 13, maxLen: 13 },
|
||||||
{ name: 'status', alignment: 'left', minLen: 25, maxLen: 25 }
|
{ name: 'status', alignment: 'left', minLen: 25, maxLen: 25 }
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
streams.forEach((stream: Stream, index: number) => {
|
streams.forEach((stream: Stream, index: number) => {
|
||||||
const key = stream.getUniqKey()
|
const tvgId = truncate(stream.getTvgId(), 25)
|
||||||
const status = results[key] || chalk.gray('PENDING')
|
const url = truncate(stream.url, 100)
|
||||||
const tvgId = stream.getTvgId()
|
const color = getColor(stream)
|
||||||
|
const label = stream.label || ''
|
||||||
|
const status = stream.statusCode || 'PENDING'
|
||||||
|
|
||||||
const row = {
|
const row = {
|
||||||
'': index,
|
'': index,
|
||||||
'tvg-id': truncate(tvgId, 25),
|
'tvg-id': chalk[color](tvgId),
|
||||||
url: truncate(stream.url, 100),
|
url: chalk[color](url),
|
||||||
status
|
label: chalk[color](label),
|
||||||
|
status: chalk[color](status)
|
||||||
}
|
}
|
||||||
table.append(row)
|
table.append(row)
|
||||||
})
|
})
|
||||||
@@ -152,9 +156,7 @@ async function removeBrokenLinks() {
|
|||||||
for (const filepath of streamsGrouped.keys()) {
|
for (const filepath of streamsGrouped.keys()) {
|
||||||
let streams: Collection<Stream> = new Collection(streamsGrouped.get(filepath))
|
let streams: Collection<Stream> = new Collection(streamsGrouped.get(filepath))
|
||||||
|
|
||||||
streams = streams.filter((stream: Stream) =>
|
streams = streams.filter((stream: Stream) => !isBroken(stream))
|
||||||
!stream.statusCode ? true : !errorStatusCodes.includes(stream.statusCode)
|
|
||||||
)
|
|
||||||
|
|
||||||
const playlist = new Playlist(streams, { public: false })
|
const playlist = new Playlist(streams, { public: false })
|
||||||
await rootStorage.save(filepath, playlist.toString())
|
await rootStorage.save(filepath, playlist.toString())
|
||||||
@@ -196,3 +198,20 @@ async function isOffline() {
|
|||||||
})
|
})
|
||||||
}).catch(() => {})
|
}).catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getColor(stream: Stream): string {
|
||||||
|
if (!stream.statusCode) return 'gray'
|
||||||
|
if (stream.statusCode === 'LOADING...') return 'white'
|
||||||
|
if (stream.statusCode === 'OK') return 'green'
|
||||||
|
if (errorStatusCodes.includes(stream.statusCode) && !stream.label) return 'red'
|
||||||
|
|
||||||
|
return 'yellow'
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBroken(stream: Stream): boolean {
|
||||||
|
if (!stream.statusCode) return false
|
||||||
|
if (stream.label) return false
|
||||||
|
if (!errorStatusCodes.includes(stream.statusCode)) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
3
tests/__data__/expected/playlist_test/af.m3u
Normal file
3
tests/__data__/expected/playlist_test/af.m3u
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#EXTM3U
|
||||||
|
#EXTINF:-1 tvg-id="BaharTV.af@SD",Bahar TV (720p) [Not 24/7]
|
||||||
|
https://59d39900ebfb8.streamlock.net/bahartv/bahartv/playlist.m3u8
|
||||||
@@ -10,5 +10,10 @@ module.exports = {
|
|||||||
url: 'https://tego-cdn2a.sibercdn.com/Live_TV-ABSTV-10/tracks-v3a1/rewind-7200.m3u8?token=e5f61e7be8363eb781b4bdfe591bf917dd529c1a-SjY3NzRTbDZQNnFQVkZaNkZja2RxV3JKc1VBa05zQkdMNStJakRGV0VTTzNrOEVGVUlIQmxta1NLV0o3bzdVdQ-1736094545-1736008145',
|
url: 'https://tego-cdn2a.sibercdn.com/Live_TV-ABSTV-10/tracks-v3a1/rewind-7200.m3u8?token=e5f61e7be8363eb781b4bdfe591bf917dd529c1a-SjY3NzRTbDZQNnFQVkZaNkZja2RxV3JKc1VBa05zQkdMNStJakRGV0VTTzNrOEVGVUlIQmxta1NLV0o3bzdVdQ-1736094545-1736008145',
|
||||||
http: { referrer: '', 'user-agent': '' },
|
http: { referrer: '', 'user-agent': '' },
|
||||||
status: { ok: false, code: 'HTTP_FORBIDDEN', message: 'HTTP 403 Forbidden' }
|
status: { ok: false, code: 'HTTP_FORBIDDEN', message: 'HTTP 403 Forbidden' }
|
||||||
}
|
},
|
||||||
|
'https://59d39900ebfb8.streamlock.net/bahartv/bahartv/playlist.m3u8': {
|
||||||
|
url: 'https://59d39900ebfb8.streamlock.net/bahartv/bahartv/playlist.m3u8',
|
||||||
|
http: { referrer: '', 'user-agent': '' },
|
||||||
|
status: { ok: false, code: 'HTTP_404_NOT_FOUND', message: 'HTTP 404 Not Found' }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
tests/__data__/input/playlist_test/streams/af.m3u
Normal file
3
tests/__data__/input/playlist_test/streams/af.m3u
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#EXTM3U
|
||||||
|
#EXTINF:-1 tvg-id="BaharTV.af@SD",Bahar TV (720p) [Not 24/7]
|
||||||
|
https://59d39900ebfb8.streamlock.net/bahartv/bahartv/playlist.m3u8
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#EXTM3U
|
#EXTM3U
|
||||||
#EXTINF:-1 tvg-id="ABSTV.ag",ABS TV
|
#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
|
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]
|
#EXTINF:-1 tvg-id="ABSTV.ag@HD",ABS TV (1080p)
|
||||||
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
|
||||||
|
|||||||
Reference in New Issue
Block a user