Merge pull request #25158 from iptv-org/patch-2025.07.2

Patch 2025.07.2
This commit is contained in:
Alstruit 2025-07-29 14:29:08 -05:00 committed by GitHub
commit e8cf7f2482
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
97 changed files with 8809 additions and 8341 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text eol=crlf

1
.readme/.gitignore vendored
View File

@ -2,3 +2,4 @@ _categories.md
_countries.md _countries.md
_languages.md _languages.md
_regions.md _regions.md
_subdivisions.md

View File

@ -1,31 +0,0 @@
# Supported Categories
| Category | Definition |
| ------------- | --------------------------------------------------------------------- |
| Auto | Programming related to cars, motorcycles, and other automobiles |
| Animation | Programming is mostly 2D or 3D animation |
| Business | Programming related to business |
| Classic | Programming is mostly from earlier decades |
| Comedy | Programming is mostly comedy |
| Cooking | Programs related to cooking or food in general |
| Culture | Programming is mostly about art and culture |
| Documentary | Programming that depicts a person or real-world event |
| Education | Programming is intended to be educational |
| Entertainment | Channels with a variety of entertainment programs |
| Family | Programming that is be suitable for all members of a family |
| General | Provides a variety of different programming |
| Kids | Programming targeted to children |
| Legislative | Programming specific to the operation of government |
| Lifestyle | Programs related to health, fitness, leisure, fashion, decor, etc. |
| Movies | Channels that only show movies |
| Music | Programming is music or music related |
| News | Programming is mostly news |
| Outdoor | Programming related to outdoor activities like fishing, hunting, etc. |
| Relax | Programming is calm sounding and beautiful views |
| Religious | Religious programming |
| Science | Science and Technology |
| Series | Channels that only show series |
| Shop | Programming is for shopping |
| Sports | Programming is sports |
| Travel | Programming is travel related |
| Weather | Programming is focused on weather |

View File

@ -36,7 +36,7 @@ https://iptv-org.github.io/iptv/index.m3u
### Grouped by category ### Grouped by category
Playlists in which channels are grouped by category. A list of all supported categories with descriptions can be found [here](.readme/supported-categories.md). Playlists in which channels are grouped by category.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
@ -91,6 +91,19 @@ Same thing, but split up into separate files:
</details> </details>
### Grouped by subdivision
Playlists in which channels are grouped by subdivision for which they are broadcasted.
<details>
<summary>Expand</summary>
<br>
<!-- prettier-ignore -->
#include "./.readme/_subdivisions.md"
</details>
### Grouped by region ### Grouped by region
Playlists in which channels are grouped by the region for which they are broadcasted. Playlists in which channels are grouped by the region for which they are broadcasted.

View File

