iptv-checker 0.20.2 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.travis.yml CHANGED
@@ -1,7 +1,7 @@
1
1
  language: node_js
2
2
 
3
3
  node_js:
4
- - "10"
4
+ - "12"
5
5
 
6
6
  script:
7
7
  - npm run lint
package/README.md CHANGED
@@ -1,19 +1,17 @@
1
- # IPTV Checker [![Build Status](https://travis-ci.com/freearhey/iptv-checker.svg?branch=master)](https://travis-ci.com/freearhey/iptv-checker)
1
+ # IPTV Checker [![Build Status](https://app.travis-ci.com/freearhey/iptv-checker.svg?branch=master)](https://app.travis-ci.com/freearhey/iptv-checker)
2
2
 
3
3
  Node.js CLI tool for checking links in IPTV playlists.
4
4
 
5
- ## Installation
6
-
7
5
  This tool is based on the `ffmpeg` library, so you need to install it on your computer first. You can find the right installer for your system here: https://www.ffmpeg.org/download.html
8
6
 
9
- After that you can install the tool itself via npm:
7
+ ## Usage
8
+
9
+ ### CLI
10
10
 
11
11
  ```sh
12
12
  npm install -g iptv-checker
13
13
  ```
14
14
 
15
- ## Usage
16
-
17
15
  #### Check local playlist file:
18
16
 
