Merge pull request #29894 from iptv-org/patch-2025.12.1

Patch 2025.12.1
This commit is contained in:
Alstruit
2025-12-05 00:26:30 -06:00
committed by GitHub
40 changed files with 151 additions and 279 deletions

View File

@@ -60,13 +60,6 @@ body:
label: HTTP Referrer
placeholder: 'https://example.com/'
- type: textarea
id: directives
attributes:
label: Directives
description: 'List of directives telling players how to play the stream. Supported `#KODIPROP` and `#EXTVLCOPT`.'
placeholder: '#KODIPROP:inputstream=inputstream.adaptive'
- type: textarea
id: notes
attributes:

View File

@@ -18,13 +18,6 @@ body:
value: |
What exactly needs to be changed? To delete an existing value without replacement use the `~` symbol.
- type: input
id: new_stream_url
attributes:
label: New Stream URL
description: New link to the stream
placeholder: 'https://servilive.com:3126/live/tele2000live.m3u8'
- type: input
id: stream_id
attributes:
@@ -72,13 +65,6 @@ body:
label: HTTP Referrer
placeholder: 'https://example.com/'
- type: textarea
id: directives
attributes:
label: Directives
description: 'List of directives telling players how to play the stream. Supported `#KODIPROP` and `#EXTVLCOPT`.'
placeholder: '#KODIPROP:inputstream=inputstream.adaptive'
- type: textarea
id: notes
attributes:

View File

@@ -6,8 +6,6 @@ on:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
packages: read
jobs:
check:
runs-on: ubuntu-latest
@@ -18,7 +16,7 @@ jobs:
run: |
git fetch origin master:master
ANY_CHANGED=false
ALL_CHANGED_FILES=$(git diff --name-only master -- streams/ | tr '\n' ' ')
ALL_CHANGED_FILES=$(git diff --diff-filter=ACMRT --name-only master -- streams/ | tr '\n' ' ')
if [ -n "${ALL_CHANGED_FILES}" ]; then
ANY_CHANGED=true
fi
@@ -29,12 +27,6 @@ jobs:
with:
node-version: 22
cache: 'npm'
- name: Setup .npmrc for GitHub Packages
if: steps.files.outputs.any_changed == 'true'
run: |
echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> .npmrc
echo "@iptv-org:registry=https://npm.pkg.github.com/" >> .npmrc
echo "always-auth=true" >> .npmrc
- name: Install dependencies
if: steps.files.outputs.any_changed == 'true'
run: npm install

View File

@@ -3,8 +3,6 @@ on:
workflow_dispatch:
# schedule:
# - cron: "0 12 * * *"
permissions:
packages: read
jobs:
main:
runs-on: ubuntu-latest
@@ -24,11 +22,6 @@ jobs:
with:
node-version: 22
cache: 'npm'
- name: Setup .npmrc for GitHub Packages
run: |
echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> .npmrc
echo "@iptv-org:registry=https://npm.pkg.github.com/" >> .npmrc
echo "always-auth=true" >> .npmrc
- name: Install dependencies
run: npm install
- name: Format internal playlists
@@ -41,7 +34,7 @@ jobs:
id: files_after
run: |
ANY_CHANGED=false
ALL_CHANGED_FILES=$(git diff --name-only master -- streams/ | tr '\n' ' ')
ALL_CHANGED_FILES=$(git diff --diff-filter=ACMRT --name-only master -- streams/ | tr '\n' ' ')
if [ -n "${ALL_CHANGED_FILES}" ]; then
ANY_CHANGED=true
fi

View File

@@ -3,8 +3,6 @@ on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
permissions:
packages: read
jobs:
main:
runs-on: ubuntu-latest
@@ -24,11 +22,6 @@ jobs:
with:
node-version: 22
cache: 'npm'
- name: Setup .npmrc for GitHub Packages
run: |
echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> .npmrc
echo "@iptv-org:registry=https://npm.pkg.github.com/" >> .npmrc
echo "always-auth=true" >> .npmrc
- name: Install dependencies
run: npm install
- name: Update internal playlists
@@ -40,8 +33,8 @@ jobs:
npm run playlist:validate
- name: Generate public playlists
run: npm run playlist:generate
- name: Generate .api/streams.json
run: npm run api:generate
- name: Create .api/streams.json
run: npm run playlist:export
- name: Update readme
run: npm run readme:update
- name: Setup git

7
.prettierrc Normal file
View File

@@ -0,0 +1,7 @@
{
"singleQuote": true,
"semi": false,
"printWidth": 100,
"trailingComma": "none",
"arrowParens": "avoid"
}

View File