@ -31,7 +31,7 @@ Note all links in playlists are sorted automatically by scripts so there is no n
### How to fix the stream description? ### How to fix the stream description?
Most of the stream description (channel name, categories, languages, broadcast area, logo) we load from the [iptv-org/database](https://github.com/iptv-org/database) using the stream ID. Most of the stream description (channel name, feed name, categories, languages, broadcast area, logo) we load from the [iptv-org/database](https://github.com/iptv-org/database) using the stream ID.
So first of all, make sure that the desired stream has the correct ID. A full list of all supported channels and their corresponding IDs can be found on [iptv-org.github.io](https://iptv-org.github.io/). To change the stream ID of any link in the playlist, just fill out this [form](https://github.com/iptv-org/iptv/issues/new?assignees=&labels=streams%3Aedit&projects=&template=2_streams_edit.yml&title=Edit%3A+). So first of all, make sure that the desired stream has the correct ID. A full list of all supported channels and their corresponding IDs can be found on [iptv-org.github.io](https://iptv-org.github.io/). To change the stream ID of any link in the playlist, just fill out this [form](https://github.com/iptv-org/iptv/issues/new?assignees=&labels=streams%3Aedit&projects=&template=2_streams_edit.yml&title=Edit%3A+).
@ -110,14 +110,14 @@ Please note that we only accept removal requests from channel owners and their o
For a stream to be approved, its description must follow this template: For a stream to be approved, its description must follow this template:
``` ```
#EXTINF:-1 tvg-id="STREAM_ID",CHANNEL_NAME (QUALITY) [LABEL] #EXTINF:-1 tvg-id="STREAM_ID",STREAM_TITLE (QUALITY) [LABEL]
STREAM_URL STREAM_URL
``` ```
| Attribute | Description | Required | Valid values | | Attribute | Description | Required | Valid values |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------- | | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------- |
| `STREAM_ID` | Stream ID consisting of channel ID and feed ID. Full list of supported channels with corresponding ID could be found on [iptv-org.github.io](https://iptv-org.github.io/). | Optional | `<channel_id>` or `<channel_id>@<feed_id>` | | `STREAM_ID` | Stream ID consisting of channel ID and feed ID. Full list of supported channels with corresponding ID could be found on [iptv-org.github.io](https://iptv-org.github.io/). | Optional | `<channel_id>` or `<channel_id>@<feed_id>` |
| `CHANNEL_NAME` | Full name of the channel. May contain any characters except: `,`, `[`, `]`. | Required | - | | `STREAM_TITLE` | Stream title consisting of channel name and feed name. May contain any characters except: `,`, `[`, `]`. | Required | - |
| `QUALITY` | Maximum stream quality. | Optional | `2160p`, `1080p`, `720p`, `480p`, `360p` etc | | `QUALITY` | Maximum stream quality. | Optional | `2160p`, `1080p`, `720p`, `480p`, `360p` etc |
| `LABEL` | Specified in cases where the broadcast for some reason may not be available to some users. | Optional | `Geo-blocked` or `Not 24/7` | | `LABEL` | Specified in cases where the broadcast for some reason may not be available to some users. | Optional | `Geo-blocked` or `Not 24/7` |
| `STREAM_URL` | Stream URL. | Required | - | | `STREAM_URL` | Stream URL. | Required | - |
@ -125,7 +125,7 @@ STREAM_URL
Example: Example:
```xml ```xml
#EXTINF:-1 tvg-id="ExampleTV.ua@HD",Example TV (720p) [Not 24/7] #EXTINF:-1 tvg-id="ExampleTV.us@East",Example TV East (720p) [Not 24/7]
https://example.com/playlist.m3u8 https://example.com/playlist.m3u8
``` ```
@ -165,7 +165,6 @@ http://example.com/stream.m3u8
- `.readme/` - `.readme/`
- `config.json`: config for the `markdown-include` package, which is used to compile everything into one `README.md` file. - `config.json`: config for the `markdown-include` package, which is used to compile everything into one `README.md` file.
- `preview.png`: image displayed in the `README.md`. - `preview.png`: image displayed in the `README.md`.
- `supported-categories.md`: list of supported categories.
- `template.md`: template for `README.md`. - `template.md`: template for `README.md`.
- `scripts/`: contains all scripts used in the repository. - `scripts/`: contains all scripts used in the repository.
- `streams/`: contains all streams broken down by the country from which they are broadcast. - `streams/`: contains all streams broken down by the country from which they are broadcast.

View File

@ -1,46 +1,54 @@
import typescriptEslint from "@typescript-eslint/eslint-plugin"; import typescriptEslint from '@typescript-eslint/eslint-plugin'
import globals from "globals"; import globals from 'globals'
import tsParser from "@typescript-eslint/parser"; import tsParser from '@typescript-eslint/parser'
import path from "node:path"; import path from 'node:path'
import { fileURLToPath } from "node:url"; import { fileURLToPath } from 'node:url'
import js from "@eslint/js"; import js from '@eslint/js'
import { FlatCompat } from "@eslint/eslintrc"; import { FlatCompat } from '@eslint/eslintrc'
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename)
const compat = new FlatCompat({ const compat = new FlatCompat({
baseDirectory: __dirname, baseDirectory: __dirname,
recommendedConfig: js.configs.recommended, recommendedConfig: js.configs.recommended,
allConfig: js.configs.all allConfig: js.configs.all
}); })
export default [ export default [
...compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"), ...compat.extends('eslint:recommended', 'plugin:@typescript-eslint/recommended'),
{ {
plugins: { plugins: {
"@typescript-eslint": typescriptEslint, '@typescript-eslint': typescriptEslint
},
languageOptions: {
globals: {
...globals.browser,
},
parser: tsParser,
ecmaVersion: "latest",
sourceType: "module",
},
rules: {
"no-case-declarations": "off",
indent: ["error", 2, {
SwitchCase: 1,
}],
"linebreak-style": ["error", "unix"],
quotes: ["error", "single"],
semi: ["error", "never"],
},
}, },
];
languageOptions: {
globals: {
...globals.browser
},
parser: tsParser,
ecmaVersion: 'latest',
sourceType: 'module'
},
rules: {
'no-case-declarations': 'off',
indent: [
'error',
2,
{
SwitchCase: 1
}
],
'linebreak-style': ['error', 'windows'],
quotes: ['error', 'single'],
semi: ['error', 'never']
}
},
{
ignores: ['tests/__data__/**']
}
]

98
package-lock.json generated
View File

@ -928,9 +928,9 @@
} }
}, },
"node_modules/@eslint/config-array": { "node_modules/@eslint/config-array": {
"version": "0.20.0", "version": "0.21.0",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
"integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
"dependencies": { "dependencies": {
"@eslint/object-schema": "^2.1.6", "@eslint/object-schema": "^2.1.6",
"debug": "^4.3.1", "debug": "^4.3.1",
@ -941,17 +941,17 @@
} }
}, },
"node_modules/@eslint/config-helpers": { "node_modules/@eslint/config-helpers": {
"version": "0.2.2", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz",
"integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@eslint/core": { "node_modules/@eslint/core": {
"version": "0.13.0", "version": "0.15.1",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
"integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
"dependencies": { "dependencies": {
"@types/json-schema": "^7.0.15" "@types/json-schema": "^7.0.15"
}, },
@ -993,11 +993,14 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.25.1", "version": "9.32.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.1.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz",
"integrity": "sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==", "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://eslint.org/donate"
} }
}, },
"node_modules/@eslint/object-schema": { "node_modules/@eslint/object-schema": {
@ -1009,11 +1012,11 @@
} }
}, },
"node_modules/@eslint/plugin-kit": { "node_modules/@eslint/plugin-kit": {
"version": "0.2.8", "version": "0.3.4",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz",
"integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==",
"dependencies": { "dependencies": {
"@eslint/core": "^0.13.0", "@eslint/core": "^0.15.1",
"levn": "^0.4.1" "levn": "^0.4.1"
}, },
"engines": { "engines": {
@ -2816,9 +2819,9 @@
} }
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.14.1", "version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@ -3651,18 +3654,18 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.25.1", "version": "9.32.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.25.1.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz",
"integrity": "sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==", "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.20.0", "@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.2.1", "@eslint/config-helpers": "^0.3.0",
"@eslint/core": "^0.13.0", "@eslint/core": "^0.15.0",
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.25.1", "@eslint/js": "9.32.0",
"@eslint/plugin-kit": "^0.2.8", "@eslint/plugin-kit": "^0.3.4",
"@humanfs/node": "^0.16.6", "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2", "@humanwhocodes/retry": "^0.4.2",
@ -3673,9 +3676,9 @@
"cross-spawn": "^7.0.6", "cross-spawn": "^7.0.6",
"debug": "^4.3.2", "debug": "^4.3.2",
"escape-string-regexp": "^4.0.0", "escape-string-regexp": "^4.0.0",
"eslint-scope": "^8.3.0", "eslint-scope": "^8.4.0",
"eslint-visitor-keys": "^4.2.0", "eslint-visitor-keys": "^4.2.1",
"espree": "^10.3.0", "espree": "^10.4.0",
"esquery": "^1.5.0", "esquery": "^1.5.0",
"esutils": "^2.0.2", "esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
@ -3710,9 +3713,9 @@
} }
}, },
"node_modules/eslint-scope": { "node_modules/eslint-scope": {
"version": "8.3.0", "version": "8.4.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
"integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
"dependencies": { "dependencies": {
"esrecurse": "^4.3.0", "esrecurse": "^4.3.0",
"estraverse": "^5.2.0" "estraverse": "^5.2.0"
@ -3736,9 +3739,9 @@
} }
}, },
"node_modules/eslint/node_modules/eslint-visitor-keys": { "node_modules/eslint/node_modules/eslint-visitor-keys": {
"version": "4.2.0", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}, },
@ -3747,13 +3750,13 @@
} }
}, },
"node_modules/espree": { "node_modules/espree": {
"version": "10.3.0", "version": "10.4.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
"integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
"dependencies": { "dependencies": {
"acorn": "^8.14.0", "acorn": "^8.15.0",
"acorn-jsx": "^5.3.2", "acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^4.2.0" "eslint-visitor-keys": "^4.2.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -3763,9 +3766,9 @@
} }
}, },
"node_modules/espree/node_modules/eslint-visitor-keys": { "node_modules/espree/node_modules/eslint-visitor-keys": {
"version": "4.2.0", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}, },
@ -4047,13 +4050,14 @@
} }
}, },
"node_modules/form-data": { "node_modules/form-data": {
"version": "4.0.2", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"dependencies": { "dependencies": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
"combined-stream": "^1.0.8", "combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0", "es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12" "mime-types": "^2.1.12"
}, },
"engines": { "engines": {

View File

@ -21,7 +21,7 @@
"format": "npm run playlist:format", "format": "npm run playlist:format",
"update": "npm run playlist:generate && npm run api:generate && npm run readme:update", "update": "npm run playlist:generate && npm run api:generate && npm run readme:update",
"deploy": "npm run playlist:deploy && npm run api:deploy", "deploy": "npm run playlist:deploy && npm run api:deploy",
"lint": "npx eslint ./scripts/**/*.ts ./tests/**/*.ts", "lint": "npx eslint \"scripts/**/*.{ts,js}\" \"tests/**/*.{ts,js}\"",
"test": "jest --runInBand", "test": "jest --runInBand",
"postinstall": "npm run api:load" "postinstall": "npm run api:load"
}, },

View File

@ -73,7 +73,7 @@ export default async function main(filepath: string) {
logger.info('creating search index...') logger.info('creating search index...')
const items = channels.map((channel: Channel) => channel.getSearchable()).all() const items = channels.map((channel: Channel) => channel.getSearchable()).all()
const searchIndex = sjs.createIndex(items, { const searchIndex = sjs.createIndex(items, {
searchable: ['name', 'altNames', 'guideNames', 'streamNames', 'feedFullNames'] searchable: ['name', 'altNames', 'guideNames', 'streamTitles', 'feedFullNames']
}) })
logger.info('starting...\n') logger.info('starting...\n')
@ -100,7 +100,7 @@ async function selectChannel(
feedsGroupedByChannelId: Dictionary, feedsGroupedByChannelId: Dictionary,
channelsKeyById: Dictionary channelsKeyById: Dictionary
): Promise<string> { ): Promise<string> {
const query = escapeRegex(stream.getName()) const query = escapeRegex(stream.getTitle())
const similarChannels = searchIndex const similarChannels = searchIndex
.search(query) .search(query)
.map((item: ChannelSearchableData) => channelsKeyById.get(item.id)) .map((item: ChannelSearchableData) => channelsKeyById.get(item.id))
@ -108,7 +108,7 @@ async function selectChannel(
const url = stream.url.length > 50 ? stream.url.slice(0, 50) + '...' : stream.url const url = stream.url.length > 50 ? stream.url.slice(0, 50) + '...' : stream.url
const selected: ChoiceValue = await select({ const selected: ChoiceValue = await select({
message: `Select channel ID for "${stream.name}" (${url}):`, message: `Select channel ID for "${stream.title}" (${url}):`,
choices: getChannelChoises(new Collection(similarChannels)), choices: getChannelChoises(new Collection(similarChannels)),
pageSize: 10 pageSize: 10
}) })
@ -136,7 +136,7 @@ async function selectChannel(
} }
async function selectFeed(channelId: string, feedsGroupedByChannelId: Dictionary): Promise<string> { async function selectFeed(channelId: string, feedsGroupedByChannelId: Dictionary): Promise<string> {
const channelFeeds = new Collection(feedsGroupedByChannelId.get(channelId)) || new Collection() const channelFeeds = new Collection(feedsGroupedByChannelId.get(channelId))
const choices = getFeedChoises(channelFeeds) const choices = getFeedChoises(channelFeeds)
const selected: ChoiceValue = await select({ const selected: ChoiceValue = await select({

View File

@ -53,7 +53,7 @@ async function main() {
logger.info('sorting links...') logger.info('sorting links...')
streams = streams.orderBy( streams = streams.orderBy(
[ [
(stream: Stream) => stream.name, (stream: Stream) => stream.title,
(stream: Stream) => stream.getVerticalResolution(), (stream: Stream) => stream.getVerticalResolution(),
(stream: Stream) => stream.getLabel(), (stream: Stream) => stream.getLabel(),
(stream: Stream) => stream.url (stream: Stream) => stream.url
@ -63,7 +63,7 @@ async function main() {
logger.info('saving...') logger.info('saving...')
const groupedStreams = streams.groupBy((stream: Stream) => stream.getFilepath()) const groupedStreams = streams.groupBy((stream: Stream) => stream.getFilepath())
for (let filepath of groupedStreams.keys()) { for (const filepath of groupedStreams.keys()) {
const streams = groupedStreams.get(filepath) || [] const streams = groupedStreams.get(filepath) || []
if (!streams.length) return if (!streams.length) return

View File

@ -9,13 +9,14 @@ import {
IndexCategoryGenerator, IndexCategoryGenerator,
IndexLanguageGenerator, IndexLanguageGenerator,
IndexCountryGenerator, IndexCountryGenerator,
SubdivisionsGenerator,
IndexRegionGenerator, IndexRegionGenerator,
CategoriesGenerator, CategoriesGenerator,
CountriesGenerator, CountriesGenerator,
LanguagesGenerator, LanguagesGenerator,
RegionsGenerator, RegionsGenerator,
IndexGenerator,
SourcesGenerator, SourcesGenerator,
IndexGenerator,
RawGenerator RawGenerator
} from '../../generators' } from '../../generators'
@ -32,6 +33,7 @@ async function main() {
feedsGroupedByChannelId, feedsGroupedByChannelId,
logosGroupedByStreamId, logosGroupedByStreamId,
channelsKeyById, channelsKeyById,
subdivisions,
categories, categories,
countries, countries,
regions regions
@ -71,6 +73,9 @@ async function main() {
logger.info('generating categories/...') logger.info('generating categories/...')
await new CategoriesGenerator({ categories, streams, logFile }).generate() await new CategoriesGenerator({ categories, streams, logFile }).generate()
logger.info('generating languages/...')
await new LanguagesGenerator({ streams, logFile }).generate()
logger.info('generating countries/...') logger.info('generating countries/...')
await new CountriesGenerator({ await new CountriesGenerator({
countries, countries,
@ -78,8 +83,12 @@ async function main() {
logFile logFile
}).generate() }).generate()
logger.info('generating languages/...') logger.info('generating subdivisions/...')
await new LanguagesGenerator({ streams, logFile }).generate() await new SubdivisionsGenerator({
subdivisions,
streams,
logFile
}).generate()
logger.info('generating regions/...') logger.info('generating regions/...')
await new RegionsGenerator({ await new RegionsGenerator({

View File

@ -18,7 +18,7 @@ const LIVE_UPDATE_MAX_STREAMS = 100
let errors = 0 let errors = 0
let warnings = 0 let warnings = 0
let results = {} const results = {}
let interval let interval
let streams = new Collection() let streams = new Collection()
let isLiveUpdateEnabled = true let isLiveUpdateEnabled = true

View File

@ -4,9 +4,9 @@ import type { DataProcessorData } from '../../types/dataProcessor'
import { Stream, Playlist, Channel, Issue } from '../../models' import { Stream, Playlist, Channel, Issue } from '../../models'
import type { DataLoaderData } from '../../types/dataLoader' import type { DataLoaderData } from '../../types/dataLoader'
import { DATA_DIR, STREAMS_DIR } from '../../constants' import { DATA_DIR, STREAMS_DIR } from '../../constants'
import validUrl from 'valid-url' import { isURI } from '../../utils'
let processedIssues = new Collection() const processedIssues = new Collection()
async function main() { async function main() {
const logger = new Logger({ level: -999 }) const logger = new Logger({ level: -999 })
@ -55,7 +55,7 @@ async function main() {
logger.info('saving...') logger.info('saving...')
const groupedStreams = streams.groupBy((stream: Stream) => stream.getFilepath()) const groupedStreams = streams.groupBy((stream: Stream) => stream.getFilepath())
for (let filepath of groupedStreams.keys()) { for (const filepath of groupedStreams.keys()) {
let streams = groupedStreams.get(filepath) || [] let streams = groupedStreams.get(filepath) || []
streams = streams.filter((stream: Stream) => stream.removed === false) streams = streams.filter((stream: Stream) => stream.removed === false)
@ -114,7 +114,7 @@ async function editStreams({
if (data.missing('streamUrl')) return if (data.missing('streamUrl')) return
let stream: Stream = streams.first( const stream: Stream = streams.first(
(_stream: Stream) => _stream.url === data.getString('streamUrl') (_stream: Stream) => _stream.url === data.getString('streamUrl')
) )
if (!stream) return if (!stream) return
@ -129,7 +129,7 @@ async function editStreams({
.withChannel(channelsKeyById) .withChannel(channelsKeyById)
.withFeed(feedsGroupedByChannelId) .withFeed(feedsGroupedByChannelId)
.updateId() .updateId()
.updateName() .updateTitle()
.updateFilepath() .updateFilepath()
} }
@ -158,7 +158,7 @@ async function addStreams({
if (data.missing('streamId') || data.missing('streamUrl')) return if (data.missing('streamId') || data.missing('streamUrl')) return
if (streams.includes((_stream: Stream) => _stream.url === data.getString('streamUrl'))) return if (streams.includes((_stream: Stream) => _stream.url === data.getString('streamUrl'))) return
const streamUrl = data.getString('streamUrl') || '' const streamUrl = data.getString('streamUrl') || ''
if (!isUri(streamUrl)) return if (!isURI(streamUrl)) return
const streamId = data.getString('streamId') || '' const streamId = data.getString('streamId') || ''
const [channelId, feedId] = streamId.split('@') const [channelId, feedId] = streamId.split('@')
@ -173,11 +173,11 @@ async function addStreams({
const directives = data.getArray('directives') || [] const directives = data.getArray('directives') || []
const stream = new Stream({ const stream = new Stream({
channel: channelId, channelId,
feed: feedId, feedId,
name: data.getString('channelName') || channel.name, title: channel.name,
url: streamUrl, url: streamUrl,
user_agent: httpUserAgent, userAgent: httpUserAgent,
referrer: httpReferrer, referrer: httpReferrer,
directives, directives,
quality, quality,
@ -185,14 +185,10 @@ async function addStreams({
}) })
.withChannel(channelsKeyById) .withChannel(channelsKeyById)
.withFeed(feedsGroupedByChannelId) .withFeed(feedsGroupedByChannelId)
.updateName() .updateTitle()
.updateFilepath() .updateFilepath()
streams.add(stream) streams.add(stream)
processedIssues.add(issue.number) processedIssues.add(issue.number)
}) })
} }
function isUri(string: string) {
return validUrl.isUri(encodeURI(string))
}

View File

@ -44,7 +44,7 @@ async function main() {
let errors = new Collection() let errors = new Collection()
let warnings = new Collection() let warnings = new Collection()
let streamsGroupedByFilepath = streams.groupBy((stream: Stream) => stream.getFilepath()) const streamsGroupedByFilepath = streams.groupBy((stream: Stream) => stream.getFilepath())
for (const filepath of streamsGroupedByFilepath.keys()) { for (const filepath of streamsGroupedByFilepath.keys()) {
const streams = streamsGroupedByFilepath.get(filepath) const streams = streamsGroupedByFilepath.get(filepath)
if (!streams) continue if (!streams) continue

View File

@ -1,5 +1,11 @@
import { Logger } from '@freearhey/core' import { Logger } from '@freearhey/core'
import { CategoryTable, CountryTable, LanguageTable, RegionTable } from '../../tables' import {
CategoryTable,
CountryTable,
LanguageTable,
RegionTable,
SubdivisionTable
} from '../../tables'
import { Markdown } from '../../core' import { Markdown } from '../../core'
import { README_DIR } from '../../constants' import { README_DIR } from '../../constants'
import path from 'path' import path from 'path'
@ -9,10 +15,12 @@ async function main() {
logger.info('creating category table...') logger.info('creating category table...')
await new CategoryTable().make() await new CategoryTable().make()
logger.info('creating country table...')
await new CountryTable().make()
logger.info('creating language table...') logger.info('creating language table...')
await new LanguageTable().make() await new LanguageTable().make()
logger.info('creating country table...')
await new CountryTable().make()
logger.info('creating subdivision table...')
await new SubdivisionTable().make()
logger.info('creating region table...') logger.info('creating region table...')
await new RegionTable().make() await new RegionTable().make()

View File

@ -4,6 +4,7 @@ import { DataProcessorData } from '../../types/dataProcessor'
import { DATA_DIR, STREAMS_DIR } from '../../constants' import { DATA_DIR, STREAMS_DIR } from '../../constants'
import { DataLoaderData } from '../../types/dataLoader' import { DataLoaderData } from '../../types/dataLoader'
import { Issue, Stream } from '../../models' import { Issue, Stream } from '../../models'
import { isURI } from '../../utils'
async function main() { async function main() {
const logger = new Logger() const logger = new Logger()
@ -44,7 +45,7 @@ async function main() {
issue.labels.find((label: string) => label === 'streams:remove') issue.labels.find((label: string) => label === 'streams:remove')
) )
removeRequests.forEach((issue: Issue) => { removeRequests.forEach((issue: Issue) => {
const streamUrls = issue.data.has('streamUrl') ? issue.data.getArray('streamUrl') : [] const streamUrls = issue.data.getArray('streamUrl') || []
if (!streamUrls.length) { if (!streamUrls.length) {
const result = { const result = {
@ -93,6 +94,7 @@ async function main() {
if (!channelId) result.status = 'missing_id' if (!channelId) result.status = 'missing_id'
else if (!streamUrl) result.status = 'missing_link' else if (!streamUrl) result.status = 'missing_link'
else if (!isURI(streamUrl)) result.status = 'invalid_link'
else if (blocklistRecordsGroupedByChannelId.has(channelId)) result.status = 'blocked' else if (blocklistRecordsGroupedByChannelId.has(channelId)) result.status = 'blocked'
else if (channelsKeyById.missing(channelId)) result.status = 'wrong_id' else if (channelsKeyById.missing(channelId)) result.status = 'wrong_id'
else if (streamsGroupedByUrl.has(streamUrl)) result.status = 'on_playlist' else if (streamsGroupedByUrl.has(streamUrl)) result.status = 'on_playlist'

View File

@ -25,7 +25,7 @@ export class DataProcessor {
const languages = new Collection(data.languages).map(data => new Language(data)) const languages = new Collection(data.languages).map(data => new Language(data))
const languagesKeyByCode = languages.keyBy((language: Language) => language.code) const languagesKeyByCode = languages.keyBy((language: Language) => language.code)
const subdivisions = new Collection(data.subdivisions).map(data => new Subdivision(data)) let subdivisions = new Collection(data.subdivisions).map(data => new Subdivision(data))
const subdivisionsKeyByCode = subdivisions.keyBy((subdivision: Subdivision) => subdivision.code) const subdivisionsKeyByCode = subdivisions.keyBy((subdivision: Subdivision) => subdivision.code)
const subdivisionsGroupedByCountryCode = subdivisions.groupBy( const subdivisionsGroupedByCountryCode = subdivisions.groupBy(
(subdivision: Subdivision) => subdivision.countryCode (subdivision: Subdivision) => subdivision.countryCode
@ -42,6 +42,10 @@ export class DataProcessor {
) )
const countriesKeyByCode = countries.keyBy((country: Country) => country.code) const countriesKeyByCode = countries.keyBy((country: Country) => country.code)
subdivisions = subdivisions.map((subdivision: Subdivision) =>
subdivision.withCountry(countriesKeyByCode)
)
const timezones = new Collection(data.timezones).map(data => const timezones = new Collection(data.timezones).map(data =>
new Timezone(data).withCountries(countriesKeyByCode) new Timezone(data).withCountries(countriesKeyByCode)
) )
@ -74,7 +78,7 @@ export class DataProcessor {
const feedsGroupedByChannelId = feeds.groupBy((feed: Feed) => feed.channelId) const feedsGroupedByChannelId = feeds.groupBy((feed: Feed) => feed.channelId)
const feedsGroupedById = feeds.groupBy((feed: Feed) => feed.id) const feedsGroupedById = feeds.groupBy((feed: Feed) => feed.id)
let logos = new Collection(data.logos).map(data => new Logo(data).withFeed(feedsGroupedById)) const logos = new Collection(data.logos).map(data => new Logo(data).withFeed(feedsGroupedById))
const logosGroupedByChannelId = logos.groupBy((logo: Logo) => logo.channelId) const logosGroupedByChannelId = logos.groupBy((logo: Logo) => logo.channelId)
const logosGroupedByStreamId = logos.groupBy((logo: Logo) => logo.getStreamId()) const logosGroupedByStreamId = logos.groupBy((logo: Logo) => logo.getStreamId())

View File

@ -23,6 +23,7 @@ export class IssueLoader {
repo: REPO, repo: REPO,
per_page: 100, per_page: 100,
labels, labels,
status: 'open',
headers: { headers: {
'X-GitHub-Api-Version': '2022-11-28' 'X-GitHub-Api-Version': '2022-11-28'
} }

View File

@ -10,7 +10,6 @@ const FIELDS = new Dictionary({
'New Stream URL': 'newStreamUrl', 'New Stream URL': 'newStreamUrl',
Label: 'label', Label: 'label',
Quality: 'quality', Quality: 'quality',
'Channel Name': 'channelName',
'HTTP User-Agent': 'httpUserAgent', 'HTTP User-Agent': 'httpUserAgent',
'HTTP User Agent': 'httpUserAgent', 'HTTP User Agent': 'httpUserAgent',
'HTTP Referrer': 'httpReferrer', 'HTTP Referrer': 'httpReferrer',

View File

@ -1,4 +1,4 @@
import { Country, Subdivision, Stream, Playlist } from '../models' import { Country, Stream, Playlist } from '../models'
import { Collection, Storage, File } from '@freearhey/core' import { Collection, Storage, File } from '@freearhey/core'
import { PUBLIC_DIR } from '../constants' import { PUBLIC_DIR } from '../constants'
import { Generator } from './generator' import { Generator } from './generator'
@ -40,21 +40,6 @@ export class CountriesGenerator implements Generator {
this.logFile.append( this.logFile.append(
JSON.stringify({ type: 'country', filepath, count: playlist.streams.count() }) + EOL JSON.stringify({ type: 'country', filepath, count: playlist.streams.count() }) + EOL
) )
country.getSubdivisions().forEach(async (subdivision: Subdivision) => {
const subdivisionStreams = streams.filter((stream: Stream) =>
stream.isBroadcastInSubdivision(subdivision)
)
if (subdivisionStreams.isEmpty()) return
const playlist = new Playlist(subdivisionStreams, { public: true })
const filepath = `subdivisions/${subdivision.code.toLowerCase()}.m3u`
await this.storage.save(filepath, playlist.toString())
this.logFile.append(
JSON.stringify({ type: 'subdivision', filepath, count: playlist.streams.count() }) + EOL
)
})
}) })
const undefinedStreams = streams.filter((stream: Stream) => !stream.hasBroadcastArea()) const undefinedStreams = streams.filter((stream: Stream) => !stream.hasBroadcastArea())

View File

@ -10,3 +10,4 @@ export * from './languagesGenerator'
export * from './rawGenerator' export * from './rawGenerator'
export * from './regionsGenerator' export * from './regionsGenerator'
export * from './sourcesGenerator' export * from './sourcesGenerator'
export * from './subdivisionsGenerator'

View File

@ -23,7 +23,7 @@ export class RawGenerator implements Generator {
async generate() { async generate() {
const files = this.streams.groupBy((stream: Stream) => stream.getFilename()) const files = this.streams.groupBy((stream: Stream) => stream.getFilename())
for (let filename of files.keys()) { for (const filename of files.keys()) {
const streams = new Collection(files.get(filename)).map((stream: Stream) => { const streams = new Collection(files.get(filename)).map((stream: Stream) => {
const groupTitle = stream.getCategoryNames().join(';') const groupTitle = stream.getCategoryNames().join(';')
if (groupTitle) stream.groupTitle = groupTitle if (groupTitle) stream.groupTitle = groupTitle

View File

@ -23,7 +23,7 @@ export class SourcesGenerator implements Generator {
async generate() { async generate() {
const files: Dictionary = this.streams.groupBy((stream: Stream) => stream.getFilename()) const files: Dictionary = this.streams.groupBy((stream: Stream) => stream.getFilename())
for (let filename of files.keys()) { for (const filename of files.keys()) {
if (!filename) continue if (!filename) continue
let streams = new Collection(files.get(filename)) let streams = new Collection(files.get(filename))

View File

@ -0,0 +1,46 @@
import { Subdivision, Stream, Playlist } from '../models'
import { Collection, Storage, File } from '@freearhey/core'
import { PUBLIC_DIR } from '../constants'
import { Generator } from './generator'
import { EOL } from 'node:os'
type SubdivisionsGeneratorProps = {
streams: Collection
subdivisions: Collection
logFile: File
}
export class SubdivisionsGenerator implements Generator {
streams: Collection
subdivisions: Collection
storage: Storage
logFile: File
constructor({ streams, subdivisions, logFile }: SubdivisionsGeneratorProps) {
this.streams = streams.clone()
this.subdivisions = subdivisions
this.storage = new Storage(PUBLIC_DIR)
this.logFile = logFile
}
async generate(): Promise<void> {
const streams = this.streams
.orderBy((stream: Stream) => stream.getTitle())
.filter((stream: Stream) => stream.isSFW())
this.subdivisions.forEach(async (subdivision: Subdivision) => {
const subdivisionStreams = streams.filter((stream: Stream) =>
stream.isBroadcastInSubdivision(subdivision)
)
if (subdivisionStreams.isEmpty()) return
const playlist = new Playlist(subdivisionStreams, { public: true })
const filepath = `subdivisions/${subdivision.code.toLowerCase()}.m3u`
await this.storage.save(filepath, playlist.toString())
this.logFile.append(
JSON.stringify({ type: 'subdivision', filepath, count: playlist.streams.count() }) + EOL
)
})
}
}

View File

@ -133,9 +133,9 @@ export class Channel {
return streams return streams
} }
getStreamNames(): Collection { getStreamTitles(): Collection {
return this.getStreams() return this.getStreams()
.map((stream: Stream) => stream.getName()) .map((stream: Stream) => stream.getTitle())
.uniq() .uniq()
} }
@ -184,7 +184,7 @@ export class Channel {
name: this.name, name: this.name,
altNames: this.altNames.all(), altNames: this.altNames.all(),
guideNames: this.getGuideNames().all(), guideNames: this.getGuideNames().all(),
streamNames: this.getStreamNames().all(), streamTitles: this.getStreamTitles().all(),
feedFullNames: this.getFeedFullNames().all() feedFullNames: this.getFeedFullNames().all()
} }
} }

View File

@ -190,8 +190,15 @@ export class Feed {
isBroadcastInSubdivision(subdivision: Subdivision): boolean { isBroadcastInSubdivision(subdivision: Subdivision): boolean {
if (this.isInternational()) return false if (this.isInternational()) return false
if (this.broadcastSubdivisionCodes.includes(subdivision.code)) return true
if (
this.broadcastSubdivisionCodes.isEmpty() &&
subdivision.country &&
this.isBroadcastInCountry(subdivision.country)
)
return true
return this.broadcastSubdivisionCodes.includes(subdivision.code) return false
} }
isBroadcastInCountry(country: Country): boolean { isBroadcastInCountry(country: Country): boolean {

View File

@ -6,7 +6,7 @@ import { IssueData } from '../core'
import path from 'node:path' import path from 'node:path'
export class Stream { export class Stream {
name?: string title: string
url: string url: string
id?: string id?: string
channelId?: string channelId?: string
@ -28,16 +28,17 @@ export class Stream {
constructor(data?: StreamData) { constructor(data?: StreamData) {
if (!data) return if (!data) return
const id = data.channel && data.feed ? [data.channel, data.feed].join('@') : data.channel const id =
data.channelId && data.feedId ? [data.channelId, data.feedId].join('@') : data.channelId
const { verticalResolution, isInterlaced } = parseQuality(data.quality) const { verticalResolution, isInterlaced } = parseQuality(data.quality)
this.id = id || undefined this.id = id || undefined
this.channelId = data.channel || undefined this.channelId = data.channelId || undefined
this.feedId = data.feed || undefined this.feedId = data.feedId || undefined
this.name = data.name || undefined this.title = data.title || ''
this.url = data.url this.url = data.url
this.referrer = data.referrer || undefined this.referrer = data.referrer || undefined
this.userAgent = data.user_agent || undefined this.userAgent = data.userAgent || undefined
this.verticalResolution = verticalResolution || undefined this.verticalResolution = verticalResolution || undefined
this.isInterlaced = isInterlaced || undefined this.isInterlaced = isInterlaced || undefined
this.label = data.label || undefined this.label = data.label || undefined
@ -65,21 +66,22 @@ export class Stream {
} }
fromPlaylistItem(data: parser.PlaylistItem): this { fromPlaylistItem(data: parser.PlaylistItem): this {
function parseTitle(title: string): { function parseName(name: string): {
name: string title: string
label: string label: string
quality: string quality: string
} { } {
let title = name
const [, label] = title.match(/ \[(.*)\]$/) || [null, ''] const [, label] = title.match(/ \[(.*)\]$/) || [null, '']
title = title.replace(new RegExp(` \\[${escapeRegExp(label)}\\]$`), '') title = title.replace(new RegExp(` \\[${escapeRegExp(label)}\\]$`), '')
const [, quality] = title.match(/ \(([0-9]+p)\)$/) || [null, ''] const [, quality] = title.match(/ \(([0-9]+p)\)$/) || [null, '']
title = title.replace(new RegExp(` \\(${quality}\\)$`), '') title = title.replace(new RegExp(` \\(${quality}\\)$`), '')
return { name: title, label, quality } return { title, label, quality }
} }
function parseDirectives(string: string) { function parseDirectives(string: string) {
let directives = new Collection() const directives = new Collection()
if (!string) return directives if (!string) return directives
@ -100,7 +102,7 @@ export class Stream {
if (!data.url) throw new Error('"url" property is required') if (!data.url) throw new Error('"url" property is required')
const [channelId, feedId] = data.tvg.id.split('@') const [channelId, feedId] = data.tvg.id.split('@')
const { name, label, quality } = parseTitle(data.name) const { title, label, quality } = parseName(data.name)
const { verticalResolution, isInterlaced } = parseQuality(quality) const { verticalResolution, isInterlaced } = parseQuality(quality)
this.id = data.tvg.id || undefined this.id = data.tvg.id || undefined
@ -108,7 +110,7 @@ export class Stream {
this.channelId = channelId || undefined this.channelId = channelId || undefined
this.line = data.line this.line = data.line
this.label = label || undefined this.label = label || undefined
this.name = name this.title = title
this.verticalResolution = verticalResolution || undefined this.verticalResolution = verticalResolution || undefined
this.isInterlaced = isInterlaced || undefined this.isInterlaced = isInterlaced || undefined
this.url = data.url this.url = data.url
@ -241,12 +243,12 @@ export class Stream {
return parseInt(this.getQuality().replace(/p|i/, '')) return parseInt(this.getQuality().replace(/p|i/, ''))
} }
updateName(): this { updateTitle(): this {
if (!this.channel) return this if (!this.channel) return this
this.name = this.channel.name this.title = this.channel.name
if (this.feed && !this.feed.isMain) { if (this.feed && !this.feed.isMain) {
this.name += ` ${this.feed.name}` this.title += ` ${this.feed.name}`
} }
return this return this
@ -375,12 +377,12 @@ export class Stream {
return logo ? logo.url : '' return logo ? logo.url : ''
} }
getName(): string { getTitle(): string {
return this.name || '' return this.title || ''
} }
getTitle(): string { getFullTitle(): string {
let title = `${this.getName()}` let title = `${this.getTitle()}`
if (this.getQuality()) { if (this.getQuality()) {
title += ` (${this.getQuality()})` title += ` (${this.getQuality()})`
@ -405,6 +407,7 @@ export class Stream {
return { return {
channel: this.channelId || null, channel: this.channelId || null,
feed: this.feedId || null, feed: this.feedId || null,
title: this.title,
url: this.url, url: this.url,
referrer: this.referrer || null, referrer: this.referrer || null,
user_agent: this.userAgent || null, user_agent: this.userAgent || null,
@ -427,7 +430,7 @@ export class Stream {
output += ` http-user-agent="${this.userAgent}"` output += ` http-user-agent="${this.userAgent}"`
} }
output += `,${this.getTitle()}` output += `,${this.getFullTitle()}`
this.directives.forEach((prop: string) => { this.directives.forEach((prop: string) => {
output += `\r\n${prop}` output += `\r\n${prop}`

View File

@ -1,6 +1,6 @@
import { Storage, Collection, File } from '@freearhey/core' import { Storage, Collection, File } from '@freearhey/core'
import { HTMLTable, LogParser, LogItem } from '../core' import { HTMLTable, LogParser, LogItem } from '../core'
import { Country, Subdivision } from '../models' import { Country } from '../models'
import { DATA_DIR, LOGS_DIR, README_DIR } from '../constants' import { DATA_DIR, LOGS_DIR, README_DIR } from '../constants'
import { Table } from './table' import { Table } from './table'
@ -13,11 +13,6 @@ export class CountryTable implements Table {
const countriesContent = await dataStorage.json('countries.json') const countriesContent = await dataStorage.json('countries.json')
const countries = new Collection(countriesContent).map(data => new Country(data)) const countries = new Collection(countriesContent).map(data => new Country(data))
const countriesGroupedByCode = countries.keyBy((country: Country) => country.code) const countriesGroupedByCode = countries.keyBy((country: Country) => country.code)
const subdivisionsContent = await dataStorage.json('subdivisions.json')
const subdivisions = new Collection(subdivisionsContent).map(data => new Subdivision(data))
const subdivisionsGroupedByCode = subdivisions.keyBy(
(subdivision: Subdivision) => subdivision.code
)
const parser = new LogParser() const parser = new LogParser()
const logsStorage = new Storage(LOGS_DIR) const logsStorage = new Storage(LOGS_DIR)
@ -26,27 +21,6 @@ export class CountryTable implements Table {
let data = new Collection() let data = new Collection()
parsed
.filter((logItem: LogItem) => logItem.type === 'subdivision')
.forEach((logItem: LogItem) => {
const file = new File(logItem.filepath)
const code = file.name().toUpperCase()
const [countryCode, subdivisionCode] = code.split('-') || ['', '']
const country = countriesGroupedByCode.get(countryCode)
if (country && subdivisionCode) {
const subdivision = subdivisionsGroupedByCode.get(code)
if (subdivision) {
data.add([
`${country.name}/${subdivision.name}`,
`&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${subdivision.name}`,
logItem.count,
`<code>https://iptv-org.github.io/iptv/${logItem.filepath}</code>`
])
}
}
})
parsed parsed
.filter((logItem: LogItem) => logItem.type === 'country') .filter((logItem: LogItem) => logItem.type === 'country')
.forEach((logItem: LogItem) => { .forEach((logItem: LogItem) => {

View File

@ -2,3 +2,4 @@ export * from './categoryTable'
export * from './countryTable' export * from './countryTable'
export * from './languageTable' export * from './languageTable'
export * from './regionTable' export * from './regionTable'
export * from './subdivisionTable'

View File

@ -0,0 +1,72 @@
import { Storage, Collection, File } from '@freearhey/core'
import { HTMLTable, LogParser, LogItem } from '../core'
import { Country, Subdivision } from '../models'
import { DATA_DIR, LOGS_DIR, README_DIR } from '../constants'
import { Table } from './table'
export class SubdivisionTable implements Table {
constructor() {}
async make() {
const dataStorage = new Storage(DATA_DIR)
const countriesContent = await dataStorage.json('countries.json')
const countries = new Collection(countriesContent).map(data => new Country(data))
const countriesGroupedByCode = countries.keyBy((country: Country) => country.code)
const subdivisionsContent = await dataStorage.json('subdivisions.json')
const subdivisions = new Collection(subdivisionsContent).map(data => new Subdivision(data))
const subdivisionsGroupedByCode = subdivisions.keyBy(
(subdivision: Subdivision) => subdivision.code
)
const parser = new LogParser()
const logsStorage = new Storage(LOGS_DIR)
const generatorsLog = await logsStorage.load('generators.log')
const parsed = parser.parse(generatorsLog)
const parsedSubdivisions = parsed.filter((logItem: LogItem) => logItem.type === 'subdivision')
let output = ''
countries.forEach((country: Country) => {
const parsedCountrySubdivisions = parsedSubdivisions.filter((logItem: LogItem) =>
logItem.filepath.includes(`subdivisions/${country.code.toLowerCase()}`)
)
if (!parsedCountrySubdivisions.length) return
output += `\r\n<details>\r\n<summary>${country.name}</summary>\r\n`
const data = new Collection()
parsedCountrySubdivisions.forEach((logItem: LogItem) => {
const file = new File(logItem.filepath)
const code = file.name().toUpperCase()
const [countryCode, subdivisionCode] = code.split('-') || ['', '']
const country = countriesGroupedByCode.get(countryCode)
if (country && subdivisionCode) {
const subdivision = subdivisionsGroupedByCode.get(code)
if (subdivision) {
data.add([
subdivision.name,
logItem.count,
`<code>https://iptv-org.github.io/iptv/${logItem.filepath}</code>`
])
}
}
})
const table = new HTMLTable(data.all(), [
{ name: 'Subdivision' },
{ name: 'Channels', align: 'right' },
{ name: 'Playlist', nowrap: true }
])
output += table.toString()
output += '\r\n</details>'
})
const readmeStorage = new Storage(README_DIR)
await readmeStorage.save('_subdivisions.md', output.trim())
}
}

View File

@ -45,6 +45,6 @@ export type ChannelSearchableData = {
name: string name: string
altNames: string[] altNames: string[]
guideNames: string[] guideNames: string[]
streamNames: string[] streamTitles: string[]
feedFullNames: string[] feedFullNames: string[]
} }

View File

@ -1,10 +1,10 @@
export type StreamData = { export type StreamData = {
channel: string | null channelId: string | null
feed: string | null feedId: string | null
name: string | null title: string | null
url: string url: string
referrer: string | null referrer: string | null
user_agent: string | null userAgent: string | null
quality: string | null quality: string | null
label: string | null label: string | null
directives: string[] directives: string[]

5
scripts/utils.ts Normal file
View File

@ -0,0 +1,5 @@
import validUrl from 'valid-url'
export function isURI(string: string) {
return validUrl.isUri(encodeURI(string))
}

View File

@ -2,6 +2,7 @@
{ {
"channel": null, "channel": null,
"feed": null, "feed": null,
"title": "Daawah TV",
"url": "http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8", "url": "http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8",
"referrer": null, "referrer": null,
"user_agent": null "user_agent": null
@ -9,6 +10,7 @@
{ {
"channel": null, "channel": null,
"feed": null, "feed": null,
"title": "Andorra TV",
"url": "http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8", "url": "http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8",
"referrer": "http://imn.iq", "referrer": "http://imn.iq",
"user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148" "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"
@ -16,12 +18,15 @@
{ {
"channel": "AndorraTV.ad", "channel": "AndorraTV.ad",
"feed": "SD", "feed": "SD",
"title": "ATV",
"url": "https://iptv-all.lanesh4d0w.repl.co/andorra/atv|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\"", "url": "https://iptv-all.lanesh4d0w.repl.co/andorra/atv|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\"",
"referrer": null, "referrer": null,
"user_agent": null "user_agent": null
}, },
{ {
"channel": "BBCNews.uk", "channel": "BBCNews.uk",
"feed": null,
"title": "BBC News HD",
"url": "http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index.m3u8", "url": "http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index.m3u8",
"referrer": null, "referrer": null,
"user_agent": null "user_agent": null
@ -29,6 +34,7 @@
{ {
"channel": "LDPRTV.ru", "channel": "LDPRTV.ru",
"feed": null, "feed": null,
"title": "ЛДПР ТВ",
"url": "http://46.46.143.222:1935/live/mp4:ldpr.stream/blocked.m3u8", "url": "http://46.46.143.222:1935/live/mp4:ldpr.stream/blocked.m3u8",
"referrer": null, "referrer": null,
"user_agent": null "user_agent": null
@ -36,6 +42,7 @@
{ {
"channel": "MeteoMedia.ca", "channel": "MeteoMedia.ca",
"feed": null, "feed": null,
"title": "Meteomedia",
"url": "http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8", "url": "http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8",
"referrer": null, "referrer": null,
"user_agent": null "user_agent": null
@ -43,6 +50,7 @@
{ {
"channel": "VisitXTV.nl", "channel": "VisitXTV.nl",
"feed": null, "feed": null,
"title": "Visit-X TV",
"url": "https://stream.visit-x.tv/vxtv/ngrp:live_all/30fps.m3u8", "url": "https://stream.visit-x.tv/vxtv/ngrp:live_all/30fps.m3u8",
"referrer": null, "referrer": null,
"user_agent": null "user_agent": null
@ -50,6 +58,7 @@
{ {
"channel": "Zoo.ad", "channel": "Zoo.ad",
"feed": null, "feed": null,
"title": "Zoo",
"url": "https://iptv-all.lanesh4d0w.repl.co/andorra/zoo", "url": "https://iptv-all.lanesh4d0w.repl.co/andorra/zoo",
"referrer": null, "referrer": null,
"user_agent": null "user_agent": null

View File

@ -1,4 +1,6 @@
#EXTM3U #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" 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]
#EXTVLCOPT:http-referrer=http://imn.iq #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 #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
@ -15,5 +17,5 @@ https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd
http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8 http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8
#EXTINF:-1 tvg-id="DunaWorld.hu" tvg-logo="https://i.imgur.com/uOBQJZS.png" group-title="Undefined",Duna World (576i) #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 http://146.59.85.40:89/dunaworld/index.m3u8
#EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="" group-title="Undefined",Zoo (720p) #EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="https://i.imgur.com/ciTJrnl.png" group-title="Undefined",Zoo (720p)
https://iptv-all.lanesh4d0w.repl.co/andorra/zoo https://iptv-all.lanesh4d0w.repl.co/andorra/zoo

View File

@ -1,3 +1,5 @@
#EXTM3U #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="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia #EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia
http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8 http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8

View File

@ -11,5 +11,5 @@ http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://refe
https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Daawah TV #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Daawah TV
http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8 http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8
#EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="" group-title="Undefined",Zoo (720p) #EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="https://i.imgur.com/ciTJrnl.png" group-title="Undefined",Zoo (720p)
https://iptv-all.lanesh4d0w.repl.co/andorra/zoo https://iptv-all.lanesh4d0w.repl.co/andorra/zoo

View File

@ -9,6 +9,8 @@ http://gohoski.fvds.ru:3000/mediabay/162/index.m3u8
http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index.m3u8 http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index.m3u8
#EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia #EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia
http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.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" 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]
#EXTVLCOPT:http-referrer=http://imn.iq #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 #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
@ -25,5 +27,5 @@ https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd
http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8 http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8
#EXTINF:-1 tvg-id="DunaWorld.hu" tvg-logo="https://i.imgur.com/uOBQJZS.png" group-title="Undefined",Duna World (576i) #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 http://146.59.85.40:89/dunaworld/index.m3u8
#EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="" group-title="Undefined",Zoo (720p) #EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="https://i.imgur.com/ciTJrnl.png" group-title="Undefined",Zoo (720p)
https://iptv-all.lanesh4d0w.repl.co/andorra/zoo https://iptv-all.lanesh4d0w.repl.co/andorra/zoo

View File

@ -1,6 +1,8 @@
#EXTM3U #EXTM3U
#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="https://i.imgur.com/BnhTn8i.png" group-title="Andorra",ATV #EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="https://i.imgur.com/BnhTn8i.png" group-title="Andorra",ATV
https://iptv-all.lanesh4d0w.repl.co/andorra/atv https://iptv-all.lanesh4d0w.repl.co/andorra/atv
#EXTINF:-1 tvg-id="5AABTV.ca" tvg-logo="" group-title="Canada",5AAB TV
http://158.69.124.9:1935/5aabtv/5aabtv/playlist.m3u8
#EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Canada",Meteomedia #EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Canada",Meteomedia
http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8 http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8
#EXTINF:-1 tvg-id="ElTR.kg" tvg-logo="https://i.ibb.co/r6czQwQ/365049798-774721644658455-5702658175909463406-n-2.png" group-title="Kazakhstan",ЭлТР (480p) [Not 24/7] #EXTINF:-1 tvg-id="ElTR.kg" tvg-logo="https://i.ibb.co/r6czQwQ/365049798-774721644658455-5702658175909463406-n-2.png" group-title="Kazakhstan",ЭлТР (480p) [Not 24/7]
@ -31,5 +33,5 @@ http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://refe
https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Daawah TV #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Daawah TV
http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8 http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8
#EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="" group-title="Undefined",Zoo (720p) #EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="https://i.imgur.com/ciTJrnl.png" group-title="Undefined",Zoo (720p)
https://iptv-all.lanesh4d0w.repl.co/andorra/zoo https://iptv-all.lanesh4d0w.repl.co/andorra/zoo

View File

@ -5,6 +5,8 @@ https://iptv-all.lanesh4d0w.repl.co/andorra/atv
http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index.m3u8 http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index.m3u8
#EXTINF:-1 tvg-id="LDPRTV.ru" tvg-logo="https://iptvx.one/icn/ldpr-tv.png" group-title="Russian",ЛДПР ТВ (1080p) #EXTINF:-1 tvg-id="LDPRTV.ru" tvg-logo="https://iptvx.one/icn/ldpr-tv.png" group-title="Russian",ЛДПР ТВ (1080p)
http://46.46.143.222:1935/live/mp4:ldpr.stream/blocked.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" 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]
#EXTVLCOPT:http-referrer=http://imn.iq #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 #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
@ -21,7 +23,7 @@ http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8
http://146.59.85.40:89/dunaworld/index.m3u8 http://146.59.85.40:89/dunaworld/index.m3u8
#EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Undefined",Meteomedia #EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Undefined",Meteomedia
http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8 http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8
#EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="" group-title="Undefined",Zoo (720p) #EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="https://i.imgur.com/ciTJrnl.png" group-title="Undefined",Zoo (720p)
https://iptv-all.lanesh4d0w.repl.co/andorra/zoo https://iptv-all.lanesh4d0w.repl.co/andorra/zoo
#EXTINF:-1 tvg-id="ElTR.kg" tvg-logo="https://i.ibb.co/r6czQwQ/365049798-774721644658455-5702658175909463406-n-2.png" group-title="Undefined",ЭлТР (480p) [Not 24/7] #EXTINF:-1 tvg-id="ElTR.kg" tvg-logo="https://i.ibb.co/r6czQwQ/365049798-774721644658455-5702658175909463406-n-2.png" group-title="Undefined",ЭлТР (480p) [Not 24/7]
http://gohoski.fvds.ru:3000/mediabay/162/index.m3u8 http://gohoski.fvds.ru:3000/mediabay/162/index.m3u8

View File

@ -1,4 +1,6 @@
#EXTM3U #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" 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]
#EXTVLCOPT:http-referrer=http://imn.iq #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 #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
@ -19,7 +21,7 @@ http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8
http://146.59.85.40:89/dunaworld/index.m3u8 http://146.59.85.40:89/dunaworld/index.m3u8
#EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia #EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia
http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8 http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8
#EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="" group-title="Undefined",Zoo (720p) #EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="https://i.imgur.com/ciTJrnl.png" group-title="Undefined",Zoo (720p)
https://iptv-all.lanesh4d0w.repl.co/andorra/zoo https://iptv-all.lanesh4d0w.repl.co/andorra/zoo
#EXTINF:-1 tvg-id="LDPRTV.ru" tvg-logo="https://iptvx.one/icn/ldpr-tv.png" group-title="General",ЛДПР ТВ (1080p) #EXTINF:-1 tvg-id="LDPRTV.ru" tvg-logo="https://iptvx.one/icn/ldpr-tv.png" group-title="General",ЛДПР ТВ (1080p)
http://46.46.143.222:1935/live/mp4:ldpr.stream/blocked.m3u8 http://46.46.143.222:1935/live/mp4:ldpr.stream/blocked.m3u8

View File

@ -1,4 +1,6 @@
#EXTM3U #EXTM3U
#EXTINF:-1 tvg-id="5AABTV.ca" tvg-logo="" group-title="Americas",5AAB TV
http://158.69.124.9:1935/5aabtv/5aabtv/playlist.m3u8
#EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Americas",Meteomedia #EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Americas",Meteomedia
http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8 http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8
#EXTINF:-1 tvg-id="LDPRTV.ru" tvg-logo="https://iptvx.one/icn/ldpr-tv.png" group-title="Asia",ЛДПР ТВ (1080p) #EXTINF:-1 tvg-id="LDPRTV.ru" tvg-logo="https://iptvx.one/icn/ldpr-tv.png" group-title="Asia",ЛДПР ТВ (1080p)
@ -23,8 +25,12 @@ https://iptv-all.lanesh4d0w.repl.co/andorra/atv
http://46.46.143.222:1935/live/mp4:ldpr.stream/blocked.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="Europe, the Middle East and Africa",ЭлТР (480p) [Not 24/7] #EXTINF:-1 tvg-id="ElTR.kg" tvg-logo="https://i.ibb.co/r6czQwQ/365049798-774721644658455-5702658175909463406-n-2.png" group-title="Europe, the Middle East and Africa",ЭлТР (480p) [Not 24/7]
http://gohoski.fvds.ru:3000/mediabay/162/index.m3u8 http://gohoski.fvds.ru:3000/mediabay/162/index.m3u8
#EXTINF:-1 tvg-id="5AABTV.ca" tvg-logo="" group-title="North America",5AAB TV
http://158.69.124.9:1935/5aabtv/5aabtv/playlist.m3u8
#EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="North America",Meteomedia #EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="North America",Meteomedia
http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8 http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8
#EXTINF:-1 tvg-id="5AABTV.ca" tvg-logo="" group-title="Northern America",5AAB TV
http://158.69.124.9:1935/5aabtv/5aabtv/playlist.m3u8
#EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Northern America",Meteomedia #EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Northern America",Meteomedia
http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8 http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8
#EXTINF:-1 tvg-id="BBCNews.uk" tvg-logo="https://raw.githubusercontent.com/Tapiosinn/tv-logos/master/countries/united-kingdom/bbc-news-uk.png" group-title="International",BBC News HD #EXTINF:-1 tvg-id="BBCNews.uk" tvg-logo="https://raw.githubusercontent.com/Tapiosinn/tv-logos/master/countries/united-kingdom/bbc-news-uk.png" group-title="International",BBC News HD
@ -43,5 +49,5 @@ http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://refe
https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Daawah TV #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Daawah TV
http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8 http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8
#EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="" group-title="Undefined",Zoo (720p) #EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="https://i.imgur.com/ciTJrnl.png" group-title="Undefined",Zoo (720p)
https://iptv-all.lanesh4d0w.repl.co/andorra/zoo https://iptv-all.lanesh4d0w.repl.co/andorra/zoo

View File

@ -1,4 +1,6 @@
#EXTM3U #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" 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]
#EXTVLCOPT:http-referrer=http://imn.iq #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 #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
@ -15,7 +17,7 @@ http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8
http://146.59.85.40:89/dunaworld/index.m3u8 http://146.59.85.40:89/dunaworld/index.m3u8
#EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia #EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia
http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8 http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8
#EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="" group-title="Undefined",Zoo (720p) #EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="https://i.imgur.com/ciTJrnl.png" group-title="Undefined",Zoo (720p)
https://iptv-all.lanesh4d0w.repl.co/andorra/zoo https://iptv-all.lanesh4d0w.repl.co/andorra/zoo
#EXTINF:-1 tvg-id="ElTR.kg" tvg-logo="https://i.ibb.co/r6czQwQ/365049798-774721644658455-5702658175909463406-n-2.png" group-title="General",ЭлТР (480p) [Not 24/7] #EXTINF:-1 tvg-id="ElTR.kg" tvg-logo="https://i.ibb.co/r6czQwQ/365049798-774721644658455-5702658175909463406-n-2.png" group-title="General",ЭлТР (480p) [Not 24/7]
http://gohoski.fvds.ru:3000/mediabay/162/index.m3u8 http://gohoski.fvds.ru:3000/mediabay/162/index.m3u8

View File

@ -1,5 +1,5 @@
#EXTM3U #EXTM3U
#EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="" group-title="Undefined",Zoo (720p) #EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="https://i.imgur.com/ciTJrnl.png" group-title="Undefined",Zoo (720p)
https://iptv-all.lanesh4d0w.repl.co/andorra/zoo https://iptv-all.lanesh4d0w.repl.co/andorra/zoo
#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="https://i.imgur.com/BnhTn8i.png" group-title="Undefined",ATV #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 https://iptv-all.lanesh4d0w.repl.co/andorra/atv

View File

@ -1,3 +1,5 @@
#EXTM3U #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="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia #EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia
http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8 http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8

View File

@ -1,3 +1,5 @@
#EXTM3U #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="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia #EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia
http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8 http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8

View File

@ -1,3 +1,5 @@
#EXTM3U #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="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia #EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia
http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8 http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8

View File

@ -1,3 +1,5 @@
#EXTM3U #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="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia #EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia
http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8 http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8

View File

@ -11,5 +11,5 @@ http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index2.m3u8|Referer="https://refe
https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd
#EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Daawah TV #EXTINF:-1 tvg-id="" tvg-logo="" group-title="Undefined",Daawah TV
http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8 http://51.15.246.58:8081/daawahtv/daawahtv2/playlist.m3u8
#EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="" group-title="Undefined",Zoo (720p) #EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="https://i.imgur.com/ciTJrnl.png" group-title="Undefined",Zoo (720p)
https://iptv-all.lanesh4d0w.repl.co/andorra/zoo https://iptv-all.lanesh4d0w.repl.co/andorra/zoo

View File

@ -3,5 +3,5 @@
https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd https://iptv-all.lanesh4d0w.repl.co/andorra/atv_hd
#EXTINF:-1 tvg-id="AndorraTV.ad@SD" tvg-logo="https://i.imgur.com/BnhTn8i.png" group-title="Undefined",ATV #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 https://iptv-all.lanesh4d0w.repl.co/andorra/atv
#EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="" group-title="Undefined",Zoo (720p) #EXTINF:-1 tvg-id="Zoo.ad@HD" tvg-logo="https://i.imgur.com/ciTJrnl.png" group-title="Undefined",Zoo (720p)
https://iptv-all.lanesh4d0w.repl.co/andorra/zoo https://iptv-all.lanesh4d0w.repl.co/andorra/zoo

View File

@ -1,3 +1,5 @@
#EXTM3U #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="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia #EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia
http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8 http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8

View File

@ -1,3 +1,5 @@
#EXTM3U #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="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia #EXTINF:-1 tvg-id="MeteoMedia.ca" tvg-logo="https://s1.twnmm.com/images/en_ca/mobile/logos/twn-mobile-logo.png" group-title="Weather",Meteomedia
http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8 http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8

View File

@ -1,88 +1,238 @@
{"type":"raw","filepath":"raw/ad.m3u","count":4} {"type":"raw","filepath":"raw/ad.m3u","count":4}
{"type":"raw","filepath":"raw/ca.m3u","count":1} {"type":"raw","filepath":"raw/ca.m3u","count":2}
{"type":"raw","filepath":"raw/in.m3u","count":1} {"type":"raw","filepath":"raw/in.m3u","count":1}
{"type":"raw","filepath":"raw/kg.m3u","count":1} {"type":"raw","filepath":"raw/kg.m3u","count":1}
{"type":"raw","filepath":"raw/uk.m3u","count":1} {"type":"raw","filepath":"raw/uk.m3u","count":1}
{"type":"raw","filepath":"raw/unsorted.m3u","count":4} {"type":"raw","filepath":"raw/unsorted.m3u","count":4}
{"type":"source","filepath":"sources/ad.m3u","count":3}
{"type":"source","filepath":"sources/ca.m3u","count":1}
{"type":"source","filepath":"sources/in.m3u","count":1}
{"type":"source","filepath":"sources/kg.m3u","count":1}
{"type":"source","filepath":"sources/uk.m3u","count":1}
{"type":"source","filepath":"sources/unsorted.m3u","count":4}
{"type":"category","filepath":"categories/auto.m3u","count":0} {"type":"category","filepath":"categories/auto.m3u","count":0}
{"type":"category","filepath":"categories/cooking.m3u","count":0}
{"type":"category","filepath":"categories/comedy.m3u","count":0} {"type":"category","filepath":"categories/comedy.m3u","count":0}
{"type":"category","filepath":"categories/documentary.m3u","count":0}
{"type":"category","filepath":"categories/business.m3u","count":0} {"type":"category","filepath":"categories/business.m3u","count":0}
{"type":"category","filepath":"categories/classic.m3u","count":0} {"type":"category","filepath":"categories/cooking.m3u","count":0}
{"type":"category","filepath":"categories/entertainment.m3u","count":0}
{"type":"category","filepath":"categories/education.m3u","count":0}
{"type":"category","filepath":"categories/animation.m3u","count":0} {"type":"category","filepath":"categories/animation.m3u","count":0}
{"type":"category","filepath":"categories/classic.m3u","count":0}
{"type":"category","filepath":"categories/documentary.m3u","count":0}
{"type":"category","filepath":"categories/family.m3u","count":0} {"type":"category","filepath":"categories/family.m3u","count":0}
{"type":"category","filepath":"categories/kids.m3u","count":0}
{"type":"category","filepath":"categories/culture.m3u","count":0} {"type":"category","filepath":"categories/culture.m3u","count":0}
{"type":"category","filepath":"categories/lifestyle.m3u","count":0} {"type":"category","filepath":"categories/education.m3u","count":0}
{"type":"category","filepath":"categories/general.m3u","count":3} {"type":"category","filepath":"categories/general.m3u","count":3}
{"type":"category","filepath":"categories/outdoor.m3u","count":0} {"type":"category","filepath":"categories/kids.m3u","count":0}
{"type":"category","filepath":"categories/music.m3u","count":0} {"type":"category","filepath":"categories/lifestyle.m3u","count":0}
{"type":"category","filepath":"categories/legislative.m3u","count":0}
{"type":"category","filepath":"categories/series.m3u","count":0}
{"type":"category","filepath":"categories/news.m3u","count":1}
{"type":"category","filepath":"categories/movies.m3u","count":0} {"type":"category","filepath":"categories/movies.m3u","count":0}
{"type":"category","filepath":"categories/relax.m3u","count":0} {"type":"category","filepath":"categories/music.m3u","count":0}
{"type":"category","filepath":"categories/religious.m3u","count":0} {"type":"category","filepath":"categories/outdoor.m3u","count":0}
{"type":"category","filepath":"categories/weather.m3u","count":1}
{"type":"category","filepath":"categories/science.m3u","count":0} {"type":"category","filepath":"categories/science.m3u","count":0}
{"type":"category","filepath":"categories/shop.m3u","count":0} {"type":"category","filepath":"categories/news.m3u","count":1}
{"type":"category","filepath":"categories/xxx.m3u","count":1} {"type":"category","filepath":"categories/religious.m3u","count":0}
{"type":"category","filepath":"categories/series.m3u","count":0}
{"type":"category","filepath":"categories/relax.m3u","count":0}
{"type":"category","filepath":"categories/sports.m3u","count":0} {"type":"category","filepath":"categories/sports.m3u","count":0}
{"type":"category","filepath":"categories/undefined.m3u","count":7}
{"type":"category","filepath":"categories/shop.m3u","count":0}
{"type":"category","filepath":"categories/entertainment.m3u","count":0}
{"type":"category","filepath":"categories/travel.m3u","count":0} {"type":"category","filepath":"categories/travel.m3u","count":0}
{"type":"category","filepath":"categories/undefined.m3u","count":6} {"type":"category","filepath":"categories/xxx.m3u","count":1}
{"type":"country","filepath":"countries/ad.m3u","count":1} {"type":"category","filepath":"categories/legislative.m3u","count":0}
{"type":"country","filepath":"countries/ca.m3u","count":1} {"type":"category","filepath":"categories/weather.m3u","count":1}
{"type":"country","filepath":"countries/kg.m3u","count":1}
{"type":"country","filepath":"countries/kz.m3u","count":1}
{"type":"country","filepath":"countries/tj.m3u","count":1}
{"type":"country","filepath":"countries/ru.m3u","count":1}
{"type":"country","filepath":"countries/tm.m3u","count":1}
{"type":"country","filepath":"countries/undefined.m3u","count":4}
{"type":"country","filepath":"countries/uz.m3u","count":1}
{"type":"language","filepath":"languages/cat.m3u","count":1} {"type":"language","filepath":"languages/cat.m3u","count":1}
{"type":"language","filepath":"languages/rus.m3u","count":1} {"type":"language","filepath":"languages/rus.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ca-on.m3u","count":1}
{"type":"language","filepath":"languages/undefined.m3u","count":7}
{"type":"language","filepath":"languages/eng.m3u","count":1} {"type":"language","filepath":"languages/eng.m3u","count":1}
{"type":"language","filepath":"languages/undefined.m3u","count":8}
{"type":"country","filepath":"countries/ca.m3u","count":2}
{"type":"country","filepath":"countries/ad.m3u","count":1}
{"type":"country","filepath":"countries/ru.m3u","count":1}
{"type":"country","filepath":"countries/uz.m3u","count":1}
{"type":"country","filepath":"countries/kz.m3u","count":1}
{"type":"country","filepath":"countries/tj.m3u","count":1}
{"type":"country","filepath":"countries/tm.m3u","count":1}
{"type":"country","filepath":"countries/undefined.m3u","count":4}
{"type":"country","filepath":"countries/kg.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ad-07.m3u","count":1}
{"type":"region","filepath":"regions/afr.m3u","count":0} {"type":"region","filepath":"regions/afr.m3u","count":0}
{"type":"subdivision","filepath":"subdivisions/ad-02.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ad-04.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ad-08.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ad-03.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ca-ab.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ad-05.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ca-bc.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ca-nl.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ad-06.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ca-mb.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ca-nb.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ca-nt.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ca-nu.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ca-on.m3u","count":2}
{"type":"subdivision","filepath":"subdivisions/ca-ns.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ca-pe.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ca-qc.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ca-sk.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kg-j.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kg-b.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kg-t.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ca-yt.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kg-c.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kg-gb.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kg-n.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kg-y.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kz-ala.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kg-go.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kz-alm.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kz-akm.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kz-zap.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kz-aty.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kz-man.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kz-yuz.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kz-kus.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kz-akt.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kz-kar.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kz-kzy.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kz-ast.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kz-shy.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kz-pav.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kz-vos.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-ad.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kz-sev.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-al.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-ba.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-da.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-bu.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-in.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-kk.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-kl.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/kz-zha.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-me.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-se.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-mo.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-ko.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-ty.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-kr.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-alt.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-amu.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-bel.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-ast.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-sa.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-che.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-cu.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-ta.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-ce.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-bry.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-irk.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-chu.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-kb.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-iva.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-klu.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-kc.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-ark.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-kda.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-kha.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-kgd.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-kya.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-khm.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-kgn.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-kem.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-kir.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-mag.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-kam.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-krs.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-len.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-kos.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-lip.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-mur.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-mow.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-niz.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-nen.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-ngr.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-ore.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-oms.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-orl.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-nvs.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-mos.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-psk.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-ros.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-per.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-pnz.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-pri.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-sam.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-sak.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-rya.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-spe.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-sar.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-tam.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-tom.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-smo.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-sta.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-tul.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-sve.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-vla.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-tve.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-ud.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-vgg.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-vor.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-uly.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-zab.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-vlg.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-tyu.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/tj-du.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-yan.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/tj-kt.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/tj-gb.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/tj-ra.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/tm-a.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-yev.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ru-yar.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/tj-su.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/tm-b.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/tm-l.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/tm-m.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/tm-d.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/uz-ji.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/uz-nw.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/uz-bu.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/uz-qr.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/uz-an.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/uz-fa.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/uz-qa.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/uz-si.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/uz-sa.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/uz-tk.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/uz-ng.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/uz-xo.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/uz-su.m3u","count":1}
{"type":"region","filepath":"regions/apac.m3u","count":0} {"type":"region","filepath":"regions/apac.m3u","count":0}
{"type":"region","filepath":"regions/amer.m3u","count":1} {"type":"region","filepath":"regions/amer.m3u","count":2}
{"type":"region","filepath":"regions/arab.m3u","count":0}
{"type":"region","filepath":"regions/asean.m3u","count":0} {"type":"region","filepath":"regions/asean.m3u","count":0}
{"type":"region","filepath":"regions/cenamer.m3u","count":0} {"type":"region","filepath":"regions/arab.m3u","count":0}
{"type":"region","filepath":"regions/asia.m3u","count":2}
{"type":"region","filepath":"regions/carib.m3u","count":0} {"type":"region","filepath":"regions/carib.m3u","count":0}
{"type":"region","filepath":"regions/cis.m3u","count":2} {"type":"region","filepath":"regions/cis.m3u","count":2}
{"type":"region","filepath":"regions/hispam.m3u","count":0}
{"type":"region","filepath":"regions/emea.m3u","count":3}
{"type":"region","filepath":"regions/lac.m3u","count":0}
{"type":"region","filepath":"regions/cas.m3u","count":1} {"type":"region","filepath":"regions/cas.m3u","count":1}
{"type":"region","filepath":"regions/latam.m3u","count":0} {"type":"region","filepath":"regions/cenamer.m3u","count":0}
{"type":"region","filepath":"regions/lac.m3u","count":0}
{"type":"region","filepath":"regions/emea.m3u","count":3}
{"type":"region","filepath":"regions/eur.m3u","count":3} {"type":"region","filepath":"regions/eur.m3u","count":3}
{"type":"region","filepath":"regions/nam.m3u","count":1}
{"type":"region","filepath":"regions/mena.m3u","count":0} {"type":"region","filepath":"regions/mena.m3u","count":0}
{"type":"region","filepath":"regions/noram.m3u","count":1} {"type":"region","filepath":"regions/hispam.m3u","count":0}
{"type":"region","filepath":"regions/mideast.m3u","count":0} {"type":"region","filepath":"regions/latam.m3u","count":0}
{"type":"region","filepath":"regions/maghreb.m3u","count":0} {"type":"region","filepath":"regions/maghreb.m3u","count":0}
{"type":"region","filepath":"regions/ssa.m3u","count":0} {"type":"region","filepath":"regions/asia.m3u","count":2}
{"type":"region","filepath":"regions/nord.m3u","count":0}
{"type":"region","filepath":"regions/oce.m3u","count":0} {"type":"region","filepath":"regions/oce.m3u","count":0}
{"type":"region","filepath":"regions/southam.m3u","count":0} {"type":"region","filepath":"regions/noram.m3u","count":2}
{"type":"region","filepath":"regions/wafr.m3u","count":0} {"type":"region","filepath":"regions/nord.m3u","count":0}
{"type":"region","filepath":"regions/nam.m3u","count":2}
{"type":"region","filepath":"regions/int.m3u","count":2} {"type":"region","filepath":"regions/int.m3u","count":2}
{"type":"region","filepath":"regions/southam.m3u","count":0}
{"type":"region","filepath":"regions/mideast.m3u","count":0}
{"type":"region","filepath":"regions/wafr.m3u","count":0}
{"type":"region","filepath":"regions/sas.m3u","count":0} {"type":"region","filepath":"regions/sas.m3u","count":0}
{"type":"region","filepath":"regions/ssa.m3u","count":0}
{"type":"region","filepath":"regions/undefined.m3u","count":4} {"type":"region","filepath":"regions/undefined.m3u","count":4}
{"type":"index","filepath":"index.m3u","count":10} {"type":"source","filepath":"sources/in.m3u","count":1}
{"type":"index","filepath":"index.category.m3u","count":11} {"type":"source","filepath":"sources/unsorted.m3u","count":4}
{"type":"index","filepath":"index.country.m3u","count":14} {"type":"source","filepath":"sources/ca.m3u","count":2}
{"type":"index","filepath":"index.language.m3u","count":10} {"type":"source","filepath":"sources/ad.m3u","count":3}
{"type":"index","filepath":"index.region.m3u","count":20} {"type":"source","filepath":"sources/uk.m3u","count":1}
{"type":"source","filepath":"sources/kg.m3u","count":1}
{"type":"index","filepath":"index.m3u","count":11}
{"type":"index","filepath":"index.category.m3u","count":12}
{"type":"index","filepath":"index.country.m3u","count":15}
{"type":"index","filepath":"index.language.m3u","count":11}
{"type":"index","filepath":"index.region.m3u","count":23}

View File

@ -145,7 +145,6 @@ Same thing, but split up into separate files:
<tbody> <tbody>
<tr><td>🇨🇲 Cameroon</td><td align="right">1</td><td nowrap><code>https://iptv-org.github.io/iptv/countries/cm.m3u</code></td></tr> <tr><td>🇨🇲 Cameroon</td><td align="right">1</td><td nowrap><code>https://iptv-org.github.io/iptv/countries/cm.m3u</code></td></tr>
<tr><td>🇨🇦 Canada</td><td align="right">2</td><td nowrap><code>https://iptv-org.github.io/iptv/countries/ca.m3u</code></td></tr> <tr><td>🇨🇦 Canada</td><td align="right">2</td><td nowrap><code>https://iptv-org.github.io/iptv/countries/ca.m3u</code></td></tr>
<tr><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Ontario</td><td align="right">1</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/ca-on.m3u</code></td></tr>
<tr><td>🇨🇻 Cape Verde</td><td align="right">1</td><td nowrap><code>https://iptv-org.github.io/iptv/countries/cv.m3u</code></td></tr> <tr><td>🇨🇻 Cape Verde</td><td align="right">1</td><td nowrap><code>https://iptv-org.github.io/iptv/countries/cv.m3u</code></td></tr>
<tr><td>🇨🇬 Republic of the Congo</td><td align="right">1</td><td nowrap><code>https://iptv-org.github.io/iptv/countries/cg.m3u</code></td></tr> <tr><td>🇨🇬 Republic of the Congo</td><td align="right">1</td><td nowrap><code>https://iptv-org.github.io/iptv/countries/cg.m3u</code></td></tr>
<tr><td>🇷🇪 Réunion</td><td align="right">1</td><td nowrap><code>https://iptv-org.github.io/iptv/countries/re.m3u</code></td></tr> <tr><td>🇷🇪 Réunion</td><td align="right">1</td><td nowrap><code>https://iptv-org.github.io/iptv/countries/re.m3u</code></td></tr>
@ -161,6 +160,27 @@ Same thing, but split up into separate files:
</details> </details>
### Grouped by subdivision
<details>
<summary>Expand</summary>
<br>
<!-- prettier-ignore -->
<details>
<summary>Canada</summary>
<table>
<thead>
<tr><th align="left">Subdivision</th><th align="left">Channels</th><th align="left">Playlist</th></tr>
</thead>
<tbody>
<tr><td>Ontario</td><td align="right">1</td><td nowrap><code>https://iptv-org.github.io/iptv/subdivisions/ca-on.m3u</code></td></tr>
</tbody>
</table>
</details>
</details>
### Grouped by region ### Grouped by region
<details> <details>

View File

@ -669,5 +669,15 @@
"city": null, "city": null,
"categories": [], "categories": [],
"is_nsfw": false "is_nsfw": false
},
{
"id": "5AABTV.ca",
"name": "5AAB TV",
"network": null,
"country": "CA",
"subdivision": null,
"city": null,
"categories": [],
"is_nsfw": false
} }
] ]

View File

@ -826,5 +826,19 @@
], ],
"languages": [], "languages": [],
"video_format": "576i" "video_format": "576i"
},
{
"channel": "5AABTV.ca",
"id": "SD",
"name": "SD",
"is_main": true,
"broadcast_area": [
"c/CA"
],
"timezones": [
"Asia/Bishkek"
],
"languages": [],
"video_format": "576i"
} }
] ]

View File

@ -7,5 +7,6 @@
{"channel":"VisitXTV.nl","feed":null,"tags":[],"width":512,"height":512,"format":"JPEG","url":"https://i.imgur.com/RJ9wbNF.jpg"}, {"channel":"VisitXTV.nl","feed":null,"tags":[],"width":512,"height":512,"format":"JPEG","url":"https://i.imgur.com/RJ9wbNF.jpg"},
{"channel":"AndorraTV.ad","feed":"SD","tags":[],"width":512,"height":512,"format":"PNG","url":"https://i.imgur.com/BnhTn8i.png"}, {"channel":"AndorraTV.ad","feed":"SD","tags":[],"width":512,"height":512,"format":"PNG","url":"https://i.imgur.com/BnhTn8i.png"},
{"channel":"AndorraTV.ad","feed":null,"tags":[],"width":1000,"height":1000,"format":"JPEG","url":"https://i.imgur.com/AnhTn8i.png"}, {"channel":"AndorraTV.ad","feed":null,"tags":[],"width":1000,"height":1000,"format":"JPEG","url":"https://i.imgur.com/AnhTn8i.png"},
{"channel":"AndorraTV.ad","feed":null,"tags":[],"width":512,"height":512,"format":"PNG","url":"https://i.imgur.com/CnhTn8i.png"} {"channel":"AndorraTV.ad","feed":null,"tags":[],"width":512,"height":512,"format":"PNG","url":"https://i.imgur.com/CnhTn8i.png"},
{"channel":"Zoo.ad","feed":null,"tags":[],"width":512,"height":512,"format":"PNG","url":"https://i.imgur.com/ciTJrnl.png"}
] ]

View File

@ -1682,7 +1682,7 @@ module.exports = [
closed_at: null, closed_at: null,
author_association: 'COLLABORATOR', author_association: 'COLLABORATOR',
active_lock_reason: null, active_lock_reason: null,
body: '### Stream URL\n\nhttps://livestream.telvue.com/templeuni1/f7b44cfafd5c52223d5498196c8a2e7b.sdp/playlist.m3u8\n\n### Stream ID\n\nboo.us\n\n### Channel Name\n\nBBC America\n\n### Quality\n\n720p\n\n### Label\n\nGeo-blocked\n\n### HTTP User-Agent\n\nMozilla/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\n\n### HTTP Referrer\n\n_No response_\n\n### Notes\n\n_No response_\n\n### Contributing Guide\n\n- [X] I have read [Contributing Guide](https://github.com/iptv-org/iptv/blob/master/CONTRIBUTING.md)', body: '### Stream URL\n\nhttps://livestream.telvue.com/templeuni1/f7b44cfafd5c52223d5498196c8a2e7b.sdp/playlist.m3u8\n\n### Stream ID\n\nboo.us\n\n### Quality\n\n720p\n\n### Label\n\nGeo-blocked\n\n### HTTP User-Agent\n\nMozilla/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\n\n### HTTP Referrer\n\n_No response_\n\n### Notes\n\n_No response_\n\n### Contributing Guide\n\n- [X] I have read [Contributing Guide](https://github.com/iptv-org/iptv/blob/master/CONTRIBUTING.md)',
reactions: { reactions: {
url: 'https://api.github.com/repos/iptv-org/iptv/issues/14120/reactions', url: 'https://api.github.com/repos/iptv-org/iptv/issues/14120/reactions',
total_count: 0, total_count: 0,
@ -2284,5 +2284,114 @@ module.exports = [
timeline_url: 'https://api.github.com/repos/iptv-org/iptv/issues/20956/timeline', timeline_url: 'https://api.github.com/repos/iptv-org/iptv/issues/20956/timeline',
performed_via_github_app: null, performed_via_github_app: null,
state_reason: null state_reason: null
},
{
url: 'https://api.github.com/repos/iptv-org/iptv/issues/25157',
repository_url: 'https://api.github.com/repos/iptv-org/iptv',
labels_url: 'https://api.github.com/repos/iptv-org/iptv/issues/25157/labels{/name}',
comments_url: 'https://api.github.com/repos/iptv-org/iptv/issues/25157/comments',
events_url: 'https://api.github.com/repos/iptv-org/iptv/issues/25157/events',
html_url: 'https://github.com/iptv-org/iptv/issues/25157',
id: 3245640024,
node_id: 'I_kwDOCWUK8M7BdIlY',
number: 25157,
title: 'Add: OnTime Sports SD',
user: {
login: 'zezopm300',
id: 215159878,
node_id: 'U_kgDODNMURg',
avatar_url: 'https://avatars.githubusercontent.com/u/215159878?v=4',
gravatar_id: '',
url: 'https://api.github.com/users/zezopm300',
html_url: 'https://github.com/zezopm300',
followers_url: 'https://api.github.com/users/zezopm300/followers',
following_url: 'https://api.github.com/users/zezopm300/following{/other_user}',
gists_url: 'https://api.github.com/users/zezopm300/gists{/gist_id}',
starred_url: 'https://api.github.com/users/zezopm300/starred{/owner}{/repo}',
subscriptions_url: 'https://api.github.com/users/zezopm300/subscriptions',
organizations_url: 'https://api.github.com/users/zezopm300/orgs',
repos_url: 'https://api.github.com/users/zezopm300/repos',
events_url: 'https://api.github.com/users/zezopm300/events{/privacy}',
received_events_url: 'https://api.github.com/users/zezopm300/received_events',
type: 'User',
user_view_type: 'public',
site_admin: false
},
labels: [
{
id: 5923508587,
node_id: 'LA_kwDOCWUK8M8AAAABYRGRaw',
url: 'https://api.github.com/repos/iptv-org/iptv/labels/streams:add',
name: 'streams:add',
color: '017ff9',
default: false,
description: 'Request to add a new link to a playlist'
}
],
state: 'open',
locked: false,
assignee: null,
assignees: [],
milestone: null,
comments: 0,
created_at: '2025-07-19T20:44:05Z',
updated_at: '2025-07-19T20:44:05Z',
closed_at: null,
author_association: 'NONE',
type: null,
active_lock_reason: null,
sub_issues_summary: { total: 0, completed: 0, percent_completed: 0 },
body:
'### Stream ID (required)\n' +
'\n' +
'OnTimeSports.eg@SD\n' +
'\n' +
'### Stream URL (required)\n' +
'\n' +
' OnTime Sports SD.mu38\n' +
'\n' +
'### Quality\n' +
'\n' +
'None\n' +
'\n' +
'### Label\n' +
'\n' +
'None\n' +
'\n' +
'### HTTP User Agent\n' +
'\n' +
'_No response_\n' +
'\n' +
'### HTTP Referrer\n' +
'\n' +
'_No response_\n' +
'\n' +
'### Directives\n' +
'\n' +
'_No response_\n' +
'\n' +
'### Notes\n' +
'\n' +
'_No response_\n' +
'\n' +
'### Contributing Guide\n' +
'\n' +
'- [x] I have read [Contributing Guide](https://github.com/iptv-org/iptv/blob/master/CONTRIBUTING.md)',
closed_by: null,
reactions: {
url: 'https://api.github.com/repos/iptv-org/iptv/issues/25157/reactions',
total_count: 0,
'+1': 0,
'-1': 0,
laugh: 0,
hooray: 0,
confused: 0,
heart: 0,
rocket: 0,
eyes: 0
},
timeline_url: 'https://api.github.com/repos/iptv-org/iptv/issues/25157/timeline',
performed_via_github_app: null,
state_reason: null
} }
] ]

View File

@ -1,3 +1,5 @@
#EXTM3U #EXTM3U
#EXTINF:-1 tvg-id="5AABTV.ca",5AAB TV
http://158.69.124.9:1935/5aabtv/5aabtv/playlist.m3u8
#EXTINF:-1 tvg-id="MeteoMedia.ca",Meteomedia #EXTINF:-1 tvg-id="MeteoMedia.ca",Meteomedia
http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8 http://encodercdn1.frontline.ca/encoder181/output/Meteo_Media_720p/playlist.m3u8

View File

@ -96,6 +96,17 @@ Same thing, but split up into separate files:
</details> </details>
### Grouped by subdivision
<details>
<summary>Expand</summary>
<br>
<!-- prettier-ignore -->
#include "tests/__data__/output/.readme/_subdivisions.md"
</details>
### Grouped by region ### Grouped by region
<details> <details>

View File

@ -15,18 +15,20 @@ describe('report:create', () => {
expect( expect(
stdout.includes(` stdout.includes(`
(index) issueNumber type streamId streamUrl status (index) issueNumber type streamId streamUrl status
0 14120 'streams:edit' 'boo.us' 'https://livestream.telvue.com/templeuni1/f7b44cfafd5c52223d5498196c8a2e7b.sdp/playlist.m3u8' 'invalid_id' 0 14120 'streams:edit' 'boo.us' 'https://livestream.telvue.com/templeuni1/f7b44cfafd5c52223d5498196c8a2e7b.sdp/playlist.m3u8' 'invalid_id'
1 14135 'streams:add' 'BBCWorldNews.uk@SouthAsia' 'http://103.199.161.254/Content/bbcworld/Live/Channel%28BBCworld%29/Stream%2801%29/index.m3u8' 'wrong_id' 1 14135 'streams:add' 'BBCWorldNews.uk@SouthAsia' 'http://103.199.161.254/Content/bbcworld/Live/Channel%28BBCworld%29/Stream%2801%29/index.m3u8' 'wrong_id'
2 14177 'streams:add' 'TUTV.us' 'https://livestream.telvue.com/templeuni1/f7b44cfafd5c52223d5498196c8a2e7b.sdp/playlist.m3u8' 'on_playlist' 2 14177 'streams:add' 'TUTV.us' 'https://livestream.telvue.com/templeuni1/f7b44cfafd5c52223d5498196c8a2e7b.sdp/playlist.m3u8' 'on_playlist'
3 14178 'streams:add' 'TV3.my' 'https://live-streams-ssai-01.tonton.com.my/live/2dd2b7cd-1b34-4871-b669-57b5c9beca23/live.isml/.m3u8...' 'blocked' 3 14178 'streams:add' 'TV3.my' 'https://live-streams-ssai-01.tonton.com.my/live/2dd2b7cd-1b34-4871-b669-57b5c9beca23/live.isml/.m3u8...' 'blocked'
4 16120 'streams:remove' undefined 'http://190.61.102.67:2000/play/a038/index.m3u8' 'wrong_link' 4 14179 'streams:add' 'ManoramaNews.in' '(https://mitelefe.com/Api/Videos/GetSourceUrl/694564/0/HLS / https://ssl.cloud.telefe.com/Api/Videos...' 'invalid_link'
5 19956 'channel search' 'CNBCe.tr' undefined 'invalid_id' 5 16120 'streams:remove' undefined 'http://190.61.102.67:2000/play/a038/index.m3u8' 'wrong_link'
6 19957 'channel search' '13thStreet.au' undefined 'closed' 6 19956 'channel search' 'CNBCe.tr' undefined 'invalid_id'
7 20956 'channel search' 'IONTV.us' undefined 'fulfilled' 7 19957 'channel search' '13thStreet.au' undefined 'closed'
`) 8 20956 'channel search' 'IONTV.us' undefined 'fulfilled'
9 25157 'streams:add' 'OnTimeSports.eg@SD' 'OnTime Sports SD.mu38' 'invalid_link'
`)
).toBe(true) ).toBe(true)
}) })
}) })