19
17
  ```sh
@@ -40,6 +38,197 @@ Arguments:
40
38
  - `-k, --insecure`: allow insecure connections when using SSL
41
39
  - `-p, --parallel`: Batch size of channels to check concurrently (default to 1)
42
40
 
41
+ ### Node.js
42
+
43
+ ```sh
44
+ npm install iptv-checker
45
+ ```
46
+
47
+ ```js
48
+ var checker = require('iptv-checker')
49
+
50
+ // using playlist url
51
+ checker.checkPlaylist('https://some-playlist.lol/list.m3u').then(results => {
52
+ console.log(results)
53
+ })
54
+
55
+ // using local path
56
+ checker.checkPlaylist('path/to/playlist.m3u').then(results => {
57
+ console.log(results)
58
+ })
59
+
60
+ // using playlist as string
61
+ checker.checkPlaylist(string).then(results => {
62
+ console.log(results)
63
+ })
64
+ ```
65
+
66
+ #### Results
67
+
68
+ On success:
69
+
70
+ ```jsonc
71
+ {
72
+ "header": {
73
+ "attrs": {},
74
+ "raw": "#EXTM3U x-tvg-url=\"\""
75
+ },
76
+ "items": [
77
+ {
78
+ "name": "KBSV/AssyriaSat (720p) [Not 24/7]",
79
+ "tvg": {
80
+ "id": "KBSVAssyriaSat.us",
81
+ "name": "",
82
+ "language": "Assyrian Neo-Aramaic;English",
83
+ "country": "US",
84
+ "logo": "https://i.imgur.com/zEWSSdf.jpg",
85
+ "url": "",
86
+ "rec": ""
87
+ },
88
+ "group": {
89
+ "title": "General"
90
+ },
91
+ "http": {
92
+ "referrer": "",
93
+ "user-agent": ""
94
+ },
95
+ "url": "http://66.242.170.53/hls/live/temp/index.m3u8",
96
+ "raw": "#EXTINF:-1 tvg-id=\"KBSVAssyriaSat.us\" tvg-country=\"US\" tvg-language=\"Assyrian Neo-Aramaic;English\" tvg-logo=\"https://i.imgur.com/zEWSSdf.jpg\" group-title=\"General\",KBSV/AssyriaSat (720p) [Not 24/7]\r\nhttp://66.242.170.53/hls/live/temp/index.m3u8",
97
+ "line": 2,
98
+ "catchup": {
99
+ "type": "",
100
+ "days": "",
101
+ "source": ""
102
+ },
103
+ "timeshift": "",
104
+ "status": {
105
+ "ok": true,
106
+ "metadata": {
107
+ "streams": [
108
+ {
109
+ "index": 0,
110
+ "codec_name": "h264",
111
+ "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
112
+ "profile": "High",
113
+ "codec_type": "video",
114
+ "codec_tag_string": "[27][0][0][0]",
115
+ "codec_tag": "0x001b",
116
+ "width": 1280,
117
+ "height": 720,
118
+ "coded_width": 1280,
119
+ "coded_height": 720,
120
+ "closed_captions": 0,
121
+ "has_b_frames": 2,
122
+ "pix_fmt": "yuv420p",
123
+ "level": 31,
124
+ "chroma_location": "left",
125
+ "refs": 1,
126
+ "is_avc": "false",
127
+ "nal_length_size": "0",
128
+ "r_frame_rate": "30/1",
129
+ "avg_frame_rate": "0/0",
130
+ "time_base": "1/90000",
131
+ "start_pts": 943358850,
132
+ "start_time": "10481.765000",
133
+ "bits_per_raw_sample": "8",
134
+ "disposition": {
135
+ "default": 0,
136
+ "dub": 0,
137
+ "original": 0,
138
+ "comment": 0,
139
+ "lyrics": 0,
140
+ "karaoke": 0,
141
+ "forced": 0,
142
+ "hearing_impaired": 0,
143
+ "visual_impaired": 0,
144
+ "clean_effects": 0,
145
+ "attached_pic": 0,
146
+ "timed_thumbnails": 0
147
+ },
148
+ "tags": {
149
+ "variant_bitrate": "400000"
150
+ }
151
+ }
152
+ //...
153
+ ],
154
+ "format": {
155
+ "filename": "http://66.242.170.53/hls/live/temp/index.m3u8",
156
+ "nb_streams": 2,
157
+ "nb_programs": 1,
158
+ "format_name": "hls",
159
+ "format_long_name": "Apple HTTP Live Streaming",
160
+ "start_time": "10481.560589",
161
+ "size": "214",
162
+ "probe_score": 100
163
+ },
164
+ "requests": [
165
+ {
166
+ "method": "GET",
167
+ "url": "http://66.242.170.53/hls/live/temp/index.m3u8",
168
+ "headers": {
169
+ "User-Agent": "Lavf/58.76.100",
170
+ "Accept": "*/*",
171
+ "Range": "bytes=0-",
172
+ "Connection": "close",
173
+ "Host": "66.242.170.53",
174
+ "Icy-MetaData": "1"
175
+ }
176
+ }
177
+ //...
178
+ ]
179
+ }
180
+ }
181
+ }
182
+ //...
183
+ ]
184
+ }
185
+ ```
186
+
187
+ On error:
188
+
189
+ ```jsonc
190
+ {
191
+ "header": {
192
+ "attrs": {},
193
+ "raw": "#EXTM3U x-tvg-url=\"\""
194
+ },
195
+ "items": [
196
+ {
197
+ "name": "Addis TV (720p)",
198
+ "tvg": {
199
+ "id": "AddisTV.et",
200
+ "name": "",
201
+ "language": "Amharic",
202
+ "country": "ET",
203
+ "logo": "https://i.imgur.com/KAg6MOI.png",
204
+ "url": "",
205
+ "rec": ""
206
+ },
207
+ "group": {
208
+ "title": ""
209
+ },
210
+ "http": {
211
+ "referrer": "",
212
+ "user-agent": ""
213
+ },
214
+ "url": "https://rrsatrtmp.tulix.tv/addis1/addis1multi.smil/playlist.m3u8",
215
+ "raw": "#EXTINF:-1 tvg-id=\"AddisTV.et\" tvg-country=\"ET\" tvg-language=\"Amharic\" tvg-logo=\"https://i.imgur.com/KAg6MOI.png\" group-title=\"Undefined\",Addis TV (720p)\r\nhttps://rrsatrtmp.tulix.tv/addis1/addis1multi.smil/playlist.m3u8",
216
+ "line": 2,
217
+ "catchup": {
218
+ "type": "",
219
+ "days": "",
220
+ "source": ""
221
+ },
222
+ "timeshift": "",
223
+ "status": {
224
+ "ok": false,
225
+ "reason": "Operation timed out"
226
+ }
227
+ }
228
+ ]
229
+ }
230
+ ```
231
+
43
232
  ## Contribution
44
233
 
45
234
  If you find a bug or want to contribute to the code or documentation, you can help by submitting an [issue](https://github.com/freearhey/iptv-checker/issues) or a [pull request](https://github.com/freearhey/iptv-checker/pulls).
@@ -62,8 +62,6 @@ const config = {
62
62
 
63
63
  const logger = new Logger(config)
64
64
 
65
- logger.debug({ config })
66
-
67
65
  const outputDir =
68
66
  argv.output || `iptv-checker-${dateFormat(new Date(), 'd-m-yyyy-hh-MM-ss')}`
69
67
  const onlineFile = `${outputDir}/online.m3u`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iptv-checker",
3
- "version": "0.20.2",
3
+ "version": "0.23.0",
4
4
  "description": "Node.js CLI tool for checking links in IPTV playlists",
5
5
  "main": "src/index.js",
6
6
  "preferGlobal": true,
@@ -36,7 +36,7 @@
36
36
  "commander": "^2.20.0",
37
37
  "dateformat": "^3.0.3",
38
38
  "get-stdin": "^7.0.0",
39
- "iptv-playlist-parser": "^0.5.4",
39
+ "iptv-playlist-parser": "^0.11.0",
40
40
  "jest": "^27.0.6",
41
41
  "lodash.chunk": "^4.2.0",
42
42
  "progress": "^2.0.3",
@@ -45,10 +45,10 @@
45
45
  "devDependencies": {
46
46
  "babel-eslint": "^10.1.0",
47
47
  "del": "^5.1.0",
48
- "eslint": "^6.8.0",
48
+ "eslint": "^8.10.0",
49
49
  "eslint-config-prettier": "^6.10.1",
50
50
  "eslint-plugin-babel": "^5.3.0",
51
- "eslint-plugin-jest": "^24.3.6",
51
+ "eslint-plugin-jest": "^26.1.1",
52
52
  "eslint-plugin-prettier": "^3.1.2",
53
53
  "mkdirp": "^1.0.4",
54
54
  "prettier": "^2.0.4"
package/src/helper.js CHANGED
@@ -48,7 +48,7 @@ async function parsePlaylist(input) {
48
48
  return parse(data)
49
49
  }
50
50
 
51
- function parseStdout(output, item) {
51
+ function parseError(output, item) {
52
52
  const url = item.url
53
53
  const line = output.split('\n').find(l => {
54
54
  return l.indexOf(url) === 0
@@ -61,6 +61,41 @@ function parseStdout(output, item) {
61
61
  return line.replace(`${url}: `, '')
62
62
  }
63
63
 
64
+ function parseStderr(stderr) {
65
+ const requests = stderr
66
+ .split('\r\n\n')
67
+ .map(parseRequest)
68
+ .filter(l => l)
69
+
70
+ return { requests }
71
+ }
72
+
73
+ function parseRequest(string) {
74
+ const urlMatch = string.match(/Opening '(.*)' for reading/)
75
+ const url = urlMatch ? urlMatch[1] : null
76
+ if (!url) return null
77
+ const requestMatch = string.match(/request: (.|[\r\n])+/gm)
78
+ const request = requestMatch ? requestMatch[0] : null
79
+ if (!request) return null
80
+ const arr = request
81
+ .split('\n')
82
+ .map(l => l.trim())
83
+ .filter(l => l)
84
+ const methodMatch = arr[0].match(/request: (GET|POST)/)
85
+ const method = methodMatch ? methodMatch[1] : null
86
+ arr.shift()
87
+ if (!arr) return null
88
+ const headers = {}
89
+ arr.forEach(line => {
90
+ const parts = line.split(': ')
91
+ if (parts && parts[1]) {
92
+ headers[parts[0]] = parts[1]
93
+ }
94
+ })
95
+
96
+ return { method, url, headers }
97
+ }
98
+
64
99
  function checkItem(item) {
65
100
  const { config, logger } = this
66
101
 
@@ -69,12 +104,14 @@ function checkItem(item) {
69
104
  logger.debug(`EXECUTING: "${command}"`)
70
105
 
71
106
  return execAsync(command, { timeout: config.timeout })
72
- .then(({ stdout }) => {
73
- if (stdout && isJSON(stdout)) {
107
+ .then(({ stdout, stderr }) => {
108
+ if (stdout && isJSON(stdout) && stderr) {
74
109
  const metadata = JSON.parse(stdout)
75
110
  if (!metadata.streams.length) {
76
111
  return { ok: false, reason: 'No streams found' }
77
112
  }
113
+ const results = parseStderr(stderr)
114
+ metadata.requests = results.requests
78
115
 
79
116
  return { ok: true, metadata }
80
117
  }
@@ -82,34 +119,36 @@ function checkItem(item) {
82
119
  return { ok: false, reason: 'Parsing error' }
83
120
  })
84
121
  .catch(err => {
85
- const reason = parseStdout(err.message, item)
122
+ const reason = parseError(err.message, item)
86
123
 
87
124
  return { ok: false, reason }
88
125
  })
89
126
  }
90
127
 
91
128
  function buildCommand(item, config) {
92
- const { url, http = {} } = item
93
- const { referrer = ``, 'user-agent': itemUserAgent = `` } = http
94
- const userAgent = itemUserAgent.length ? itemUserAgent : config.userAgent
95
-
129
+ const userAgent = item.http['user-agent'] || config.userAgent
96
130
  let args = [
97
131
  `ffprobe`,
98
132
  `-of json`,
99
- `-v error`,
133
+ `-v debug`,
100
134
  `-hide_banner`,
101
135
  `-show_streams`,
136
+ `-show_format`,
102
137
  ]
103
138
 
104
- if (referrer.length) {
105
- args.push(`-headers`, `'Referer: ${referrer}'`)
139
+ if (config.timeout) {
140
+ args.push(`-timeout`, `"${config.timeout * 1000}"`)
141
+ }
142
+
143
+ if (item.http.referrer) {
144
+ args.push(`-headers`, `"Referer: ${item.http.referrer}"`)
106
145
  }
107
146
 
108
147
  if (userAgent) {
109
- args.push(`-user_agent`, `'${userAgent}'`)
148
+ args.push(`-user_agent`, `"${userAgent}"`)
110
149
  }
111
150
 
112
- args.push(`'${url}'`)
151
+ args.push(`"${item.url}"`)
113
152
 
114
153
  args = args.join(` `)
115
154
 
package/src/index.js CHANGED
@@ -90,19 +90,16 @@ class IPTVChecker {
90
90
  }
91
91
 
92
92
  async checkStream(item) {
93
- const config = this.config
94
- const logger = this.logger
95
-
96
- await config.beforeEach(item)
93
+ await this.config.beforeEach(item)
97
94
 
98
- item.status = await helper.checkItem.call({ config, logger }, item)
95
+ item.status = await helper.checkItem.call(this, item)
99
96
  if (item.status.ok) {
100
- logger.debug(`OK: ${item.url}`.green)
97
+ this.logger.debug(`OK: ${item.url}`.green)
101
98
  } else {
102
- logger.debug(`FAILED: ${item.url} (${item.status.reason})`.red)
99
+ this.logger.debug(`FAILED: ${item.url} (${item.status.reason})`.red)
103
100
  }
104
101
 
105
- await config.afterEach(item)
102
+ await this.config.afterEach(item)
106
103
 
107
104
  return item
108
105
  }
package/test/bin.test.js CHANGED
@@ -1,3 +1,4 @@
1
+ const { performance } = require('perf_hooks')
1
2
  const { execSync } = require('child_process')
2
3
  const mkdirp = require('mkdirp')
3
4
  const del = require('del')
@@ -41,3 +42,14 @@ test(`Should process a playlist URL`, () => {
41
42
 
42
43
  expect(stdoutResultTester(result)).toBeTruthy()
43
44
  })
45
+
46
+ test(`Should respect timeout argument`, () => {
47
+ let t0 = performance.now()
48
+ execSync(
49
+ `node ${pwd}/bin/iptv-checker.js -t 7000 -o ${pwd}/test/output ${pwd}/test/input/timeout.m3u`,
50
+ { encoding: 'utf8' }
51
+ )
52
+ let t1 = performance.now()
53
+
54
+ expect(t1 - t0).toBeGreaterThan(7000)
55
+ })
@@ -12,7 +12,8 @@ function resultTester(result) {
12
12
  Reflect.has(item, `status`) &&
13
13
  Reflect.has(item.status, `ok`) &&
14
14
  (Reflect.has(item.status, `reason`) ||
15
- Reflect.has(item.status, `metadata`))
15
+ (Reflect.has(item.status, `metadata`) &&
16
+ Reflect.has(item.status.metadata, `requests`)))
16
17
  )
17
18
  })
18
19
  }
@@ -1,4 +1,3 @@
1
-
2
1
  #EXTM3U x-tvg-url="http://195.154.221.171/epg/guidealbania.xml.gz"
3
2
 
4
3
  #EXTINF:0,offline
@@ -0,0 +1,3 @@
1
+ #EXTM3U
2
+ #EXTINF:-1,Connection timed out
3
+ https://streaming.3eeshalaan.net/AAAFinalFeed/AlAanFeed_live.m3u8