@@ -20,9 +20,10 @@ Regardless of which option you choose, before posting your request please do the
- Make sure the link you want to add works stably. To check this, open it in one of the players (for example, [VLC player](https://www.videolan.org/vlc/index.html)) and watch the broadcast for at least a minute (some test streams are interrupted after 15-30 seconds).
- Make sure the link is not already in the playlist. This can be done by [searching](https://github.com/search?q=repo%3Aiptv-org%2Fiptv+http%3A%2F%2Fexample.com&type=code) the repository.
- Make sure the link does not lead to the Xtream Codes server. [Why don't you accept links to Xtream Codes server?](FAQ.md#why-dont-you-accept-links-to-xtream-codes-server)
- Make sure that the link leads directly to the broadcast, without unnecessary redirects.
- Find the ID of the channel you want on [iptv-org.github.io](https://iptv-org.github.io/). If your desired channel is not on the list you can leave a request to add it [here](https://github.com/iptv-org/database/issues/new/choose).
- Make sure the channel is not blocklisted. It can also be done through [iptv-org.github.io](https://iptv-org.github.io/).
- The link does not lead to the Xtream Codes server. [Why don't you accept links to Xtream Codes server?](FAQ.md#why-dont-you-accept-links-to-xtream-codes-server)
- If you know that the broadcast only works in certain countries or it is periodically interrupted, do not forget to indicate this in the request.
A requests without a valid stream ID or working link to the stream will be closed immediately.
@@ -129,33 +130,15 @@ Example:
https://example.com/playlist.m3u8
```
Also, if necessary, you can specify custom [HTTP User-Agent](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent) and [HTTP Referrer](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer) through additional attributes:
Also, if necessary, you can specify custom [HTTP User-Agent](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent) and [HTTP Referrer](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer) through `#EXTVLCOPT` directive:
```xml
#EXTINF:-1 tvg-id="ExampleTV.us" http-referrer="http://example.com/" http-user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64)",Example TV
http://example.com/stream.m3u8
```
or use player-specific directives:
_VLC_
```xml
#EXTINF:-1 tvg-id="ExampleTV.us@VLC",Example TV
#EXTINF:-1 tvg-id="ExampleTV.us",Example TV
#EXTVLCOPT:http-referrer=http://example.com/
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64)
http://example.com/stream.m3u8
```
_Kodi_
```xml
#EXTINF:-1 tvg-id="ExampleTV.us@Kodi",Example TV
#KODIPROP:inputstream=inputstream.adaptive
#KODIPROP:inputstream.adaptive.stream_headers=Referer=http://example.com/&User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64)
http://example.com/stream.m3u8
```
## Project Structure
- `.github/`
@@ -185,8 +168,6 @@ To run scripts use the `npm run <script-name>` command.
- `act:format`: allows to test the [format](https://github.com/iptv-org/iptv/blob/master/.github/workflows/update.yml) workflow locally. Depends on [nektos/gh-act](https://github.com/nektos/gh-act).
- `act:update`: allows to test the [update](https://github.com/iptv-org/iptv/blob/master/.github/workflows/update.yml) workflow locally. Depends on [nektos/gh-act](https://github.com/nektos/gh-act).
- `api:load`: downloads the latest channel and stream data from the [iptv-org/api](https://github.com/iptv-org/api).
- `api:generate`: generates a JSON file with all streams for the [iptv-org/api](https://github.com/iptv-org/api) repository.
- `api:deploy`: allows to manually upload a JSON file created via `api:generate` to the [iptv-org/api](https://github.com/iptv-org/api) repository. To run the script you must provide your [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) with write access to the repository.
- `playlist:format`: formats internal playlists. The process includes [URL normalization](https://en.wikipedia.org/wiki/URI_normalization), duplicate removal, removing invalid id's and sorting links by channel name, quality, and label.
- `playlist:update`: triggers an update of internal playlists. The process involves processing approved requests from issues.
- `playlist:generate`: generates all public playlists.
@@ -194,13 +175,9 @@ To run scripts use the `npm run <script-name>` command.
- `playlist:lint`: сhecks internal playlists for syntax errors.
- `playlist:test`: tests links in internal playlists.
- `playlist:edit`: utility for quick streams mapping.
- `playlist:deploy`: allows to manually publish all generated via `playlist:generate` playlists. To run the script you must provide your [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) with write access to the repository.
- `playlist:export`: creates a JSON file with all streams for the [iptv-org/api](https://github.com/iptv-org/api) repository.
- `readme:update`: updates the list of playlists in [README.md](README.md).
- `report:create`: creates a report on current issues.
- `check`: (shorthand) sequentially runs the `playlist:lint` and `playlist:validate` scripts.
- `format`: (shorthand) runs the `playlist:format` script.
- `update`: (shorthand) sequentially runs the `playlist:generate`, `api:generate` and `readme:update` scripts.
- `deploy`: (shorthand) sequentially runs the `playlist:deploy` and `api:deploy` scripts.
- `lint`: сhecks the scripts for syntax errors.
- `test`: runs a test of all the scripts described above.
@@ -212,4 +189,4 @@ Each workflow includes its own set of scripts that can be run either manually or
- `check`: sequentially runs the `api:load`, `playlist:check` and `playlist:validate` scripts when a new pull request appears, and blocks the merge if it detects an error in it.
- `format`: sequentially runs `api:load`, `playlist:format`, `playlist:lint` and `playlist:validate` scripts.
- `update`: every day at 0:00 UTC sequentially runs `api:load`, `playlist:update`, `playlist:lint`, `playlist:validate`, `playlist:generate`, `api:generate` and `readme:update` scripts and deploys the output files if successful.
- `update`: every day at 0:00 UTC sequentially runs `api:load`, `playlist:update`, `playlist:lint`, `playlist:validate`, `playlist:generate`, `playlist:export` and `readme:update` scripts and deploys the output files if successful.

View File

@@ -50,7 +50,7 @@ Links to other useful IPTV-related resources can be found in the [iptv-org/aweso
## Discussions
If you need help finding a channel, have a question or idea, welcome to the [Discussions](https://github.com/orgs/iptv-org/discussions).
If you have a question or idea, welcome to the [Discussions](https://github.com/orgs/iptv-org/discussions).
## FAQ

87
package-lock.json generated
View File

@@ -15,7 +15,7 @@
"@freearhey/search-js": "^0.1.2",
"@freearhey/storage-js": "^0.1.0",
"@inquirer/prompts": "^7.8.0",
"@iptv-org/sdk": "^1.0.2",
"@iptv-org/sdk": "^1.1.3",
"@octokit/core": "^7.0.3",
"@octokit/plugin-paginate-rest": "^13.1.1",
"@octokit/plugin-rest-endpoint-methods": "^16.0.0",
@@ -1501,18 +1501,35 @@
}
},
"node_modules/@iptv-org/sdk": {
"version": "1.0.2",
"resolved": "https://npm.pkg.github.com/download/@iptv-org/sdk/1.0.2/131e5145ff68ffb5e213eb02d26d43db91a8a5a3",
"integrity": "sha512-Lpq+5vko9HkqOMDaXpeLBzKVgJikuKax7dJgBjr+XIgxRgMB1gucCjbvTNCR5AHhnORWvX+LFaodzy06jKukOA==",
"license": "UNLICENSED",
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@iptv-org/sdk/-/sdk-1.1.3.tgz",
"integrity": "sha512-e2IQWPVpNdMJnCkJulnBpiU2Hn5hpaSKNTxo4bvOI4uMRRXR7R8hWbm9jtJnf5LYQ6xdN0xT52EjI0zi9dh5yg==",
"dependencies": {
"@freearhey/core": "^0.14.3",
"@freearhey/core": "^0.15.1",
"@freearhey/search-js": "^0.2.0",
"@ntlab/sfetch": "^1.2.0",
"axios": "^1.11.0",
"dayjs": "^1.11.18"
}
},
"node_modules/@iptv-org/sdk/node_modules/@freearhey/core": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/@freearhey/core/-/core-0.15.1.tgz",
"integrity": "sha512-wYquasgTNGsLGDXPjHlPMvwfuoG9kgy9sEWsEc/i4Z2T2uKCfTboHYola9Ykh9X1lGlECypq8lL0IBAiOhKE/w==",
"dependencies": {
"@types/lodash": "^4.14.198",
"@types/pako": "^2.0.3",
"consola": "^3.4.2",
"dayjs": "^1.11.13",
"glob": "^11.0.1",
"lodash": "^4.17.21",
"natural-orderby": "^5.0.0",
"normalize-url": "^8.1.0",
"object-treeify": "^2.1.1",
"pako": "^2.1.0",
"timer-node": "^5.0.9"
}
},
"node_modules/@iptv-org/sdk/node_modules/@freearhey/search-js": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@freearhey/search-js/-/search-js-0.2.0.tgz",
@@ -1528,7 +1545,6 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
"license": "MIT",
"engines": {
"node": "20 || >=22"
}
@@ -1537,7 +1553,6 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
"license": "MIT",
"dependencies": {
"@isaacs/balanced-match": "^4.0.1"
},
@@ -1653,10 +1668,9 @@
}
},
"node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"license": "MIT",
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
@@ -2007,10 +2021,9 @@
}
},
"node_modules/@jest/reporters/node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"license": "ISC",
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
@@ -4829,14 +4842,13 @@
}
},
"node_modules/glob": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz",
"integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==",
"license": "ISC",
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz",
"integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==",
"dependencies": {
"foreground-child": "^3.3.1",
"jackspeak": "^4.1.1",
"minimatch": "^10.0.3",
"minimatch": "^10.1.1",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^2.0.0"
@@ -4863,10 +4875,9 @@
}
},
"node_modules/glob/node_modules/minimatch": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",
"integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
"license": "ISC",
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz",
"integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==",
"dependencies": {
"@isaacs/brace-expansion": "^5.0.0"
},
@@ -5467,10 +5478,9 @@
}
},
"node_modules/jest-config/node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"license": "ISC",
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
@@ -5936,10 +5946,9 @@
}
},
"node_modules/jest-runtime/node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"license": "ISC",
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
@@ -6218,9 +6227,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dependencies": {
"argparse": "^2.0.1"
},
@@ -7707,9 +7716,9 @@
}
},
"node_modules/validator": {
"version": "13.15.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.0.tgz",
"integrity": "sha512-36B2ryl4+oL5QxZ3AzD0t5SsMNGvTtQHpjgFO5tbNxfXbMFkY822ktCDe1MnlqV3301QQI9SLHDNJokDI+Z9pA==",
"version": "13.15.23",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.23.tgz",
"integrity": "sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==",
"engines": {
"node": ">= 0.10"
}

View File

@@ -5,8 +5,6 @@
"act:format": "gh act workflow_dispatch -W .github/workflows/format.yml",
"act:update": "gh act workflow_dispatch -W .github/workflows/update.yml",
"api:load": "tsx scripts/commands/api/load.ts",
"api:generate": "tsx scripts/commands/api/generate.ts",
"api:deploy": "npx gh-pages-clean && npx gh-pages -a -m \"Deploy to iptv-org/api\" -d .api -r https://$GITHUB_TOKEN@github.com/iptv-org/api.git",
"playlist:format": "tsx scripts/commands/playlist/format.ts",
"playlist:update": "tsx scripts/commands/playlist/update.ts",
"playlist:generate": "tsx scripts/commands/playlist/generate.ts",
@@ -14,13 +12,9 @@
"playlist:lint": "npx m3u-linter -c m3u-linter.json",
"playlist:test": "tsx scripts/commands/playlist/test.ts",
"playlist:edit": "tsx scripts/commands/playlist/edit.ts",
"playlist:deploy": "npx gh-pages-clean && npx gh-pages -m \"Deploy to GitHub Pages\" -d .gh-pages -r https://$GITHUB_TOKEN@github.com/iptv-org/iptv.git",
"playlist:export": "tsx scripts/commands/playlist/export.ts",
"readme:update": "tsx scripts/commands/readme/update.ts",
"report:create": "tsx scripts/commands/report/create.ts",
"check": "npm run playlist:lint && npm run playlist:validate",
"format": "npm run playlist:format",
"update": "npm run playlist:generate && npm run api:generate && npm run readme:update",
"deploy": "npm run playlist:deploy && npm run api:deploy",
"lint": "npx eslint \"scripts/**/*.{ts,js}\" \"tests/**/*.{ts,js}\"",
"test": "jest --runInBand",
"postinstall": "npm run api:load"
@@ -45,7 +39,7 @@
"@freearhey/search-js": "^0.1.2",
"@freearhey/storage-js": "^0.1.0",
"@inquirer/prompts": "^7.8.0",
"@iptv-org/sdk": "^1.0.2",
"@iptv-org/sdk": "^1.1.3",
"@octokit/core": "^7.0.3",
"@octokit/plugin-paginate-rest": "^13.1.1",
"@octokit/plugin-rest-endpoint-methods": "^16.0.0",

View File

@@ -1,5 +1,5 @@
import { PlaylistParser, StreamTester, CliTable } from '../../core'
import type { TestResult } from '../../core/streamTester'
import type { StreamTesterResult } from '../../core/streamTester'
import { ROOT_DIR, STREAMS_DIR } from '../../constants'
import { Logger, Collection } from '@freearhey/core'
import { program, OptionValues } from 'commander'
@@ -92,10 +92,10 @@ async function runTest(stream: Stream) {
const key = stream.getUniqKey()
results[key] = chalk.white('LOADING...')
const result: TestResult = await tester.test(stream)
const result: StreamTesterResult = await tester.test(stream)
let status = ''
const errorStatusCodes = ['ENOTFOUND', 'HTTP_404_NOT_FOUND']
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 +144,7 @@ function drawTable() {
}
}
function onFinish(error: Error) {
function onFinish(error: Error | null | undefined) {
clearInterval(interval)
if (error) {

View File

@@ -71,9 +71,9 @@ async function removeStreams({
requests.forEach((issue: Issue) => {
const data = issue.data
if (data.missing('streamUrl')) return
if (data.missing('stream_url')) return
const streamUrls = data.getString('streamUrl') || ''
const streamUrls = data.getString('stream_url') || ''
let changed = false
streamUrls
@@ -104,14 +104,14 @@ async function editStreams({
requests.forEach((issue: Issue) => {
const data = issue.data
if (data.missing('streamUrl')) return
if (data.missing('stream_url')) return
const stream: Stream = streams.first(
(_stream: Stream) => _stream.url === data.getString('streamUrl')
(_stream: Stream) => _stream.url === data.getString('stream_url')
)
if (!stream) return
const streamId = data.getString('streamId') || ''
const streamId = data.getString('stream_id') || ''
const [channelId, feedId] = streamId.split('@')
if (channelId) {
@@ -138,12 +138,12 @@ async function addStreams({
)
requests.forEach((issue: Issue) => {
const data = issue.data
if (data.missing('streamId') || data.missing('streamUrl')) return
if (streams.includes((_stream: Stream) => _stream.url === data.getString('streamUrl'))) return
const streamUrl = data.getString('streamUrl') || ''
if (data.missing('stream_id') || data.missing('stream_url')) return
if (streams.includes((_stream: Stream) => _stream.url === data.getString('stream_url'))) return
const streamUrl = data.getString('stream_url') || ''
if (!isURI(streamUrl)) return
const streamId = data.getString('streamId') || ''
const streamId = data.getString('stream_id') || ''
const [channelId, feedId] = streamId.split('@')
const channel: sdk.Models.Channel | undefined = apiData.channelsKeyById.get(channelId)
@@ -151,9 +151,8 @@ async function addStreams({
const label = data.getString('label') || ''
const quality = data.getString('quality') || null
const httpUserAgent = data.getString('httpUserAgent') || null
const httpReferrer = data.getString('httpReferrer') || null
const directives = data.getArray('directives') || []
const httpUserAgent = data.getString('http_user_agent') || null
const httpReferrer = data.getString('http_referrer') || null
const stream = new Stream({
channel: channelId,
@@ -166,7 +165,7 @@ async function addStreams({
})
stream.label = label
stream.setDirectives(directives).updateTitle().updateFilepath()
stream.updateTitle().updateFilepath()
streams.add(stream)
processedIssues.add(issue.number)

View File

@@ -31,8 +31,8 @@ async function main() {
const streams = await parser.parse(files)
logger.info(`found ${streams.count()} streams`)
let errors = new Collection()
let warnings = new Collection()
const errors = new Collection()
const warnings = new Collection()
const streamsGroupedByFilepath = streams.groupBy((stream: Stream) => stream.getFilepath())
for (const filepath of streamsGroupedByFilepath.keys()) {
const streams = streamsGroupedByFilepath.get(filepath)
@@ -97,8 +97,10 @@ async function main() {
console.log(` ${chalk.gray(position)}${status}${logItem.message}`)
})
errors = errors.concat(log.filter((logItem: LogItem) => logItem.type === 'error'))
warnings = warnings.concat(log.filter((logItem: LogItem) => logItem.type === 'warning'))
log.forEach((logItem: LogItem) => {
if (logItem.type === 'error') errors.add(logItem)
else if (logItem.type === 'warning') warnings.add(logItem)
})
}
}

View File

@@ -47,7 +47,7 @@ async function main() {
issue.labels.find((label: string) => label === 'streams:remove')
)
removeRequests.forEach((issue: Issue) => {
const streamUrls = issue.data.getArray('streamUrl') || []
const streamUrls = issue.data.getArray('stream_url') || []
if (!streamUrls.length) {
const result = {
@@ -82,8 +82,8 @@ async function main() {
const addRequests = issues.filter(issue => issue.labels.includes('streams:add'))
const addRequestsBuffer = new Dictionary()
addRequests.forEach((issue: Issue) => {
const streamId = issue.data.getString('streamId') || ''
const streamUrl = issue.data.getString('streamUrl') || ''
const streamId = issue.data.getString('stream_id') || ''
const streamUrl = issue.data.getString('stream_url') || ''
const [channelId] = streamId.split('@')
const result = {
@@ -114,8 +114,8 @@ async function main() {
issue.labels.find((label: string) => label === 'streams:edit')
)
editRequests.forEach((issue: Issue) => {
const streamId = issue.data.getString('streamId') || ''
const streamUrl = issue.data.getString('streamUrl') || ''
const streamId = issue.data.getString('stream_id') || ''
const streamUrl = issue.data.getString('stream_url') || ''
const [channelId] = streamId.split('@')
const result = {
@@ -140,7 +140,7 @@ async function main() {
)
const channelSearchRequestsBuffer = new Dictionary()
channelSearchRequests.forEach((issue: Issue) => {
const streamId = issue.data.getString('streamId') || issue.data.getString('channelId') || ''
const streamId = issue.data.getString('stream_id') || issue.data.getString('channel_id') || ''
const [channelId, feedId] = streamId.split('@')
const result = {

View File

@@ -3,20 +3,18 @@ import { IssueData } from './issueData'
import { Issue } from '../models'
const FIELDS = new Dictionary({
'Stream ID': 'streamId',
'Channel ID': 'channelId',
'Feed ID': 'feedId',
'Stream URL': 'streamUrl',
'New Stream URL': 'newStreamUrl',
'Stream ID': 'stream_id',
'Channel ID': 'channel_id',
'Feed ID': 'feed_id',
'Stream URL': 'stream_url',
Label: 'label',
Quality: 'quality',
'HTTP User-Agent': 'httpUserAgent',
'HTTP User Agent': 'httpUserAgent',
'HTTP Referrer': 'httpReferrer',
'HTTP User-Agent': 'http_user_agent',
'HTTP User Agent': 'http_user_agent',
'HTTP Referrer': 'http_referrer',
'What happened to the stream?': 'reason',
Reason: 'reason',
Notes: 'notes',
Directives: 'directives'
Notes: 'notes'
})
export class IssueParser {

View File

@@ -20,7 +20,9 @@ export class PlaylistParser {
for (const filepath of files) {
if (!this.storage.existsSync(filepath)) continue
const _parsed: Collection<Stream> = await this.parseFile(filepath)
parsed.concat(_parsed)
_parsed.forEach((item: Stream) => {
parsed.add(item)
})
}
return parsed

View File

@@ -25,7 +25,9 @@ export class LanguagesGenerator implements Generator {
const languages = new Collection<sdk.Models.Language>()
streams.forEach((stream: Stream) => {
languages.concat(stream.getLanguages())
stream.getLanguages().forEach((language: sdk.Models.Language) => {
languages.add(language)
})
})
languages

View File

@@ -7,7 +7,6 @@ import { data } from '../api'
import path from 'node:path'
export class Stream extends sdk.Models.Stream {
directives: Collection<string>
filepath?: string
line?: number
groupTitle: string = 'Undefined'
@@ -19,18 +18,14 @@ export class Stream extends sdk.Models.Stream {
const data = {
label: issueData.getString('label'),
quality: issueData.getString('quality'),
httpUserAgent: issueData.getString('httpUserAgent'),
httpReferrer: issueData.getString('httpReferrer'),
newStreamUrl: issueData.getString('newStreamUrl'),
directives: issueData.getArray('directives')
httpUserAgent: issueData.getString('http_user_agent'),
httpReferrer: issueData.getString('http_referrer')
}
if (data.label !== undefined) this.label = data.label
if (data.quality !== undefined) this.quality = data.quality
if (data.httpUserAgent !== undefined) this.user_agent = data.httpUserAgent
if (data.httpReferrer !== undefined) this.referrer = data.httpReferrer
if (data.newStreamUrl !== undefined) this.url = data.newStreamUrl
if (data.directives !== undefined) this.setDirectives(data.directives)
return this
}
@@ -54,24 +49,6 @@ export class Stream extends sdk.Models.Stream {
return { title, label, quality }
}
function parseDirectives(string: string): Collection<string> {
const directives = new Collection<string>()
if (!string) return directives
const supportedDirectives = ['#EXTVLCOPT', '#KODIPROP']
const lines = string.split('\r\n')
const regex = new RegExp(`^${supportedDirectives.join('|')}`, 'i')
lines.forEach((line: string) => {
if (regex.test(line)) {
directives.add(line.trim())
}
})
return directives
}
if (!data.name) throw new Error('"name" property is required')
if (!data.url) throw new Error('"url" property is required')
@@ -91,7 +68,6 @@ export class Stream extends sdk.Models.Stream {
stream.tvgId = data.tvg.id
stream.line = data.line
stream.label = label || null
stream.directives = parseDirectives(data.raw)
return stream
}
@@ -235,7 +211,9 @@ export class Stream extends sdk.Models.Stream {
.intersects(new Collection<string>(region.countries))
.isNotEmpty()
)
regions.concat(relatedRegions)
relatedRegions.forEach(region => {
regions.add(region)
})
break
}
case 'country': {
@@ -246,7 +224,9 @@ export class Stream extends sdk.Models.Stream {
(code: string) => code === country.code
)
)
regions.concat(countryRegions)
countryRegions.forEach(region => {
regions.add(region)
})
break
}
case 'subdivision': {
@@ -257,7 +237,9 @@ export class Stream extends sdk.Models.Stream {
(code: string) => code === subdivision.country
)
)
regions.concat(subdivisionRegions)
subdivisionRegions.forEach(region => {
regions.add(region)
})
break
}
case 'city': {
@@ -268,7 +250,9 @@ export class Stream extends sdk.Models.Stream {
(code: string) => code === city.country
)
)
regions.concat(cityRegions)
cityRegions.forEach(region => {
regions.add(region)
})
break
}
}
@@ -306,14 +290,6 @@ export class Stream extends sdk.Models.Stream {
return !!found
}
setDirectives(directives: string[]): this {
this.directives = new Collection(directives).filter((directive: string) =>
/^(#KODIPROP|#EXTVLCOPT)/.test(directive)
)
return this
}
updateTvgId(): this {
if (!this.channel) return this
if (this.feed) {
@@ -418,20 +394,16 @@ export class Stream extends sdk.Models.Stream {
output += ` tvg-logo="${this.getTvgLogo()}" group-title="${this.groupTitle}"`
}
output += `,${this.getFullTitle()}`
if (this.referrer) {
output += ` http-referrer="${this.referrer}"`
output += `\r\n#EXTVLCOPT:http-referrer=${this.referrer}`
}
if (this.user_agent) {
output += ` http-user-agent="${this.user_agent}"`
output += `\r\n#EXTVLCOPT:http-user-agent=${this.user_agent}`
}
output += `,${this.getFullTitle()}`
this.directives.forEach((prop: string) => {
output += `\r\n${prop}`
})
output += `\r\n${this.url}`
return output

View File

@@ -1,3 +1,5 @@
#EXTM3U
#EXTINF:-1 tvg-id="" http-referrer="http://test.com" http-user-agent="Mozilla/5.0",Manorama News -2 [U3] (480p) [Geo-blocked] [Not 24/7]
#EXTINF:-1 tvg-id="",Manorama News -2 [U3] (480p) [Geo-blocked] [Not 24/7]
#EXTVLCOPT:http-referrer=http://test.com
#EXTVLCOPT:http-user-agent=Mozilla/5.0
https://ythls.onrender.com/channel/UCP0uG-mcMImgKnJz-VjJZmQ.m3u8

View File

@@ -3,13 +3,9 @@
http://resolver.streaming.api.nos.nl/livestream?url=/live/npo/tvlive/npo1/npo1.isml/.m3u8
#EXTINF:-1 tvg-id="NPO2.nl@SD",NPO 2 (342p)
http://resolver.streaming.api.nos.nl/livestream?url=/live/npo/tvlive/npo2/npo2.isml/.m3u8
#EXTINF:-1 tvg-id="NPO2.nl@SD" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",NPO 2 (302p) [Geo-blocked]
#EXTINF:-1 tvg-id="NPO2.nl@SD",NPO 2 (302p) [Geo-blocked]
#EXTVLCOPT:http-referrer=http://imn.iq
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
#KODIPROP:inputstream=inputstream.adaptive
#KODIPROP:inputstream.adaptive.manifest_type=mpd
#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha
#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}|
http://stream.tvtap.net:8081/live/nl-npo2.stream/playlist.m3u8?|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0+(iPhone;+CPU+iPhone+OS+17_7+like+Mac+OS+X)+AppleWebKit/605.1.15+(KHTML,+like+Gecko)+Version/18.0+Mobile/15E148+Safari/604.1"|Origin="https://origin.xyz"
#EXTINF:-1 tvg-id="NPO2.nl@SD",NPO 2 [Geo-blocked]
http://resolver.streaming.api.nos.nl/livestream?url=/live/npo/tvlive/npo2/npo22.isml/.m3u8

View File

@@ -1,13 +1,9 @@
#EXTM3U
#EXTINF:-1 tvg-id="5AABTV.ca" tvg-logo="" group-title="Undefined",5AAB TV
http://158.69.124.9:1935/5aabtv/5aabtv/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7]
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Andorra TV (720p) [Not 24/7]
#EXTVLCOPT:http-referrer=http://imn.iq
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
#KODIPROP:inputstream=inputstream.adaptive
#KODIPROP:inputstream.adaptive.manifest_type=mpd
#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha
#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}|
http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz"
#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="https://i.imgur.com/BnhTn8i.png" group-title="Undefined",ATV
https://iptv-all.lanesh4d0w.repl.co/andorra/atv

View File

@@ -1,11 +1,7 @@
#EXTM3U
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7]
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Andorra TV (720p) [Not 24/7]
#EXTVLCOPT:http-referrer=http://imn.iq
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
#KODIPROP:inputstream=inputstream.adaptive
#KODIPROP:inputstream.adaptive.manifest_type=mpd
#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha
#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}|
http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz"
#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="https://i.imgur.com/CnhTn8i.png" group-title="Undefined",ATV HD
https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd

View File

@@ -11,13 +11,9 @@ http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index.m3u8
http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8
#EXTINF:-1 tvg-id="5AABTV.ca" tvg-logo="" group-title="Undefined",5AAB TV
http://158.69.124.9:1935/5aabtv/5aabtv/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7]
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Andorra TV (720p) [Not 24/7]
#EXTVLCOPT:http-referrer=http://imn.iq
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
#KODIPROP:inputstream=inputstream.adaptive
#KODIPROP:inputstream.adaptive.manifest_type=mpd
#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha
#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}|
http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz"
#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="https://i.imgur.com/BnhTn8i.png" group-title="Undefined",ATV
https://iptv-all.lanesh4d0w.repl.co/andorra/atv

View File

@@ -15,13 +15,9 @@ http://146.59.85.40:89/dunaworld/index.m3u8
http://46.46.143.222:1935/live/mp4:ldpr.stream/blocked.m3u8
#EXTINF:-1 tvg-id="ElTR.kg" tvg-logo="https://i.ibb.co/r6czQwQ/365049798-774721644658455-5702658175909463406-n-2.png" group-title="International",ЭлТР (480p) [Not 24/7]
http://gohoski.fvds.ru:3000/mediabay/162/index.m3u8
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7]
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Andorra TV (720p) [Not 24/7]
#EXTVLCOPT:http-referrer=http://imn.iq
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
#KODIPROP:inputstream=inputstream.adaptive
#KODIPROP:inputstream.adaptive.manifest_type=mpd
#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha
#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}|
http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz"
#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="https://i.imgur.com/CnhTn8i.png" group-title="Undefined",ATV HD
https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd

View File

@@ -7,13 +7,9 @@ http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index.m3u8
http://46.46.143.222:1935/live/mp4:ldpr.stream/blocked.m3u8
#EXTINF:-1 tvg-id="5AABTV.ca" tvg-logo="" group-title="Undefined",5AAB TV
http://158.69.124.9:1935/5aabtv/5aabtv/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7]
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Andorra TV (720p) [Not 24/7]
#EXTVLCOPT:http-referrer=http://imn.iq
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
#KODIPROP:inputstream=inputstream.adaptive
#KODIPROP:inputstream.adaptive.manifest_type=mpd
#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha
#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}|
http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz"
#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="https://i.imgur.com/CnhTn8i.png" group-title="Undefined",ATV HD
https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd

View File

@@ -1,13 +1,9 @@
#EXTM3U
#EXTINF:-1 tvg-id="5AABTV.ca" tvg-logo="" group-title="Undefined",5AAB TV
http://158.69.124.9:1935/5aabtv/5aabtv/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7]
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Andorra TV (720p) [Not 24/7]
#EXTVLCOPT:http-referrer=http://imn.iq
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
#KODIPROP:inputstream=inputstream.adaptive
#KODIPROP:inputstream.adaptive.manifest_type=mpd
#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha
#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}|
http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz"
#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="https://i.imgur.com/BnhTn8i.png" group-title="Undefined",ATV
https://iptv-all.lanesh4d0w.repl.co/andorra/atv

View File

@@ -1,13 +1,9 @@
#EXTM3U
#EXTINF:-1 tvg-id="5AABTV.ca" tvg-logo="" group-title="Undefined",5AAB TV
http://158.69.124.9:1935/5aabtv/5aabtv/playlist.m3u8
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7]
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Andorra TV (720p) [Not 24/7]
#EXTVLCOPT:http-referrer=http://imn.iq
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
#KODIPROP:inputstream=inputstream.adaptive
#KODIPROP:inputstream.adaptive.manifest_type=mpd
#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha
#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}|
http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz"
#EXTINF:-1 tvg-id="AndorraTV.ad@HD" tvg-logo="https://i.imgur.com/CnhTn8i.png" group-title="Undefined",ATV HD
https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd

View File

@@ -3,13 +3,9 @@
http://46.46.143.222:1935/live/mp4:ldpr.stream/blocked.m3u8
#EXTINF:-1 tvg-id="VisitXTV.nl" tvg-logo="https://i.imgur.com/RJ9wbNF.jpg" group-title="XXX",Visit-X TV
https://stream.visit-x.tv/vxtv/ngrp:live_all/30fps.m3u8
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7]
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Andorra TV (720p) [Not 24/7]
#EXTVLCOPT:http-referrer=http://imn.iq
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
#KODIPROP:inputstream=inputstream.adaptive
#KODIPROP:inputstream.adaptive.manifest_type=mpd
#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha
#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}|
http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz"
#EXTINF:-1 tvg-id="DunaWorld.hu" tvg-logo="https://i.imgur.com/uOBQJZS.png" group-title="Undefined",Duna World (576i)
http://146.59.85.40:89/dunaworld/index.m3u8

View File

@@ -1,11 +1,7 @@
#EXTM3U
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined" http-referrer="http://imn.iq" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",Andorra TV (720p) [Not 24/7]
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Andorra TV (720p) [Not 24/7]
#EXTVLCOPT:http-referrer=http://imn.iq
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
#KODIPROP:inputstream=inputstream.adaptive
#KODIPROP:inputstream.adaptive.manifest_type=mpd
#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha
#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}|
http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz"
#EXTINF:-1 tvg-id="DunaWorld.hu" tvg-logo="https://i.imgur.com/uOBQJZS.png" group-title="Undefined",Duna World (576i)
http://146.59.85.40:89/dunaworld/index.m3u8

View File

@@ -1,3 +1,5 @@
#EXTM3U
#EXTINF:-1 tvg-id="TFX.fr@SD" http-referrer="https://pkpakiplay.xyz/" http-user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",TFX
#EXTINF:-1 tvg-id="TFX.fr@SD",TFX
#EXTVLCOPT:http-referrer=https://pkpakiplay.xyz/
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1
https://stitcher-ipv4.pluto.tv/v1/stitch/embed/hls/channel/64c109a4798def0008a6e03e/master.m3u8?advertisingId={PSID}&appVersion=unknown&deviceDNT={TARGETOPT}&deviceId={PSID}&deviceLat=0&deviceLon=0&deviceMake=samsung&deviceModel=samsung&deviceType=samsung-tvplus&deviceVersion=unknown&embedPartner=samsung-tvplus&profileFloor=&profileLimit=&samsung_app_domain={APP_DOMAIN}&samsung_app_name={APP_NAME}&us_privacy=1YNY

View File

@@ -4,10 +4,4 @@ http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index.m3u8
#EXTINF:-1 tvg-id="BBCNews.uk",BBC News HD (480p) [Geo-blocked]
http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/playlist.m3u8
#EXTINF:-1 tvg-id="BeanoTV.uk@SD",Beano TV
#EXTVLCOPT:http-referrer=http://imn.iq
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
#KODIPROP:inputstream=inputstream.adaptive
#KODIPROP:inputstream.adaptive.manifest_type=mpd
#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha
#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}|
https://a5b4bacecd47433dad06d3189fc7422e.mediatailor.us-east-1.amazonaws.com/v1/manifest/04fd913bb278d8775298c26fdca9d9841f37601f/RakutenTV-eu_BeanoTV/b1f233d5-847c-437d-aa4f-f73e67a85323/2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz"

View File

@@ -1,11 +1,6 @@
#EXTM3U
#EXTINF:-1 tvg-id="BBCAmerica.us@East" http-user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 Edge/12.246",BBC America East (720p)
#EXTVLCOPT:http-referrer=http://imn.iq
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
#KODIPROP:inputstream=inputstream.adaptive
#KODIPROP:inputstream.adaptive.manifest_type=mpd
#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha
#KODIPROP:inputstream.adaptive.license_key=https://drm.ors.at/acquire-license/widevine?BrandGuid=13f2e056-53fe-4469-ba6d-999970dbe549&userToken=v9ZVSksv4S7rT55o10dmYNRa4asye3z05eWCFxD%2FFYIlTJEpuf6tF8asPcyQOFq0h5opS%2B6WoMxnshWkihpHq5qrdrBEZ69piE94J9Feh385snGOqK3PYO7tLLjxmsCAe%2B9%2BNnurSSO5RCAIRsL125nSj1eOR%2F1GSKOgGH80HK2FDLiePxPkeaAxuWzacNBB%2FqnIGGxfe3GlmN65cU9F8WEpKFDlaxW%2Fv3ZSLAp3%2BZEq1aZXJ6Oz%2Fi0diD0EybH7|Content-Type=application/octet-stream|R{SSM}|
https://xui-backend.energeek.cl/live/9/playlist.m3u8?username=ZZDemoIPTVGH&password=mdo96EuqMkTR|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz"
#EXTINF:-1 tvg-id="BBCAmerica.us@East",BBC America East (720p)
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 Edge/12.246
https://servilive.com:3126/live/tele2000live.m3u8
#EXTINF:-1 tvg-id="FastTV.us@SD",Fast TV
https://3fa797d5.wurl.com/manifest/f36d25e7e52f1ba8d7e56eb859c636563214f541/T05PX01vdG9yVHJlbmRGYXN0VFZfSExT/b5e5e0e2-12b3-4312-93c9-c0a7c50b41ca/4.m3u8

View File

@@ -3,20 +3,20 @@ import { execSync } from 'child_process'
import fs from 'fs-extra'
const ENV_VAR =
'cross-env DATA_DIR=tests/__data__/input/data STREAMS_DIR=tests/__data__/input/api_generate API_DIR=tests/__data__/output/.api'
'cross-env DATA_DIR=tests/__data__/input/data STREAMS_DIR=tests/__data__/input/playlist_export API_DIR=tests/__data__/output/.api'
beforeEach(() => {
fs.emptyDirSync('tests/__data__/output')
})
describe('api:generate', () => {
describe('playlist:export', () => {
it('can create streams.json', () => {
const cmd = `${ENV_VAR} npm run api:generate`
const cmd = `${ENV_VAR} npm run playlist:export`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/.api/streams.json')).toMatchObject(
content('tests/__data__/expected/api_generate/.api/streams.json')
content('tests/__data__/expected/playlist_export/.api/streams.json')
)
})
})