Replace LF with CRLF

pull/28852/head
freearhey 1 month ago
parent bbf2dfa1f3
commit 8e166f97d5

@ -1,13 +1,13 @@
# Contributor Code of Conduct # Contributor Code of Conduct
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at https://www.contributor-covenant.org/version/1/0/0/code-of-conduct.html This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at https://www.contributor-covenant.org/version/1/0/0/code-of-conduct.html

@ -1,12 +1,12 @@
# These are supported funding model platforms # These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username patreon: # Replace with a single Patreon username
open_collective: iptv-org open_collective: iptv-org
ko_fi: # Replace with a single Ko-fi username ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

16
.gitignore vendored

@ -1,9 +1,9 @@
node_modules node_modules
.artifacts .artifacts
.secrets .secrets
.actrc .actrc
.DS_Store .DS_Store
/.gh-pages/ /.gh-pages/
/.api/ /.api/
.env .env
/temp/ /temp/

@ -1,4 +1,4 @@
_categories.md _categories.md
_countries.md _countries.md
_languages.md _languages.md
_regions.md _regions.md

@ -1,88 +1,88 @@
## Playlists ## Playlists
There are several versions of playlists that differ in the way they are grouped. As of January 30th, 2024, we have stopped distributing NSFW channels. For more information, please look at [this issue](https://github.com/iptv-org/iptv/issues/15723). There are several versions of playlists that differ in the way they are grouped. As of January 30th, 2024, we have stopped distributing NSFW channels. For more information, please look at [this issue](https://github.com/iptv-org/iptv/issues/15723).
### Grouped by category ### Grouped by category
Playlists in which channels are grouped by category. Playlists in which channels are grouped by category.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
<br> <br>
``` ```
https://iptv-org.github.io/iptv/index.category.m3u https://iptv-org.github.io/iptv/index.category.m3u
``` ```
Same thing, but split up into separate files: Same thing, but split up into separate files:
<!-- prettier-ignore --> <!-- prettier-ignore -->
#include "./.readme/_categories.md" #include "./.readme/_categories.md"
</details> </details>
### Grouped by language ### Grouped by language
Playlists in which channels are grouped by the language in which they are broadcast. Playlists in which channels are grouped by the language in which they are broadcast.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
<br> <br>
``` ```
https://iptv-org.github.io/iptv/index.language.m3u https://iptv-org.github.io/iptv/index.language.m3u
``` ```
Same thing, but split up into separate files: Same thing, but split up into separate files:
<!-- prettier-ignore --> <!-- prettier-ignore -->
#include "./.readme/_languages.md" #include "./.readme/_languages.md"
</details> </details>
### Grouped by broadcast area ### Grouped by broadcast area
Playlists in which channels are grouped by broadcast area. Playlists in which channels are grouped by broadcast area.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
#### Countries #### Countries
``` ```
https://iptv-org.github.io/iptv/index.country.m3u https://iptv-org.github.io/iptv/index.country.m3u
``` ```
Same thing, but split up into separate files: Same thing, but split up into separate files:
<!-- prettier-ignore --> <!-- prettier-ignore -->
#include "./.readme/_countries.md" #include "./.readme/_countries.md"
#### Regions #### Regions
<!-- prettier-ignore --> <!-- prettier-ignore -->
#include "./.readme/_regions.md" #include "./.readme/_regions.md"
</details> </details>
### Grouped by sources ### Grouped by sources
Playlists in which channels are grouped by broadcast source. Playlists in which channels are grouped by broadcast source.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
<br> <br>
To use the playlist, simply replace `<FILENAME>` in the link below with the name of one of the files in the [streams](streams) folder. To use the playlist, simply replace `<FILENAME>` in the link below with the name of one of the files in the [streams](streams) folder.
``` ```
https://iptv-org.github.io/iptv/sources/<FILENAME>.m3u https://iptv-org.github.io/iptv/sources/<FILENAME>.m3u
``` ```
</details> </details>
Also, any of our internal playlists are available in raw form (without any filtering or sorting) at this link: Also, any of our internal playlists are available in raw form (without any filtering or sorting) at this link:
``` ```
https://iptv-org.github.io/iptv/raw/<FILENAME>.m3u https://iptv-org.github.io/iptv/raw/<FILENAME>.m3u
``` ```

@ -1,215 +1,215 @@
# Contributing Guide # Contributing Guide
- [How to?](#how-to) - [How to?](#how-to)
- [Stream Description Scheme](#stream-description-scheme) - [Stream Description Scheme](#stream-description-scheme)
- [Project Structure](#project-structure) - [Project Structure](#project-structure)
- [Scripts](#scripts) - [Scripts](#scripts)
- [Workflows](#workflows) - [Workflows](#workflows)
## How to? ## How to?
### How to add a new stream link to a playlists? ### How to add a new stream link to a playlists?
You have several options: You have several options:
1. Create a new request using this [form](https://github.com/iptv-org/iptv/issues/new?assignees=&labels=streams:add&projects=&template=1_streams_add.yml&title=Add%3A+) and if approved, the link will automatically be added to the playlist on the next update. 1. Create a new request using this [form](https://github.com/iptv-org/iptv/issues/new?assignees=&labels=streams:add&projects=&template=1_streams_add.yml&title=Add%3A+) and if approved, the link will automatically be added to the playlist on the next update.
2. Add the link to the playlist directly using a [pull request](https://github.com/iptv-org/iptv/pulls). 2. Add the link to the playlist directly using a [pull request](https://github.com/iptv-org/iptv/pulls).
Regardless of which option you choose, before posting your request please do the following: Regardless of which option you choose, before posting your request please do the following:
- Make sure the link you want to add works stably. To check this, open it in one of the players (for example, [VLC player](https://www.videolan.org/vlc/index.html)) and watch the broadcast for at least a minute (some test streams are interrupted after 15-30 seconds). - Make sure the link you want to add works stably. To check this, open it in one of the players (for example, [VLC player](https://www.videolan.org/vlc/index.html)) and watch the broadcast for at least a minute (some test streams are interrupted after 15-30 seconds).
- Make sure the link is not already in the playlist. This can be done by [searching](https://github.com/search?q=repo%3Aiptv-org%2Fiptv+http%3A%2F%2Fexample.com&type=code) the repository. - Make sure the link is not already in the playlist. This can be done by [searching](https://github.com/search?q=repo%3Aiptv-org%2Fiptv+http%3A%2F%2Fexample.com&type=code) the repository.
- Find the ID of the channel you want on [iptv-org.github.io](https://iptv-org.github.io/). If your desired channel is not on the list you can leave a request to add it [here](https://github.com/iptv-org/database/issues/new/choose). - Find the ID of the channel you want on [iptv-org.github.io](https://iptv-org.github.io/). If your desired channel is not on the list you can leave a request to add it [here](https://github.com/iptv-org/database/issues/new/choose).
- Make sure the channel is not blocklisted. It can also be done through [iptv-org.github.io](https://iptv-org.github.io/). - Make sure the channel is not blocklisted. It can also be done through [iptv-org.github.io](https://iptv-org.github.io/).
- The link does not lead to the Xtream Codes server. [Why don't you accept links to Xtream Codes server?](FAQ.md#why-dont-you-accept-links-to-xtream-codes-server) - The link does not lead to the Xtream Codes server. [Why don't you accept links to Xtream Codes server?](FAQ.md#why-dont-you-accept-links-to-xtream-codes-server)
- If you know that the broadcast only works in certain countries or it is periodically interrupted, do not forget to indicate this in the request. - If you know that the broadcast only works in certain countries or it is periodically interrupted, do not forget to indicate this in the request.
A requests without a valid stream ID or working link to the stream will be closed immediately. A requests without a valid stream ID or working link to the stream will be closed immediately.
Note all links in playlists are sorted automatically by scripts so there is no need to sort them manually. For more info, see [Scripts](#scripts). Note all links in playlists are sorted automatically by scripts so there is no need to sort them manually. For more info, see [Scripts](#scripts).
### How to fix the stream description? ### How to fix the stream description?
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. 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+).
If, however, you have found an error in the database itself, this is the place to go: [How to edit channel description?](https://github.com/iptv-org/database/blob/master/CONTRIBUTING.md#how-to-edit-channel-description) If, however, you have found an error in the database itself, this is the place to go: [How to edit channel description?](https://github.com/iptv-org/database/blob/master/CONTRIBUTING.md#how-to-edit-channel-description)
### How to distinguish a link to an Xtream Codes server from a regular one? ### How to distinguish a link to an Xtream Codes server from a regular one?
Most of them have this form: Most of them have this form:
`http(s)://{hostname}:{port}/{username}/{password}/{channelID}` (port is often `25461`) `http(s)://{hostname}:{port}/{username}/{password}/{channelID}` (port is often `25461`)
To make sure that the link leads to the Xtream Codes server, copy the `hostname`, `port`, `username` and `password` into the link below and try to open it in a browser: To make sure that the link leads to the Xtream Codes server, copy the `hostname`, `port`, `username` and `password` into the link below and try to open it in a browser:
`http(s)://{hostname}:{port}/panel_api.php?username={username}&password={password}` `http(s)://{hostname}:{port}/panel_api.php?username={username}&password={password}`
If the link answers, you're with an Xtream Codes server. If the link answers, you're with an Xtream Codes server.
### How to report a broken stream? ### How to report a broken stream?
Fill out this [form](https://github.com/iptv-org/iptv/issues/new?assignees=&labels=streams:remove&projects=&template=3_streams_report.yml&title=Broken%3A+) and as soon as a working replacement appears, we will add it to the playlist or at least remove the non-working one. Fill out this [form](https://github.com/iptv-org/iptv/issues/new?assignees=&labels=streams:remove&projects=&template=3_streams_report.yml&title=Broken%3A+) and as soon as a working replacement appears, we will add it to the playlist or at least remove the non-working one.
The only thing before publishing your report is to make sure that: The only thing before publishing your report is to make sure that:
- The link is still in our playlists. You can verify this by [searching](https://github.com/search?q=repo%3Aiptv-org%2Fiptv+http%3A%2F%2Fexample.com&type=code) the repository. - The link is still in our playlists. You can verify this by [searching](https://github.com/search?q=repo%3Aiptv-org%2Fiptv+http%3A%2F%2Fexample.com&type=code) the repository.
- The link really doesn't work and is not just [geo-blocked](https://en.wikipedia.org/wiki/Geo-blocking). To check this, you can either use a [VPN](https://en.wikipedia.org/wiki/Virtual_private_network) or services such as [streamtest.in](https://streamtest.in/). - The link really doesn't work and is not just [geo-blocked](https://en.wikipedia.org/wiki/Geo-blocking). To check this, you can either use a [VPN](https://en.wikipedia.org/wiki/Virtual_private_network) or services such as [streamtest.in](https://streamtest.in/).
An issue without a valid link will be closed immediately. An issue without a valid link will be closed immediately.
### How to find a broken stream? ### How to find a broken stream?
For starters, you can just try to open the playlist in [VLC player](https://www.videolan.org/vlc/). The player outputs all errors to the log (Tools -> Messages) so you'll be able to determine pretty accurately why a link isn't working. For starters, you can just try to open the playlist in [VLC player](https://www.videolan.org/vlc/). The player outputs all errors to the log (Tools -> Messages) so you'll be able to determine pretty accurately why a link isn't working.
Another way to test links is to use the NPM script. To do this, first make sure you have [Node.js](https://nodejs.org/en) installed on your system. Then go to the `iptv` folder using [Console](https://en.wikipedia.org/wiki/Windows_Console) (or [Terminal](<https://en.wikipedia.org/wiki/Terminal_(macOS)>) if you have macOS) and run the command: Another way to test links is to use the NPM script. To do this, first make sure you have [Node.js](https://nodejs.org/en) installed on your system. Then go to the `iptv` folder using [Console](https://en.wikipedia.org/wiki/Windows_Console) (or [Terminal](<https://en.wikipedia.org/wiki/Terminal_(macOS)>) if you have macOS) and run the command:
```sh ```sh
npm run playlist:test path/to/playlist.m3u npm run playlist:test path/to/playlist.m3u
``` ```
This command will run an automatic check of all links in the playlist and display their status: This command will run an automatic check of all links in the playlist and display their status:
```sh ```sh
npm run playlist:test streams/fr.m3u npm run playlist:test streams/fr.m3u
streams/fr.m3u streams/fr.m3u
┌─────┬───────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────┬───────────────────────────┐ ┌─────┬───────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────┬───────────────────────────┐
│ │ tvg-id │ url │ status │ │ │ tvg-id │ url │ status │
├─────┼───────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────┤ ├─────┼───────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────┤
│ 0 │ 6ter.fr │ https://origin-caf900c010ea8046.live.6cloud.fr/out/v1/29c7a579af3348b48230f76cd75699a5/dash_short... │ LOADING... │ │ 0 │ 6ter.fr │ https://origin-caf900c010ea8046.live.6cloud.fr/out/v1/29c7a579af3348b48230f76cd75699a5/dash_short... │ LOADING... │
│ 1 │ 20MinutesTV.fr │ https://lives.digiteka.com/stream/86d3e867-a272-496b-8412-f59aa0104771/index.m3u8 │ FFMPEG_STREAMS_NOT_FOUND │ │ 1 │ 20MinutesTV.fr │ https://lives.digiteka.com/stream/86d3e867-a272-496b-8412-f59aa0104771/index.m3u8 │ FFMPEG_STREAMS_NOT_FOUND │
│ 2 │ │ https://video1.getstreamhosting.com:1936/8420/8420/playlist.m3u8 │ OK │ │ 2 │ │ https://video1.getstreamhosting.com:1936/8420/8420/playlist.m3u8 │ OK │
│ 3 │ ADNTVPlus.fr │ https://samsunguk-adn-samsung-fre-qfrlc.amagi.tv/playlist/samsunguk-adn-samsung-fre/playlist.m3u8 │ HTTP_FORBIDDEN │ │ 3 │ ADNTVPlus.fr │ https://samsunguk-adn-samsung-fre-qfrlc.amagi.tv/playlist/samsunguk-adn-samsung-fre/playlist.m3u8 │ HTTP_FORBIDDEN │
│ 4 │ Africa24.fr │ https://edge12.vedge.infomaniak.com/livecast/ik:africa24/manifest.m3u8 │ OK │ │ 4 │ Africa24.fr │ https://edge12.vedge.infomaniak.com/livecast/ik:africa24/manifest.m3u8 │ OK │
│ 5 │ Africa24English.fr │ https://edge17.vedge.infomaniak.com/livecast/ik:africa24sport/manifest.m3u8 │ OK │ │ 5 │ Africa24English.fr │ https://edge17.vedge.infomaniak.com/livecast/ik:africa24sport/manifest.m3u8 │ OK │
│ 6 │ AfricanewsEnglish.fr │ https://37c774660687468c821a51190046facf.mediatailor.us-east-1.amazonaws.com/v1/master/04fd913bb2... │ HTTP_GATEWAY_TIMEOUT │ │ 6 │ AfricanewsEnglish.fr │ https://37c774660687468c821a51190046facf.mediatailor.us-east-1.amazonaws.com/v1/master/04fd913bb2... │ HTTP_GATEWAY_TIMEOUT │
│ 7 │ AlpedHuezTV.fr │ https://edge.vedge.infomaniak.com/livecast/ik:adhtv/chunklist.m3u8 │ HTTP_NOT_FOUND │ │ 7 │ AlpedHuezTV.fr │ https://edge.vedge.infomaniak.com/livecast/ik:adhtv/chunklist.m3u8 │ HTTP_NOT_FOUND │
``` ```
After that, all you have to do is report any broken streams you find. After that, all you have to do is report any broken streams you find.
### How to replace a broken stream? ### How to replace a broken stream?
This can be done either by filling out this [form](https://github.com/iptv-org/iptv/issues/new?assignees=&labels=streams%3Aedit&projects=&template=2_streams_edit.yml&title=Edit%3A+). This can be done either by filling out this [form](https://github.com/iptv-org/iptv/issues/new?assignees=&labels=streams%3Aedit&projects=&template=2_streams_edit.yml&title=Edit%3A+).
Either by directly updating the files in the [/streams](/streams) folder and then creating a [pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests). Either by directly updating the files in the [/streams](/streams) folder and then creating a [pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests).
### How to remove my channel from playlist? ### How to remove my channel from playlist?
To request removal of a link to a channel from the repository, you need to fill out this [form](https://github.com/iptv-org/iptv/issues/new?assignees=&labels=removal+request&projects=&template=6_copyright-claim.yml&title=Remove%3A+) and wait for the request to be reviewed (this usually takes no more than 1 business day). And if the request is approved, links to the channel will be immediately removed from the repository. To request removal of a link to a channel from the repository, you need to fill out this [form](https://github.com/iptv-org/iptv/issues/new?assignees=&labels=removal+request&projects=&template=6_copyright-claim.yml&title=Remove%3A+) and wait for the request to be reviewed (this usually takes no more than 1 business day). And if the request is approved, links to the channel will be immediately removed from the repository.
The channel will also be added to our [blocklist](https://github.com/iptv-org/database/blob/master/data/blocklist.csv) to avoid its appearance in our playlists in the future. The channel will also be added to our [blocklist](https://github.com/iptv-org/database/blob/master/data/blocklist.csv) to avoid its appearance in our playlists in the future.
Please note that we only accept removal requests from channel owners and their official representatives, all other requests will be closed immediately. Please note that we only accept removal requests from channel owners and their official representatives, all other requests will be closed immediately.
## Stream Description Scheme ## Stream Description Scheme
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",STREAM_TITLE (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>` |
| `STREAM_TITLE` | Stream title consisting of channel name and feed name. 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 | - |
Example: Example:
```xml ```xml
#EXTINF:-1 tvg-id="ExampleTV.us@East",Example TV East (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
``` ```
Also, if necessary, you can specify custom [HTTP User-Agent](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent) and [HTTP Referrer](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer) through additional attributes: Also, if necessary, you can specify custom [HTTP User-Agent](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent) and [HTTP Referrer](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer) through additional attributes:
```xml ```xml
#EXTINF:-1 tvg-id="ExampleTV.us" http-referrer="http://example.com/" http-user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64)",Example TV #EXTINF:-1 tvg-id="ExampleTV.us" http-referrer="http://example.com/" http-user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64)",Example TV
http://example.com/stream.m3u8 http://example.com/stream.m3u8
``` ```
or use player-specific directives: or use player-specific directives:
_VLC_ _VLC_
```xml ```xml
#EXTINF:-1 tvg-id="ExampleTV.us@VLC",Example TV #EXTINF:-1 tvg-id="ExampleTV.us@VLC",Example TV
#EXTVLCOPT:http-referrer=http://example.com/ #EXTVLCOPT:http-referrer=http://example.com/
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) #EXTVLCOPT:http-user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64)
http://example.com/stream.m3u8 http://example.com/stream.m3u8
``` ```
_Kodi_ _Kodi_
```xml ```xml
#EXTINF:-1 tvg-id="ExampleTV.us@Kodi",Example TV #EXTINF:-1 tvg-id="ExampleTV.us@Kodi",Example TV
#KODIPROP:inputstream=inputstream.adaptive #KODIPROP:inputstream=inputstream.adaptive
#KODIPROP:inputstream.adaptive.stream_headers=Referer=http://example.com/&amp;User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) #KODIPROP:inputstream.adaptive.stream_headers=Referer=http://example.com/&amp;User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64)
http://example.com/stream.m3u8 http://example.com/stream.m3u8
``` ```
## Project Structure ## Project Structure
- `.github/` - `.github/`
- `ISSUE_TEMPLATE/`: issue templates for the repository. - `ISSUE_TEMPLATE/`: issue templates for the repository.
- `workflows`: contains [GitHub actions](https://docs.github.com/en/actions/quickstart) workflows. - `workflows`: contains [GitHub actions](https://docs.github.com/en/actions/quickstart) workflows.
- `CODE_OF_CONDUCT.md`: rules you shouldn't break if you don't want to get banned. - `CODE_OF_CONDUCT.md`: rules you shouldn't break if you don't want to get banned.
- `.readme/` - `.readme/`
- `config.json`: config for the `markdown-include` package, which is used to compile everything into one `PLAYLISTS.md` file. - `config.json`: config for the `markdown-include` package, which is used to compile everything into one `PLAYLISTS.md` file.
- `preview.png`: image displayed in the `README.md`. - `preview.png`: image displayed in the `README.md`.
- `template.md`: template for `PLAYLISTS.md`. - `template.md`: template for `PLAYLISTS.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.
- `tests/`: contains tests to check the scripts. - `tests/`: contains tests to check the scripts.
- `CONTRIBUTING.md`: file you are currently reading. - `CONTRIBUTING.md`: file you are currently reading.
- `PLAYLISTS.md`: auto-updated list of available playlists. - `PLAYLISTS.md`: auto-updated list of available playlists.
- `README.md`: project description. - `README.md`: project description.
## Scripts ## Scripts
These scripts are created to automate routine processes in the repository and make it a bit easier to maintain. These scripts are created to automate routine processes in the repository and make it a bit easier to maintain.
For scripts to work, you must have [Node.js](https://nodejs.org/en) installed on your computer. For scripts to work, you must have [Node.js](https://nodejs.org/en) installed on your computer.
To run scripts use the `npm run <script-name>` command. To run scripts use the `npm run <script-name>` command.
- `act:check`: allows to run the [check](https://github.com/iptv-org/iptv/blob/master/.github/workflows/check.yml) workflow locally. Depends on [nektos/act](https://github.com/nektos/act). - `act:check`: allows to run the [check](https://github.com/iptv-org/iptv/blob/master/.github/workflows/check.yml) workflow locally. Depends on [nektos/act](https://github.com/nektos/act).
- `act:format`: allows to test the [format](https://github.com/iptv-org/iptv/blob/master/.github/workflows/update.yml) workflow locally. Depends on [nektos/act](https://github.com/nektos/act). - `act:format`: allows to test the [format](https://github.com/iptv-org/iptv/blob/master/.github/workflows/update.yml) workflow locally. Depends on [nektos/act](https://github.com/nektos/act).
- `act:update`: allows to test the [update](https://github.com/iptv-org/iptv/blob/master/.github/workflows/update.yml) workflow locally. Depends on [nektos/act](https://github.com/nektos/act). - `act:update`: allows to test the [update](https://github.com/iptv-org/iptv/blob/master/.github/workflows/update.yml) workflow locally. Depends on [nektos/act](https://github.com/nektos/act).
- `api:load`: downloads the latest channel and stream data from the [iptv-org/api](https://github.com/iptv-org/api). - `api:load`: downloads the latest channel and stream data from the [iptv-org/api](https://github.com/iptv-org/api).
- `api:generate`: generates a JSON file with all streams for the [iptv-org/api](https://github.com/iptv-org/api) repository. - `api:generate`: generates a JSON file with all streams for the [iptv-org/api](https://github.com/iptv-org/api) repository.
- `api:deploy`: allows to manually upload a JSON file created via `api:generate` to the [iptv-org/api](https://github.com/iptv-org/api) repository. To run the script you must provide your [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) with write access to the repository. - `api:deploy`: allows to manually upload a JSON file created via `api:generate` to the [iptv-org/api](https://github.com/iptv-org/api) repository. To run the script you must provide your [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) with write access to the repository.
- `playlist:format`: formats internal playlists. The process includes [URL normalization](https://en.wikipedia.org/wiki/URI_normalization), duplicate removal, removing invalid id's and sorting links by channel name, quality, and label. - `playlist:format`: formats internal playlists. The process includes [URL normalization](https://en.wikipedia.org/wiki/URI_normalization), duplicate removal, removing invalid id's and sorting links by channel name, quality, and label.
- `playlist:update`: triggers an update of internal playlists. The process involves processing approved requests from issues. - `playlist:update`: triggers an update of internal playlists. The process involves processing approved requests from issues.
- `playlist:generate`: generates all public playlists. - `playlist:generate`: generates all public playlists.
- `playlist:validate`: сhecks ids and links in internal playlists for errors. - `playlist:validate`: сhecks ids and links in internal playlists for errors.
- `playlist:lint`: сhecks internal playlists for syntax errors. - `playlist:lint`: сhecks internal playlists for syntax errors.
- `playlist:test`: tests links in internal playlists. - `playlist:test`: tests links in internal playlists.
- `playlist:edit`: utility for quick streams mapping. - `playlist:edit`: utility for quick streams mapping.
- `playlist:deploy`: allows to manually publish all generated via `playlist:generate` playlists. To run the script you must provide your [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) with write access to the repository. - `playlist:deploy`: allows to manually publish all generated via `playlist:generate` playlists. To run the script you must provide your [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) with write access to the repository.
- `readme:update`: updates the list of playlists in [README.md](README.md). - `readme:update`: updates the list of playlists in [README.md](README.md).
- `report:create`: creates a report on current issues. - `report:create`: creates a report on current issues.
- `check`: (shorthand) sequentially runs the `playlist:lint` and `playlist:validate` scripts. - `check`: (shorthand) sequentially runs the `playlist:lint` and `playlist:validate` scripts.
- `format`: (shorthand) runs the `playlist:format` script. - `format`: (shorthand) runs the `playlist:format` script.
- `update`: (shorthand) sequentially runs the `playlist:generate`, `api:generate` and `readme:update` scripts. - `update`: (shorthand) sequentially runs the `playlist:generate`, `api:generate` and `readme:update` scripts.
- `deploy`: (shorthand) sequentially runs the `playlist:deploy` and `api:deploy` scripts. - `deploy`: (shorthand) sequentially runs the `playlist:deploy` and `api:deploy` scripts.
- `lint`: сhecks the scripts for syntax errors. - `lint`: сhecks the scripts for syntax errors.
- `test`: runs a test of all the scripts described above. - `test`: runs a test of all the scripts described above.
## Workflows ## Workflows
To automate the run of the scripts described above, we use the [GitHub Actions workflows](https://docs.github.com/en/actions/using-workflows). To automate the run of the scripts described above, we use the [GitHub Actions workflows](https://docs.github.com/en/actions/using-workflows).
Each workflow includes its own set of scripts that can be run either manually or in response to an event. Each workflow includes its own set of scripts that can be run either manually or in response to an event.
- `check`: sequentially runs the `api:load`, `playlist:check` and `playlist:validate` scripts when a new pull request appears, and blocks the merge if it detects an error in it. - `check`: sequentially runs the `api:load`, `playlist:check` and `playlist:validate` scripts when a new pull request appears, and blocks the merge if it detects an error in it.
- `format`: sequentially runs `api:load`, `playlist:format`, `playlist:lint` and `playlist:validate` scripts. - `format`: sequentially runs `api:load`, `playlist:format`, `playlist:lint` and `playlist:validate` scripts.
- `update`: every day at 0:00 UTC sequentially runs `api:load`, `playlist:update`, `playlist:lint`, `playlist:validate`, `playlist:generate`, `api:generate` and `readme:update` scripts and deploys the output files if successful. - `update`: every day at 0:00 UTC sequentially runs `api:load`, `playlist:update`, `playlist:lint`, `playlist:validate`, `playlist:generate`, `api:generate` and `readme:update` scripts and deploys the output files if successful.

@ -1,23 +1,23 @@
# Frequently Asked Questions # Frequently Asked Questions
### My favorite channel is not on the playlist. ### My favorite channel is not on the playlist.
Start by asking our community for help via [Discussions](https://github.com/orgs/iptv-org/discussions). It is quite possible that someone already has a link to the channel you need and they just haven't added it to our playlist yet. Start by asking our community for help via [Discussions](https://github.com/orgs/iptv-org/discussions). It is quite possible that someone already has a link to the channel you need and they just haven't added it to our playlist yet.
But keep in mind that not all TV channels are available for viewing online, and in this case there is little we can do about it. But keep in mind that not all TV channels are available for viewing online, and in this case there is little we can do about it.
### Are you planning to include a Video On Demand (VOD) to the playlist? ### Are you planning to include a Video On Demand (VOD) to the playlist?
No. No.
### Why is the channel on the iptv-org.github.io but not in the playlist? ### Why is the channel on the iptv-org.github.io but not in the playlist?
The site contains a list of all TV channels in the world and only those of them for which we have working stream links are included in the playlists. The site contains a list of all TV channels in the world and only those of them for which we have working stream links are included in the playlists.
### Can I add a radio broadcast? ### Can I add a radio broadcast?
Yes, if it is a [visual radio](https://en.wikipedia.org/wiki/Visual_radio) in which a video and audio are shown at the same time. Yes, if it is a [visual radio](https://en.wikipedia.org/wiki/Visual_radio) in which a video and audio are shown at the same time.
### Why don't you accept links to Xtream Codes server? ### Why don't you accept links to Xtream Codes server?
Xtream Codes streams tend to be very unstable, and often links to them fail very quickly, so it's easier for us to initially exclude them from the playlist than to search for expired ones every day. Xtream Codes streams tend to be very unstable, and often links to them fail very quickly, so it's easier for us to initially exclude them from the playlist than to search for expired ones every day.

@ -1,24 +1,24 @@
This is free and unencumbered software released into the public domain. This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any binary, for any purpose, commercial or non-commercial, and by any
means. means.
In jurisdictions that recognize copyright laws, the author or authors In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this relinquishment in perpetuity of all present and future rights to this
software under copyright law. software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE. OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/> For more information, please refer to <http://unlicense.org/>

@ -1,22 +1,22 @@
## Playlists ## Playlists
There are several versions of playlists that differ in the way they are grouped. As of January 30th, 2024, we have stopped distributing NSFW channels. For more information, please look at [this issue](https://github.com/iptv-org/iptv/issues/15723). There are several versions of playlists that differ in the way they are grouped. As of January 30th, 2024, we have stopped distributing NSFW channels. For more information, please look at [this issue](https://github.com/iptv-org/iptv/issues/15723).
### Grouped by category ### Grouped by category
Playlists in which channels are grouped by category. Playlists in which channels are grouped by category.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
<br> <br>
``` ```
https://iptv-org.github.io/iptv/index.category.m3u https://iptv-org.github.io/iptv/index.category.m3u
``` ```
Same thing, but split up into separate files: Same thing, but split up into separate files:
<!-- prettier-ignore --> <!-- prettier-ignore -->
<table> <table>
<thead> <thead>
<tr><th align="left">Category</th><th align="left">Channels</th><th align="left">Playlist</th></tr> <tr><th align="left">Category</th><th align="left">Channels</th><th align="left">Playlist</th></tr>
@ -54,25 +54,25 @@ Same thing, but split up into separate files:
<tr><td>XXX</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/xxx.m3u</code></td></tr> <tr><td>XXX</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/xxx.m3u</code></td></tr>
<tr><td>Undefined</td><td align="right">3693</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/undefined.m3u</code></td></tr> <tr><td>Undefined</td><td align="right">3693</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/undefined.m3u</code></td></tr>
</tbody> </tbody>
</table> </table>
</details> </details>
### Grouped by language ### Grouped by language
Playlists in which channels are grouped by the language in which they are broadcast. Playlists in which channels are grouped by the language in which they are broadcast.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
<br> <br>
``` ```
https://iptv-org.github.io/iptv/index.language.m3u https://iptv-org.github.io/iptv/index.language.m3u
``` ```
Same thing, but split up into separate files: Same thing, but split up into separate files:
<!-- prettier-ignore --> <!-- prettier-ignore -->
<table> <table>
<thead> <thead>
<tr><th align="left">Language</th><th align="left">Channels</th><th align="left">Playlist</th></tr> <tr><th align="left">Language</th><th align="left">Channels</th><th align="left">Playlist</th></tr>
@ -288,26 +288,26 @@ Same thing, but split up into separate files:
<tr><td align="left">Zulu</td><td align="right">1</td><td align="left" nowrap><code>https://iptv-org.github.io/iptv/languages/zul.m3u</code></td></tr> <tr><td align="left">Zulu</td><td align="right">1</td><td align="left" nowrap><code>https://iptv-org.github.io/iptv/languages/zul.m3u</code></td></tr>
<tr><td align="left">Undefined</td><td align="right">2176</td><td align="left" nowrap><code>https://iptv-org.github.io/iptv/languages/undefined.m3u</code></td></tr> <tr><td align="left">Undefined</td><td align="right">2176</td><td align="left" nowrap><code>https://iptv-org.github.io/iptv/languages/undefined.m3u</code></td></tr>
</tbody> </tbody>
</table> </table>
</details> </details>
### Grouped by broadcast area ### Grouped by broadcast area
Playlists in which channels are grouped by broadcast area. Playlists in which channels are grouped by broadcast area.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
#### Countries #### Countries
``` ```
https://iptv-org.github.io/iptv/index.country.m3u https://iptv-org.github.io/iptv/index.country.m3u
``` ```
Same thing, but split up into separate files: Same thing, but split up into separate files:
<!-- prettier-ignore --> <!-- prettier-ignore -->
- 🇦🇫 Afghanistan <code>https://iptv-org.github.io/iptv/countries/af.m3u</code> - 🇦🇫 Afghanistan <code>https://iptv-org.github.io/iptv/countries/af.m3u</code>
- 🇦🇱 Albania <code>https://iptv-org.github.io/iptv/countries/al.m3u</code> - 🇦🇱 Albania <code>https://iptv-org.github.io/iptv/countries/al.m3u</code>
- 🇩🇿 Algeria <code>https://iptv-org.github.io/iptv/countries/dz.m3u</code> - 🇩🇿 Algeria <code>https://iptv-org.github.io/iptv/countries/dz.m3u</code>
@ -1283,11 +1283,11 @@ Same thing, but split up into separate files:
- 🇿🇲 Zambia <code>https://iptv-org.github.io/iptv/countries/zm.m3u</code> - 🇿🇲 Zambia <code>https://iptv-org.github.io/iptv/countries/zm.m3u</code>
- 🇿🇼 Zimbabwe <code>https://iptv-org.github.io/iptv/countries/zw.m3u</code> - 🇿🇼 Zimbabwe <code>https://iptv-org.github.io/iptv/countries/zw.m3u</code>
- 🌐 International <code>https://iptv-org.github.io/iptv/countries/int.m3u</code> - 🌐 International <code>https://iptv-org.github.io/iptv/countries/int.m3u</code>
- Undefined <code>https://iptv-org.github.io/iptv/countries/undefined.m3u</code> - Undefined <code>https://iptv-org.github.io/iptv/countries/undefined.m3u</code>
#### Regions #### Regions
<!-- prettier-ignore --> <!-- prettier-ignore -->
- Africa <code>https://iptv-org.github.io/iptv/regions/afr.m3u</code> - Africa <code>https://iptv-org.github.io/iptv/regions/afr.m3u</code>
- Americas <code>https://iptv-org.github.io/iptv/regions/amer.m3u</code> - Americas <code>https://iptv-org.github.io/iptv/regions/amer.m3u</code>
- Arab world <code>https://iptv-org.github.io/iptv/regions/arab.m3u</code> - Arab world <code>https://iptv-org.github.io/iptv/regions/arab.m3u</code>
@ -1329,28 +1329,28 @@ Same thing, but split up into separate files:
- West Africa <code>https://iptv-org.github.io/iptv/regions/waf.m3u</code> - West Africa <code>https://iptv-org.github.io/iptv/regions/waf.m3u</code>
- West Asia <code>https://iptv-org.github.io/iptv/regions/was.m3u</code> - West Asia <code>https://iptv-org.github.io/iptv/regions/was.m3u</code>
- Western Europe <code>https://iptv-org.github.io/iptv/regions/wer.m3u</code> - Western Europe <code>https://iptv-org.github.io/iptv/regions/wer.m3u</code>
- Worldwide <code>https://iptv-org.github.io/iptv/regions/ww.m3u</code> - Worldwide <code>https://iptv-org.github.io/iptv/regions/ww.m3u</code>
</details> </details>
### Grouped by sources ### Grouped by sources
Playlists in which channels are grouped by broadcast source. Playlists in which channels are grouped by broadcast source.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
<br> <br>
To use the playlist, simply replace `<FILENAME>` in the link below with the name of one of the files in the [streams](streams) folder. To use the playlist, simply replace `<FILENAME>` in the link below with the name of one of the files in the [streams](streams) folder.
``` ```
https://iptv-org.github.io/iptv/sources/<FILENAME>.m3u https://iptv-org.github.io/iptv/sources/<FILENAME>.m3u
``` ```
</details> </details>
Also, any of our internal playlists are available in raw form (without any filtering or sorting) at this link: Also, any of our internal playlists are available in raw form (without any filtering or sorting) at this link:
``` ```
https://iptv-org.github.io/iptv/raw/<FILENAME>.m3u https://iptv-org.github.io/iptv/raw/<FILENAME>.m3u
``` ```

@ -1,79 +1,79 @@
# IPTV [![update](https://github.com/iptv-org/iptv/actions/workflows/update.yml/badge.svg)](https://github.com/iptv-org/iptv/actions/workflows/update.yml) # IPTV [![update](https://github.com/iptv-org/iptv/actions/workflows/update.yml/badge.svg)](https://github.com/iptv-org/iptv/actions/workflows/update.yml)
Collection of publicly available IPTV (Internet Protocol television) channels from all over the world. Collection of publicly available IPTV (Internet Protocol television) channels from all over the world.
## Table of contents ## Table of contents
- 🚀 [How to use?](#how-to-use) - 🚀 [How to use?](#how-to-use)
- 📺 [Playlists](#playlists) - 📺 [Playlists](#playlists)
- 🗓 [EPG](#epg) - 🗓 [EPG](#epg)
- 🗄 [Database](#database) - 🗄 [Database](#database)
- 👨‍💻 [API](#api) - 👨‍💻 [API](#api)
- 📚 [Resources](#resources) - 📚 [Resources](#resources)
- 💬 [Discussions](#discussions) - 💬 [Discussions](#discussions)
- ❓ [FAQ](#faq) - ❓ [FAQ](#faq)
- 🛠 [Contribution](#contribution) - 🛠 [Contribution](#contribution)
- ⚖ [Legal](#legal) - ⚖ [Legal](#legal)
- © [License](#license) - © [License](#license)
## How to use? ## How to use?
Simply paste the link to one of the playlists into [any video player](https://github.com/iptv-org/awesome-iptv#apps) that supports live streaming and press _Open_. Simply paste the link to one of the playlists into [any video player](https://github.com/iptv-org/awesome-iptv#apps) that supports live streaming and press _Open_.
![VLC Network Panel](https://github.com/iptv-org/iptv/raw/master/.readme/preview.png) ![VLC Network Panel](https://github.com/iptv-org/iptv/raw/master/.readme/preview.png)
## Playlists ## Playlists
The main playlist containing all channels available in the repository can be found at: The main playlist containing all channels available in the repository can be found at:
``` ```
https://iptv-org.github.io/iptv/index.m3u https://iptv-org.github.io/iptv/index.m3u
``` ```
Links to other playlists can be found in the [PLAYLISTS.md](PLAYLISTS.md) file. Links to other playlists can be found in the [PLAYLISTS.md](PLAYLISTS.md) file.
## EPG ## EPG
[Electronic Program Guide](https://en.wikipedia.org/wiki/Electronic_program_guide) for most of the channels can be downloaded using utilities published in the [iptv-org/epg](https://github.com/iptv-org/epg) repository. [Electronic Program Guide](https://en.wikipedia.org/wiki/Electronic_program_guide) for most of the channels can be downloaded using utilities published in the [iptv-org/epg](https://github.com/iptv-org/epg) repository.
## Database ## Database
All channel data is taken from the [iptv-org/database](https://github.com/iptv-org/database) repository. If you find any errors please open a new [issue](https://github.com/iptv-org/database/issues) there. All channel data is taken from the [iptv-org/database](https://github.com/iptv-org/database) repository. If you find any errors please open a new [issue](https://github.com/iptv-org/database/issues) there.
## API ## API
The API documentation can be found in the [iptv-org/api](https://github.com/iptv-org/api) repository. The API documentation can be found in the [iptv-org/api](https://github.com/iptv-org/api) repository.
## Resources ## Resources
Links to other useful IPTV-related resources can be found in the [iptv-org/awesome-iptv](https://github.com/iptv-org/awesome-iptv) repository. Links to other useful IPTV-related resources can be found in the [iptv-org/awesome-iptv](https://github.com/iptv-org/awesome-iptv) repository.
## Discussions ## Discussions
If you need help finding a channel, have a question or idea, welcome to the [Discussions](https://github.com/orgs/iptv-org/discussions). If you need help finding a channel, have a question or idea, welcome to the [Discussions](https://github.com/orgs/iptv-org/discussions).
## FAQ ## FAQ
The answers to the most popular questions can be found in the [FAQ.md](FAQ.md) file. The answers to the most popular questions can be found in the [FAQ.md](FAQ.md) file.
## Contribution ## Contribution
Please make sure to read the [Contributing Guide](CONTRIBUTING.md) before sending an issue or making a pull request. Please make sure to read the [Contributing Guide](CONTRIBUTING.md) before sending an issue or making a pull request.
And thank you to everyone who has already contributed! And thank you to everyone who has already contributed!
### Backers ### Backers
<a href="https://opencollective.com/iptv-org"><img src="https://opencollective.com/iptv-org/backers.svg?width=890" /></a> <a href="https://opencollective.com/iptv-org"><img src="https://opencollective.com/iptv-org/backers.svg?width=890" /></a>
### Contributors ### Contributors
<a href="https://github.com/iptv-org/iptv/graphs/contributors"><img src="https://opencollective.com/iptv-org/contributors.svg?width=890" /></a> <a href="https://github.com/iptv-org/iptv/graphs/contributors"><img src="https://opencollective.com/iptv-org/contributors.svg?width=890" /></a>
## Legal ## Legal
No video files are stored in this repository. The repository simply contains user-submitted links to publicly available video stream URLs, which to the best of our knowledge have been intentionally made publicly by the copyright holders. If any links in these playlists infringe on your rights as a copyright holder, they may be removed by sending a [pull request](https://github.com/iptv-org/iptv/pulls) or opening an [issue](https://github.com/iptv-org/iptv/issues/new?assignees=freearhey&labels=removal+request&template=--removal-request.yml&title=Remove%3A+). However, note that we have **no control** over the destination of the link, and just removing the link from the playlist will not remove its contents from the web. Note that linking does not directly infringe copyright because no copy is made on the site providing the link, and thus this is **not** a valid reason to send a DMCA notice to GitHub. To remove this content from the web, you should contact the web host that's actually hosting the content (**not** GitHub, nor the maintainers of this repository). No video files are stored in this repository. The repository simply contains user-submitted links to publicly available video stream URLs, which to the best of our knowledge have been intentionally made publicly by the copyright holders. If any links in these playlists infringe on your rights as a copyright holder, they may be removed by sending a [pull request](https://github.com/iptv-org/iptv/pulls) or opening an [issue](https://github.com/iptv-org/iptv/issues/new?assignees=freearhey&labels=removal+request&template=--removal-request.yml&title=Remove%3A+). However, note that we have **no control** over the destination of the link, and just removing the link from the playlist will not remove its contents from the web. Note that linking does not directly infringe copyright because no copy is made on the site providing the link, and thus this is **not** a valid reason to send a DMCA notice to GitHub. To remove this content from the web, you should contact the web host that's actually hosting the content (**not** GitHub, nor the maintainers of this repository).
## License ## License
[![CC0](http://mirrors.creativecommons.org/presskit/buttons/88x31/svg/cc-zero.svg)](LICENSE) [![CC0](http://mirrors.creativecommons.org/presskit/buttons/88x31/svg/cc-zero.svg)](LICENSE)

@ -1,17 +1,17 @@
{ {
"files": ["streams/*.m3u"], "files": ["streams/*.m3u"],
"rules": { "rules": {
"no-empty-lines": true, "no-empty-lines": true,
"require-header": true, "require-header": true,
"attribute-quotes": true, "attribute-quotes": true,
"require-info": true, "require-info": true,
"require-title": true, "require-title": true,
"no-trailing-spaces": false, "no-trailing-spaces": false,
"no-whitespace-before-title": true, "no-whitespace-before-title": true,
"no-multi-spaces": true, "no-multi-spaces": true,
"no-extra-comma": true, "no-extra-comma": true,
"space-before-paren": true, "space-before-paren": true,
"no-dash": true, "no-dash": true,
"require-link": true "require-link": true
} }
} }

@ -1,11 +1,11 @@
export const ROOT_DIR = process.env.ROOT_DIR || './' export const ROOT_DIR = process.env.ROOT_DIR || './'
export const STREAMS_DIR = process.env.STREAMS_DIR || './streams' export const STREAMS_DIR = process.env.STREAMS_DIR || './streams'
export const PUBLIC_DIR = process.env.PUBLIC_DIR || './.gh-pages' export const PUBLIC_DIR = process.env.PUBLIC_DIR || './.gh-pages'
export const README_DIR = process.env.README_DIR || './.readme' export const README_DIR = process.env.README_DIR || './.readme'
export const API_DIR = process.env.API_DIR || './.api' export const API_DIR = process.env.API_DIR || './.api'
export const DATA_DIR = process.env.DATA_DIR || './temp/data' export const DATA_DIR = process.env.DATA_DIR || './temp/data'
export const LOGS_DIR = process.env.LOGS_DIR || './temp/logs' export const LOGS_DIR = process.env.LOGS_DIR || './temp/logs'
export const TESTING = process.env.NODE_ENV === 'test' ? true : false export const TESTING = process.env.NODE_ENV === 'test' ? true : false
export const OWNER = 'iptv-org' export const OWNER = 'iptv-org'
export const REPO = 'iptv' export const REPO = 'iptv'
export const EOL = '\r\n' export const EOL = '\r\n'

@ -1,14 +1,14 @@
export type LogItem = { export type LogItem = {
type: string type: string
filepath: string filepath: string
count: number count: number
} }
export class LogParser { export class LogParser {
parse(content: string): LogItem[] { parse(content: string): LogItem[] {
if (!content) return [] if (!content) return []
const lines = content.split('\n') const lines = content.split('\n')
return lines.map(line => (line ? JSON.parse(line) : null)).filter(l => l) return lines.map(line => (line ? JSON.parse(line) : null)).filter(l => l)
} }
} }

@ -1,10 +1,10 @@
export default class NumberParser { export default class NumberParser {
async parse(number: string) { async parse(number: string) {
const parsed = parseInt(number) const parsed = parseInt(number)
if (isNaN(parsed)) { if (isNaN(parsed)) {
throw new Error('numberParser:parse() Input value is not a number') throw new Error('numberParser:parse() Input value is not a number')
} }
return parsed return parsed
} }
} }

@ -1,31 +1,31 @@
import { URL } from 'node:url' import { URL } from 'node:url'
interface ProxyParserResult { interface ProxyParserResult {
protocol: string | null protocol: string | null
auth?: { auth?: {
username?: string username?: string
password?: string password?: string
} }
host: string host: string
port: number | null port: number | null
} }
export class ProxyParser { export class ProxyParser {
parse(_url: string): ProxyParserResult { parse(_url: string): ProxyParserResult {
const parsed = new URL(_url) const parsed = new URL(_url)
const result: ProxyParserResult = { const result: ProxyParserResult = {
protocol: parsed.protocol.replace(':', '') || null, protocol: parsed.protocol.replace(':', '') || null,
host: parsed.hostname, host: parsed.hostname,
port: parsed.port ? parseInt(parsed.port) : null port: parsed.port ? parseInt(parsed.port) : null
} }
if (parsed.username || parsed.password) { if (parsed.username || parsed.password) {
result.auth = {} result.auth = {}
if (parsed.username) result.auth.username = parsed.username if (parsed.username) result.auth.username = parsed.username
if (parsed.password) result.auth.password = parsed.password if (parsed.password) result.auth.password = parsed.password
} }
return result return result
} }
} }

@ -1,3 +1,3 @@
export interface Generator { export interface Generator {
generate(): Promise<void> generate(): Promise<void>
} }

@ -1,19 +1,19 @@
import { IssueData } from '../core' import { IssueData } from '../core'
type IssueProps = { type IssueProps = {
number: number number: number
labels: string[] labels: string[]
data: IssueData data: IssueData
} }
export class Issue { export class Issue {
number: number number: number
labels: string[] labels: string[]
data: IssueData data: IssueData
constructor({ number, labels, data }: IssueProps) { constructor({ number, labels, data }: IssueProps) {
this.number = number this.number = number
this.labels = labels this.labels = labels
this.data = data this.data = data
} }
} }

@ -1,4 +1,4 @@
export * from './categoriesTable' export * from './categoriesTable'
export * from './countriesTable' export * from './countriesTable'
export * from './languagesTable' export * from './languagesTable'
export * from './regionsTable' export * from './regionsTable'

@ -1,85 +1,85 @@
{"type":"raw","filepath":"raw/ad.m3u","count":4} {"type":"raw","filepath":"raw/ad.m3u","count":4}
{"type":"raw","filepath":"raw/ca.m3u","count":2} {"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":"category","filepath":"categories/auto.m3u","count":0} {"type":"category","filepath":"categories/auto.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/classic.m3u","count":0}
{"type":"category","filepath":"categories/comedy.m3u","count":0} {"type":"category","filepath":"categories/comedy.m3u","count":0}
{"type":"category","filepath":"categories/culture.m3u","count":0} {"type":"category","filepath":"categories/culture.m3u","count":0}
{"type":"category","filepath":"categories/documentary.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/cooking.m3u","count":0} {"type":"category","filepath":"categories/cooking.m3u","count":0}
{"type":"category","filepath":"categories/education.m3u","count":0} {"type":"category","filepath":"categories/education.m3u","count":0}
{"type":"category","filepath":"categories/family.m3u","count":0} {"type":"category","filepath":"categories/family.m3u","count":0}
{"type":"category","filepath":"categories/legislative.m3u","count":0} {"type":"category","filepath":"categories/legislative.m3u","count":0}
{"type":"category","filepath":"categories/news.m3u","count":1} {"type":"category","filepath":"categories/news.m3u","count":1}
{"type":"category","filepath":"categories/kids.m3u","count":0} {"type":"category","filepath":"categories/kids.m3u","count":0}
{"type":"category","filepath":"categories/lifestyle.m3u","count":0} {"type":"category","filepath":"categories/lifestyle.m3u","count":0}
{"type":"category","filepath":"categories/movies.m3u","count":0} {"type":"category","filepath":"categories/movies.m3u","count":0}
{"type":"category","filepath":"categories/religious.m3u","count":0} {"type":"category","filepath":"categories/religious.m3u","count":0}
{"type":"category","filepath":"categories/outdoor.m3u","count":0} {"type":"category","filepath":"categories/outdoor.m3u","count":0}
{"type":"category","filepath":"categories/general.m3u","count":3} {"type":"category","filepath":"categories/general.m3u","count":3}
{"type":"category","filepath":"categories/relax.m3u","count":0} {"type":"category","filepath":"categories/relax.m3u","count":0}
{"type":"category","filepath":"categories/music.m3u","count":0} {"type":"category","filepath":"categories/music.m3u","count":0}
{"type":"category","filepath":"categories/series.m3u","count":0} {"type":"category","filepath":"categories/series.m3u","count":0}
{"type":"category","filepath":"categories/travel.m3u","count":0} {"type":"category","filepath":"categories/travel.m3u","count":0}
{"type":"category","filepath":"categories/sports.m3u","count":0} {"type":"category","filepath":"categories/sports.m3u","count":0}
{"type":"category","filepath":"categories/science.m3u","count":0} {"type":"category","filepath":"categories/science.m3u","count":0}
{"type":"category","filepath":"categories/weather.m3u","count":1} {"type":"category","filepath":"categories/weather.m3u","count":1}
{"type":"category","filepath":"categories/shop.m3u","count":0} {"type":"category","filepath":"categories/shop.m3u","count":0}
{"type":"category","filepath":"categories/xxx.m3u","count":1} {"type":"category","filepath":"categories/xxx.m3u","count":1}
{"type":"category","filepath":"categories/undefined.m3u","count":7} {"type":"category","filepath":"categories/undefined.m3u","count":7}
{"type":"category","filepath":"categories/entertainment.m3u","count":0} {"type":"category","filepath":"categories/entertainment.m3u","count":0}
{"type":"language","filepath":"languages/cat.m3u","count":1} {"type":"language","filepath":"languages/cat.m3u","count":1}
{"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":"language","filepath":"languages/undefined.m3u","count":8}
{"type":"language","filepath":"languages/rus.m3u","count":1} {"type":"language","filepath":"languages/rus.m3u","count":1}
{"type":"country","filepath":"countries/ad.m3u","count":1} {"type":"country","filepath":"countries/ad.m3u","count":1}
{"type":"country","filepath":"countries/ru.m3u","count":1} {"type":"country","filepath":"countries/ru.m3u","count":1}
{"type":"country","filepath":"countries/ca.m3u","count":2} {"type":"country","filepath":"countries/ca.m3u","count":2}
{"type":"country","filepath":"countries/int.m3u","count":4} {"type":"country","filepath":"countries/int.m3u","count":4}
{"type":"country","filepath":"countries/undefined.m3u","count":4} {"type":"country","filepath":"countries/undefined.m3u","count":4}
{"type":"subdivision","filepath":"subdivisions/ad-02.m3u","count":1} {"type":"subdivision","filepath":"subdivisions/ad-02.m3u","count":1}
{"type":"city","filepath":"cities/adcan.m3u","count":1} {"type":"city","filepath":"cities/adcan.m3u","count":1}
{"type":"region","filepath":"regions/afr.m3u","count":2} {"type":"region","filepath":"regions/afr.m3u","count":2}
{"type":"source","filepath":"sources/in.m3u","count":1} {"type":"source","filepath":"sources/in.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ad-07.m3u","count":1} {"type":"subdivision","filepath":"subdivisions/ad-07.m3u","count":1}
{"type":"subdivision","filepath":"subdivisions/ca-on.m3u","count":1} {"type":"subdivision","filepath":"subdivisions/ca-on.m3u","count":1}
{"type":"region","filepath":"regions/amer.m3u","count":5} {"type":"region","filepath":"regions/amer.m3u","count":5}
{"type":"region","filepath":"regions/apac.m3u","count":2} {"type":"region","filepath":"regions/apac.m3u","count":2}
{"type":"region","filepath":"regions/cas.m3u","count":3} {"type":"region","filepath":"regions/cas.m3u","count":3}
{"type":"region","filepath":"regions/asean.m3u","count":2} {"type":"region","filepath":"regions/asean.m3u","count":2}
{"type":"region","filepath":"regions/cis.m3u","count":4} {"type":"region","filepath":"regions/cis.m3u","count":4}
{"type":"region","filepath":"regions/carib.m3u","count":2} {"type":"region","filepath":"regions/carib.m3u","count":2}
{"type":"region","filepath":"regions/arab.m3u","count":2} {"type":"region","filepath":"regions/arab.m3u","count":2}
{"type":"region","filepath":"regions/emea.m3u","count":5} {"type":"region","filepath":"regions/emea.m3u","count":5}
{"type":"region","filepath":"regions/asia.m3u","count":4} {"type":"region","filepath":"regions/asia.m3u","count":4}
{"type":"region","filepath":"regions/cenamer.m3u","count":2} {"type":"region","filepath":"regions/cenamer.m3u","count":2}
{"type":"region","filepath":"regions/ww.m3u","count":7} {"type":"region","filepath":"regions/ww.m3u","count":7}
{"type":"region","filepath":"regions/eur.m3u","count":5} {"type":"region","filepath":"regions/eur.m3u","count":5}
{"type":"region","filepath":"regions/lac.m3u","count":2} {"type":"region","filepath":"regions/lac.m3u","count":2}
{"type":"region","filepath":"regions/maghreb.m3u","count":2} {"type":"region","filepath":"regions/maghreb.m3u","count":2}
{"type":"region","filepath":"regions/latam.m3u","count":2} {"type":"region","filepath":"regions/latam.m3u","count":2}
{"type":"region","filepath":"regions/hispam.m3u","count":2} {"type":"region","filepath":"regions/hispam.m3u","count":2}
{"type":"region","filepath":"regions/mena.m3u","count":2} {"type":"region","filepath":"regions/mena.m3u","count":2}
{"type":"region","filepath":"regions/mideast.m3u","count":2} {"type":"region","filepath":"regions/mideast.m3u","count":2}
{"type":"region","filepath":"regions/nam.m3u","count":5} {"type":"region","filepath":"regions/nam.m3u","count":5}
{"type":"region","filepath":"regions/oce.m3u","count":2} {"type":"region","filepath":"regions/oce.m3u","count":2}
{"type":"region","filepath":"regions/nord.m3u","count":2} {"type":"region","filepath":"regions/nord.m3u","count":2}
{"type":"region","filepath":"regions/southam.m3u","count":2} {"type":"region","filepath":"regions/southam.m3u","count":2}
{"type":"region","filepath":"regions/noram.m3u","count":5} {"type":"region","filepath":"regions/noram.m3u","count":5}
{"type":"region","filepath":"regions/wafr.m3u","count":2} {"type":"region","filepath":"regions/wafr.m3u","count":2}
{"type":"region","filepath":"regions/sas.m3u","count":2} {"type":"region","filepath":"regions/sas.m3u","count":2}
{"type":"region","filepath":"regions/ssa.m3u","count":2} {"type":"region","filepath":"regions/ssa.m3u","count":2}
{"type":"source","filepath":"sources/unsorted.m3u","count":4} {"type":"source","filepath":"sources/unsorted.m3u","count":4}
{"type":"source","filepath":"sources/ca.m3u","count":2} {"type":"source","filepath":"sources/ca.m3u","count":2}
{"type":"source","filepath":"sources/ad.m3u","count":3} {"type":"source","filepath":"sources/ad.m3u","count":3}
{"type":"source","filepath":"sources/uk.m3u","count":1} {"type":"source","filepath":"sources/uk.m3u","count":1}
{"type":"source","filepath":"sources/kg.m3u","count":1} {"type":"source","filepath":"sources/kg.m3u","count":1}
{"type":"index","filepath":"index.m3u","count":11} {"type":"index","filepath":"index.m3u","count":11}
{"type":"index","filepath":"index.category.m3u","count":12} {"type":"index","filepath":"index.category.m3u","count":12}
{"type":"index","filepath":"index.country.m3u","count":12} {"type":"index","filepath":"index.country.m3u","count":12}
{"type":"index","filepath":"index.language.m3u","count":11} {"type":"index","filepath":"index.language.m3u","count":11}

@ -1,177 +1,177 @@
## Playlists ## Playlists
There are several versions of playlists that differ in the way they are grouped. As of January 30th, 2024, we have stopped distributing NSFW channels. For more information, please look at [this issue](https://github.com/iptv-org/iptv/issues/15723). There are several versions of playlists that differ in the way they are grouped. As of January 30th, 2024, we have stopped distributing NSFW channels. For more information, please look at [this issue](https://github.com/iptv-org/iptv/issues/15723).
### Grouped by category ### Grouped by category
Playlists in which channels are grouped by category. Playlists in which channels are grouped by category.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
<br> <br>
``` ```
https://iptv-org.github.io/iptv/index.category.m3u https://iptv-org.github.io/iptv/index.category.m3u
``` ```
Same thing, but split up into separate files: Same thing, but split up into separate files:
<!-- prettier-ignore --> <!-- prettier-ignore -->
<table> <table>
<thead> <thead>
<tr><th align="left">Category</th><th align="left">Channels</th><th align="left">Playlist</th></tr> <tr><th align="left">Category</th><th align="left">Channels</th><th align="left">Playlist</th></tr>
</thead> </thead>
<tbody> <tbody>
<tr><td>Animation</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/animation.m3u</code></td></tr> <tr><td>Animation</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/animation.m3u</code></td></tr>
<tr><td>Auto</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/auto.m3u</code></td></tr> <tr><td>Auto</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/auto.m3u</code></td></tr>
<tr><td>Business</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/business.m3u</code></td></tr> <tr><td>Business</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/business.m3u</code></td></tr>
<tr><td>Classic</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/classic.m3u</code></td></tr> <tr><td>Classic</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/classic.m3u</code></td></tr>
<tr><td>Comedy</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/comedy.m3u</code></td></tr> <tr><td>Comedy</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/comedy.m3u</code></td></tr>
<tr><td>Cooking</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/cooking.m3u</code></td></tr> <tr><td>Cooking</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/cooking.m3u</code></td></tr>
<tr><td>Culture</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/culture.m3u</code></td></tr> <tr><td>Culture</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/culture.m3u</code></td></tr>
<tr><td>Documentary</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/documentary.m3u</code></td></tr> <tr><td>Documentary</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/documentary.m3u</code></td></tr>
<tr><td>Education</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/education.m3u</code></td></tr> <tr><td>Education</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/education.m3u</code></td></tr>
<tr><td>Entertainment</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/entertainment.m3u</code></td></tr> <tr><td>Entertainment</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/entertainment.m3u</code></td></tr>
<tr><td>Family</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/family.m3u</code></td></tr> <tr><td>Family</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/family.m3u</code></td></tr>
<tr><td>General</td><td align="right">2</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/general.m3u</code></td></tr> <tr><td>General</td><td align="right">2</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/general.m3u</code></td></tr>
<tr><td>Kids</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/kids.m3u</code></td></tr> <tr><td>Kids</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/kids.m3u</code></td></tr>
<tr><td>Legislative</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/legislative.m3u</code></td></tr> <tr><td>Legislative</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/legislative.m3u</code></td></tr>
<tr><td>Lifestyle</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/lifestyle.m3u</code></td></tr> <tr><td>Lifestyle</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/lifestyle.m3u</code></td></tr>
<tr><td>Movies</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/movies.m3u</code></td></tr> <tr><td>Movies</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/movies.m3u</code></td></tr>
<tr><td>Music</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/music.m3u</code></td></tr> <tr><td>Music</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/music.m3u</code></td></tr>
<tr><td>News</td><td align="right">1</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/news.m3u</code></td></tr> <tr><td>News</td><td align="right">1</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/news.m3u</code></td></tr>
<tr><td>Outdoor</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/outdoor.m3u</code></td></tr> <tr><td>Outdoor</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/outdoor.m3u</code></td></tr>
<tr><td>Relax</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/relax.m3u</code></td></tr> <tr><td>Relax</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/relax.m3u</code></td></tr>
<tr><td>Religious</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/religious.m3u</code></td></tr> <tr><td>Religious</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/religious.m3u</code></td></tr>
<tr><td>Science</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/science.m3u</code></td></tr> <tr><td>Science</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/science.m3u</code></td></tr>
<tr><td>Series</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/series.m3u</code></td></tr> <tr><td>Series</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/series.m3u</code></td></tr>
<tr><td>Shop</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/shop.m3u</code></td></tr> <tr><td>Shop</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/shop.m3u</code></td></tr>
<tr><td>Sports</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/sports.m3u</code></td></tr> <tr><td>Sports</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/sports.m3u</code></td></tr>
<tr><td>Travel</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/travel.m3u</code></td></tr> <tr><td>Travel</td><td align="right">0</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/travel.m3u</code></td></tr>
<tr><td>Weather</td><td align="right">1</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/weather.m3u</code></td></tr> <tr><td>Weather</td><td align="right">1</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/weather.m3u</code></td></tr>
<tr><td>XXX</td><td align="right">1</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/xxx.m3u</code></td></tr> <tr><td>XXX</td><td align="right">1</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/xxx.m3u</code></td></tr>
<tr><td>Undefined</td><td align="right">3</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/undefined.m3u</code></td></tr> <tr><td>Undefined</td><td align="right">3</td><td nowrap><code>https://iptv-org.github.io/iptv/categories/undefined.m3u</code></td></tr>
</tbody> </tbody>
</table> </table>
</details> </details>
### Grouped by language ### Grouped by language
Playlists in which channels are grouped by the language in which they are broadcast. Playlists in which channels are grouped by the language in which they are broadcast.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
<br> <br>
``` ```
https://iptv-org.github.io/iptv/index.language.m3u https://iptv-org.github.io/iptv/index.language.m3u
``` ```
Same thing, but split up into separate files: Same thing, but split up into separate files:
<!-- prettier-ignore --> <!-- prettier-ignore -->
<table> <table>
<thead> <thead>
<tr><th align="left">Language</th><th align="left">Channels</th><th align="left">Playlist</th></tr> <tr><th align="left">Language</th><th align="left">Channels</th><th align="left">Playlist</th></tr>
</thead> </thead>
<tbody> <tbody>
<tr><td align="left">Catalan</td><td align="right">1</td><td align="left" nowrap><code>https://iptv-org.github.io/iptv/languages/cat.m3u</code></td></tr> <tr><td align="left">Catalan</td><td align="right">1</td><td align="left" nowrap><code>https://iptv-org.github.io/iptv/languages/cat.m3u</code></td></tr>
<tr><td align="left">English</td><td align="right">1</td><td align="left" nowrap><code>https://iptv-org.github.io/iptv/languages/eng.m3u</code></td></tr> <tr><td align="left">English</td><td align="right">1</td><td align="left" nowrap><code>https://iptv-org.github.io/iptv/languages/eng.m3u</code></td></tr>
<tr><td align="left">French</td><td align="right">1</td><td align="left" nowrap><code>https://iptv-org.github.io/iptv/languages/fra.m3u</code></td></tr> <tr><td align="left">French</td><td align="right">1</td><td align="left" nowrap><code>https://iptv-org.github.io/iptv/languages/fra.m3u</code></td></tr>
<tr><td align="left">Russian</td><td align="right">1</td><td align="left" nowrap><code>https://iptv-org.github.io/iptv/languages/rus.m3u</code></td></tr> <tr><td align="left">Russian</td><td align="right">1</td><td align="left" nowrap><code>https://iptv-org.github.io/iptv/languages/rus.m3u</code></td></tr>
<tr><td align="left">Undefined</td><td align="right">2</td><td align="left" nowrap><code>https://iptv-org.github.io/iptv/languages/undefined.m3u</code></td></tr> <tr><td align="left">Undefined</td><td align="right">2</td><td align="left" nowrap><code>https://iptv-org.github.io/iptv/languages/undefined.m3u</code></td></tr>
</tbody> </tbody>
</table> </table>
</details> </details>
### Grouped by broadcast area ### Grouped by broadcast area
Playlists in which channels are grouped by broadcast area. Playlists in which channels are grouped by broadcast area.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
#### Countries #### Countries
``` ```
https://iptv-org.github.io/iptv/index.country.m3u https://iptv-org.github.io/iptv/index.country.m3u
``` ```
Same thing, but split up into separate files: Same thing, but split up into separate files:
<!-- prettier-ignore --> <!-- prettier-ignore -->
- 🇦🇩 Andorra <code>https://iptv-org.github.io/iptv/countries/ad.m3u</code> - 🇦🇩 Andorra <code>https://iptv-org.github.io/iptv/countries/ad.m3u</code>
- Canillo <code>https://iptv-org.github.io/iptv/subdivisions/ad-02.m3u</code> - Canillo <code>https://iptv-org.github.io/iptv/subdivisions/ad-02.m3u</code>
- Canillo <code>https://iptv-org.github.io/iptv/cities/adcan.m3u</code> - Canillo <code>https://iptv-org.github.io/iptv/cities/adcan.m3u</code>
- 🇨🇲 Cameroon <code>https://iptv-org.github.io/iptv/countries/cm.m3u</code> - 🇨🇲 Cameroon <code>https://iptv-org.github.io/iptv/countries/cm.m3u</code>
- 🇨🇦 Canada <code>https://iptv-org.github.io/iptv/countries/ca.m3u</code> - 🇨🇦 Canada <code>https://iptv-org.github.io/iptv/countries/ca.m3u</code>
- Ontario <code>https://iptv-org.github.io/iptv/subdivisions/ca-on.m3u</code> - Ontario <code>https://iptv-org.github.io/iptv/subdivisions/ca-on.m3u</code>
- 🇨🇻 Cape Verde <code>https://iptv-org.github.io/iptv/countries/cv.m3u</code> - 🇨🇻 Cape Verde <code>https://iptv-org.github.io/iptv/countries/cv.m3u</code>
- 🇭🇰 Hong Kong <code>https://iptv-org.github.io/iptv/countries/hk.m3u</code> - 🇭🇰 Hong Kong <code>https://iptv-org.github.io/iptv/countries/hk.m3u</code>
- Sai Kung <code>https://iptv-org.github.io/iptv/cities/hk9sk.m3u</code> - Sai Kung <code>https://iptv-org.github.io/iptv/cities/hk9sk.m3u</code>
- 🇨🇬 Republic of the Congo <code>https://iptv-org.github.io/iptv/countries/cg.m3u</code> - 🇨🇬 Republic of the Congo <code>https://iptv-org.github.io/iptv/countries/cg.m3u</code>
- 🇷🇪 Réunion <code>https://iptv-org.github.io/iptv/countries/re.m3u</code> - 🇷🇪 Réunion <code>https://iptv-org.github.io/iptv/countries/re.m3u</code>
- 🇷🇴 Romania <code>https://iptv-org.github.io/iptv/countries/ro.m3u</code> - 🇷🇴 Romania <code>https://iptv-org.github.io/iptv/countries/ro.m3u</code>
- 🇷🇺 Russia <code>https://iptv-org.github.io/iptv/countries/ru.m3u</code> - 🇷🇺 Russia <code>https://iptv-org.github.io/iptv/countries/ru.m3u</code>
- 🇷🇼 Rwanda <code>https://iptv-org.github.io/iptv/countries/rw.m3u</code> - 🇷🇼 Rwanda <code>https://iptv-org.github.io/iptv/countries/rw.m3u</code>
- 🇧🇱 Saint Barthélemy <code>https://iptv-org.github.io/iptv/countries/bl.m3u</code> - 🇧🇱 Saint Barthélemy <code>https://iptv-org.github.io/iptv/countries/bl.m3u</code>
- 🇸🇭 Saint Helena <code>https://iptv-org.github.io/iptv/countries/sh.m3u</code> - 🇸🇭 Saint Helena <code>https://iptv-org.github.io/iptv/countries/sh.m3u</code>
- 🇰🇳 Saint Kitts and Nevis <code>https://iptv-org.github.io/iptv/countries/kn.m3u</code> - 🇰🇳 Saint Kitts and Nevis <code>https://iptv-org.github.io/iptv/countries/kn.m3u</code>
- 🌐 International <code>https://iptv-org.github.io/iptv/countries/int.m3u</code> - 🌐 International <code>https://iptv-org.github.io/iptv/countries/int.m3u</code>
- Undefined <code>https://iptv-org.github.io/iptv/countries/undefined.m3u</code> - Undefined <code>https://iptv-org.github.io/iptv/countries/undefined.m3u</code>
#### Regions #### Regions
<!-- prettier-ignore --> <!-- prettier-ignore -->
- Africa <code>https://iptv-org.github.io/iptv/regions/afr.m3u</code> - Africa <code>https://iptv-org.github.io/iptv/regions/afr.m3u</code>
- Americas <code>https://iptv-org.github.io/iptv/regions/amer.m3u</code> - Americas <code>https://iptv-org.github.io/iptv/regions/amer.m3u</code>
- Arab world <code>https://iptv-org.github.io/iptv/regions/arab.m3u</code> - Arab world <code>https://iptv-org.github.io/iptv/regions/arab.m3u</code>
- Asia <code>https://iptv-org.github.io/iptv/regions/asia.m3u</code> - Asia <code>https://iptv-org.github.io/iptv/regions/asia.m3u</code>
- Asia-Pacific <code>https://iptv-org.github.io/iptv/regions/apac.m3u</code> - Asia-Pacific <code>https://iptv-org.github.io/iptv/regions/apac.m3u</code>
- Association of Southeast Asian Nations <code>https://iptv-org.github.io/iptv/regions/asean.m3u</code> - Association of Southeast Asian Nations <code>https://iptv-org.github.io/iptv/regions/asean.m3u</code>
- Caribbean <code>https://iptv-org.github.io/iptv/regions/carib.m3u</code> - Caribbean <code>https://iptv-org.github.io/iptv/regions/carib.m3u</code>
- Central America <code>https://iptv-org.github.io/iptv/regions/cenamer.m3u</code> - Central America <code>https://iptv-org.github.io/iptv/regions/cenamer.m3u</code>
- Central Asia <code>https://iptv-org.github.io/iptv/regions/cas.m3u</code> - Central Asia <code>https://iptv-org.github.io/iptv/regions/cas.m3u</code>
- Commonwealth of Independent States <code>https://iptv-org.github.io/iptv/regions/cis.m3u</code> - Commonwealth of Independent States <code>https://iptv-org.github.io/iptv/regions/cis.m3u</code>
- Europe <code>https://iptv-org.github.io/iptv/regions/eur.m3u</code> - Europe <code>https://iptv-org.github.io/iptv/regions/eur.m3u</code>
- Europe, the Middle East and Africa <code>https://iptv-org.github.io/iptv/regions/emea.m3u</code> - Europe, the Middle East and Africa <code>https://iptv-org.github.io/iptv/regions/emea.m3u</code>
- Hispanic America <code>https://iptv-org.github.io/iptv/regions/hispam.m3u</code> - Hispanic America <code>https://iptv-org.github.io/iptv/regions/hispam.m3u</code>
- Latin America <code>https://iptv-org.github.io/iptv/regions/latam.m3u</code> - Latin America <code>https://iptv-org.github.io/iptv/regions/latam.m3u</code>
- Latin America and the Caribbean <code>https://iptv-org.github.io/iptv/regions/lac.m3u</code> - Latin America and the Caribbean <code>https://iptv-org.github.io/iptv/regions/lac.m3u</code>
- Maghreb <code>https://iptv-org.github.io/iptv/regions/maghreb.m3u</code> - Maghreb <code>https://iptv-org.github.io/iptv/regions/maghreb.m3u</code>
- Middle East <code>https://iptv-org.github.io/iptv/regions/mideast.m3u</code> - Middle East <code>https://iptv-org.github.io/iptv/regions/mideast.m3u</code>
- Middle East and North Africa <code>https://iptv-org.github.io/iptv/regions/mena.m3u</code> - Middle East and North Africa <code>https://iptv-org.github.io/iptv/regions/mena.m3u</code>
- Nordics <code>https://iptv-org.github.io/iptv/regions/nord.m3u</code> - Nordics <code>https://iptv-org.github.io/iptv/regions/nord.m3u</code>
- North America <code>https://iptv-org.github.io/iptv/regions/noram.m3u</code> - North America <code>https://iptv-org.github.io/iptv/regions/noram.m3u</code>
- Northern America <code>https://iptv-org.github.io/iptv/regions/nam.m3u</code> - Northern America <code>https://iptv-org.github.io/iptv/regions/nam.m3u</code>
- Oceania <code>https://iptv-org.github.io/iptv/regions/oce.m3u</code> - Oceania <code>https://iptv-org.github.io/iptv/regions/oce.m3u</code>
- South America <code>https://iptv-org.github.io/iptv/regions/southam.m3u</code> - South America <code>https://iptv-org.github.io/iptv/regions/southam.m3u</code>
- South Asia <code>https://iptv-org.github.io/iptv/regions/sas.m3u</code> - South Asia <code>https://iptv-org.github.io/iptv/regions/sas.m3u</code>
- Sub-Saharan Africa <code>https://iptv-org.github.io/iptv/regions/ssa.m3u</code> - Sub-Saharan Africa <code>https://iptv-org.github.io/iptv/regions/ssa.m3u</code>
- West Africa <code>https://iptv-org.github.io/iptv/regions/wafr.m3u</code> - West Africa <code>https://iptv-org.github.io/iptv/regions/wafr.m3u</code>
</details> </details>
### Grouped by sources ### Grouped by sources
Playlists in which channels are grouped by broadcast source. Playlists in which channels are grouped by broadcast source.
<details> <details>
<summary>Expand</summary> <summary>Expand</summary>
<br> <br>
To use the playlist, simply replace `<FILENAME>` in the link below with the name of one of the files in the [streams](streams) folder. To use the playlist, simply replace `<FILENAME>` in the link below with the name of one of the files in the [streams](streams) folder.
``` ```
https://iptv-org.github.io/iptv/sources/<FILENAME>.m3u https://iptv-org.github.io/iptv/sources/<FILENAME>.m3u
``` ```
</details> </details>
Also, any of our internal playlists are available in raw form (without any filtering or sorting) at this link: Also, any of our internal playlists are available in raw form (without any filtering or sorting) at this link:
``` ```
https://iptv-org.github.io/iptv/raw/<FILENAME>.m3u https://iptv-org.github.io/iptv/raw/<FILENAME>.m3u
``` ```

File diff suppressed because it is too large Load Diff

@ -1,14 +1,14 @@
module.exports = { module.exports = {
'https://query-streamlink.herokuapp.com/iptv-query?streaming-ip=https://www.twitch.tv/absliveantigua3': 'https://query-streamlink.herokuapp.com/iptv-query?streaming-ip=https://www.twitch.tv/absliveantigua3':
{ {
url: 'https://query-streamlink.herokuapp.com/iptv-query?streaming-ip=https://www.twitch.tv/absliveantigua3', url: 'https://query-streamlink.herokuapp.com/iptv-query?streaming-ip=https://www.twitch.tv/absliveantigua3',
http: { referrer: '', 'user-agent': '' }, http: { referrer: '', 'user-agent': '' },
status: { ok: false, code: 'HTTP_NOT_FOUND', message: 'HTTP 404 Not Found' } status: { ok: false, code: 'HTTP_NOT_FOUND', message: 'HTTP 404 Not Found' }
}, },
'https://tego-cdn2a.sibercdn.com/Live_TV-ABSTV-10/tracks-v3a1/rewind-7200.m3u8?token=e5f61e7be8363eb781b4bdfe591bf917dd529c1a-SjY3NzRTbDZQNnFQVkZaNkZja2RxV3JKc1VBa05zQkdMNStJakRGV0VTTzNrOEVGVUlIQmxta1NLV0o3bzdVdQ-1736094545-1736008145': 'https://tego-cdn2a.sibercdn.com/Live_TV-ABSTV-10/tracks-v3a1/rewind-7200.m3u8?token=e5f61e7be8363eb781b4bdfe591bf917dd529c1a-SjY3NzRTbDZQNnFQVkZaNkZja2RxV3JKc1VBa05zQkdMNStJakRGV0VTTzNrOEVGVUlIQmxta1NLV0o3bzdVdQ-1736094545-1736008145':
{ {
url: 'https://tego-cdn2a.sibercdn.com/Live_TV-ABSTV-10/tracks-v3a1/rewind-7200.m3u8?token=e5f61e7be8363eb781b4bdfe591bf917dd529c1a-SjY3NzRTbDZQNnFQVkZaNkZja2RxV3JKc1VBa05zQkdMNStJakRGV0VTTzNrOEVGVUlIQmxta1NLV0o3bzdVdQ-1736094545-1736008145', url: 'https://tego-cdn2a.sibercdn.com/Live_TV-ABSTV-10/tracks-v3a1/rewind-7200.m3u8?token=e5f61e7be8363eb781b4bdfe591bf917dd529c1a-SjY3NzRTbDZQNnFQVkZaNkZja2RxV3JKc1VBa05zQkdMNStJakRGV0VTTzNrOEVGVUlIQmxta1NLV0o3bzdVdQ-1736094545-1736008145',
http: { referrer: '', 'user-agent': '' }, http: { referrer: '', 'user-agent': '' },
status: { ok: false, code: 'HTTP_FORBIDDEN', message: 'HTTP 403 Forbidden' } status: { ok: false, code: 'HTTP_FORBIDDEN', message: 'HTTP 403 Forbidden' }
} }
} }

@ -1,26 +1,26 @@
import { pathToFileURL } from 'node:url' import { pathToFileURL } from 'node:url'
import { execSync } from 'child_process' import { execSync } from 'child_process'
import fs from 'fs-extra' import fs from 'fs-extra'
const ENV_VAR = const ENV_VAR =
'cross-env DATA_DIR=tests/__data__/input/data STREAMS_DIR=tests/__data__/input/api_generate API_DIR=tests/__data__/output/.api' 'cross-env DATA_DIR=tests/__data__/input/data STREAMS_DIR=tests/__data__/input/api_generate API_DIR=tests/__data__/output/.api'
beforeEach(() => { beforeEach(() => {
fs.emptyDirSync('tests/__data__/output') fs.emptyDirSync('tests/__data__/output')
}) })
describe('api:generate', () => { describe('api:generate', () => {
it('can create streams.json', () => { it('can create streams.json', () => {
const cmd = `${ENV_VAR} npm run api:generate` const cmd = `${ENV_VAR} npm run api:generate`
const stdout = execSync(cmd, { encoding: 'utf8' }) const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout) if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/.api/streams.json')).toMatchObject( expect(content('tests/__data__/output/.api/streams.json')).toMatchObject(
content('tests/__data__/expected/api_generate/.api/streams.json') content('tests/__data__/expected/api_generate/.api/streams.json')
) )
}) })
}) })
function content(filepath: string) { function content(filepath: string) {
return JSON.parse(fs.readFileSync(pathToFileURL(filepath), { encoding: 'utf8' })) return JSON.parse(fs.readFileSync(pathToFileURL(filepath), { encoding: 'utf8' }))
} }

@ -1,38 +1,38 @@
import { execSync } from 'child_process' import { execSync } from 'child_process'
import fs from 'fs-extra' import fs from 'fs-extra'
type ExecError = { type ExecError = {
status: number status: number
stdout: string stdout: string
} }
const ENV_VAR = 'cross-env DATA_DIR=tests/__data__/input/data' const ENV_VAR = 'cross-env DATA_DIR=tests/__data__/input/data'
beforeEach(() => { beforeEach(() => {
fs.emptyDirSync('tests/__data__/output') fs.emptyDirSync('tests/__data__/output')
fs.copySync( fs.copySync(
'tests/__data__/input/playlist_edit/playlist.m3u', 'tests/__data__/input/playlist_edit/playlist.m3u',
'tests/__data__/output/playlist.m3u' 'tests/__data__/output/playlist.m3u'
) )
}) })
describe('playlist:edit', () => { describe('playlist:edit', () => {
it('shows list of options for a streams', () => { it('shows list of options for a streams', () => {
const cmd = `${ENV_VAR} npm run playlist:edit --- tests/__data__/output/playlist.m3u` const cmd = `${ENV_VAR} npm run playlist:edit --- tests/__data__/output/playlist.m3u`
try { try {
const stdout = execSync(cmd, { encoding: 'utf8' }) const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout) if (process.env.DEBUG === 'true') console.log(cmd, stdout)
checkStdout(stdout) checkStdout(stdout)
} catch (error) { } catch (error) {
// NOTE: for Windows only // NOTE: for Windows only
if (process.env.DEBUG === 'true') console.log(cmd, error) if (process.env.DEBUG === 'true') console.log(cmd, error)
checkStdout((error as ExecError).stdout) checkStdout((error as ExecError).stdout)
} }
}) })
}) })
function checkStdout(stdout: string) { function checkStdout(stdout: string) {
expect(stdout).toContain('TF1.fr (TF1, Télévision française 1)') expect(stdout).toContain('TF1.fr (TF1, Télévision française 1)')
expect(stdout).toContain('Type...') expect(stdout).toContain('Type...')
expect(stdout).toContain('Skip') expect(stdout).toContain('Skip')
} }

@ -1,36 +1,37 @@
import { pathToFileURL } from 'node:url' import { pathToFileURL } from 'node:url'
import { execSync } from 'child_process' import { execSync } from 'child_process'
import * as fs from 'fs-extra' import * as fs from 'fs-extra'
import { glob } from 'glob' import { glob } from 'glob'
const ENV_VAR = 'cross-env STREAMS_DIR=tests/__data__/output/streams DATA_DIR=tests/__data__/input/data' const ENV_VAR =
'cross-env STREAMS_DIR=tests/__data__/output/streams DATA_DIR=tests/__data__/input/data'
beforeEach(() => {
fs.emptyDirSync('tests/__data__/output') beforeEach(() => {
fs.copySync('tests/__data__/input/playlist_format', 'tests/__data__/output/streams') fs.emptyDirSync('tests/__data__/output')
}) fs.copySync('tests/__data__/input/playlist_format', 'tests/__data__/output/streams')
})
describe('playlist:format', () => {
it('can format playlists', () => { describe('playlist:format', () => {
const cmd = `${ENV_VAR} npm run playlist:format` it('can format playlists', () => {
const stdout = execSync(cmd, { encoding: 'utf8' }) const cmd = `${ENV_VAR} npm run playlist:format`
if (process.env.DEBUG === 'true') console.log(cmd, stdout) const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
const files = glob.sync('tests/__data__/expected/playlist_format/*.m3u').map(filepath => {
const fileUrl = pathToFileURL(filepath).toString() const files = glob.sync('tests/__data__/expected/playlist_format/*.m3u').map(filepath => {
const pathToRemove = pathToFileURL('tests/__data__/expected/playlist_format/').toString() const fileUrl = pathToFileURL(filepath).toString()
const pathToRemove = pathToFileURL('tests/__data__/expected/playlist_format/').toString()
return fileUrl.replace(pathToRemove, '')
}) return fileUrl.replace(pathToRemove, '')
})
files.forEach(filepath => {
expect(content(`tests/__data__/output/streams/${filepath}`)).toBe( files.forEach(filepath => {
content(`tests/__data__/expected/playlist_format/${filepath}`) expect(content(`tests/__data__/output/streams/${filepath}`)).toBe(
) content(`tests/__data__/expected/playlist_format/${filepath}`)
}) )
}) })
}) })
})
function content(filepath: string) {
return fs.readFileSync(pathToFileURL(filepath), { encoding: 'utf8' }) function content(filepath: string) {
} return fs.readFileSync(pathToFileURL(filepath), { encoding: 'utf8' })
}

@ -1,43 +1,43 @@
import { pathToFileURL } from 'node:url' import { pathToFileURL } from 'node:url'
import { execSync } from 'child_process' import { execSync } from 'child_process'
import { EOL } from 'node:os' import { EOL } from 'node:os'
import * as fs from 'fs-extra' import * as fs from 'fs-extra'
import * as glob from 'glob' import * as glob from 'glob'
const ENV_VAR = const ENV_VAR =
'cross-env STREAMS_DIR=tests/__data__/input/playlist_generate DATA_DIR=tests/__data__/input/data PUBLIC_DIR=tests/__data__/output/.gh-pages LOGS_DIR=tests/__data__/output/logs' 'cross-env STREAMS_DIR=tests/__data__/input/playlist_generate DATA_DIR=tests/__data__/input/data PUBLIC_DIR=tests/__data__/output/.gh-pages LOGS_DIR=tests/__data__/output/logs'
beforeEach(() => { beforeEach(() => {
fs.emptyDirSync('tests/__data__/output') fs.emptyDirSync('tests/__data__/output')
}) })
describe('playlist:generate', () => { describe('playlist:generate', () => {
it('can generate playlists and logs', () => { it('can generate playlists and logs', () => {
const cmd = `${ENV_VAR} npm run playlist:generate` const cmd = `${ENV_VAR} npm run playlist:generate`
const stdout = execSync(cmd, { encoding: 'utf8' }) const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout) if (process.env.DEBUG === 'true') console.log(cmd, stdout)
const playlists = glob const playlists = glob
.sync('tests/__data__/expected/playlist_generate/.gh-pages/**/*.m3u') .sync('tests/__data__/expected/playlist_generate/.gh-pages/**/*.m3u')
.map(filepath => { .map(filepath => {
const fileUrl = pathToFileURL(filepath).toString() const fileUrl = pathToFileURL(filepath).toString()
const pathToRemove = pathToFileURL('tests/__data__/expected/playlist_generate/').toString() const pathToRemove = pathToFileURL('tests/__data__/expected/playlist_generate/').toString()
return fileUrl.replace(pathToRemove, '') return fileUrl.replace(pathToRemove, '')
}) })
playlists.forEach((filepath: string) => { playlists.forEach((filepath: string) => {
expect(content(`tests/__data__/output/${filepath}`), filepath).toBe( expect(content(`tests/__data__/output/${filepath}`), filepath).toBe(
content(`tests/__data__/expected/playlist_generate/${filepath}`) content(`tests/__data__/expected/playlist_generate/${filepath}`)
) )
}) })
expect(content('tests/__data__/output/logs/generators.log').split(EOL).sort()).toStrictEqual( expect(content('tests/__data__/output/logs/generators.log').split(EOL).sort()).toStrictEqual(
content('tests/__data__/expected/playlist_generate/logs/generators.log').split(EOL).sort() content('tests/__data__/expected/playlist_generate/logs/generators.log').split(EOL).sort()
) )
}) })
}) })
function content(filepath: string) { function content(filepath: string) {
return fs.readFileSync(pathToFileURL(filepath), { encoding: 'utf8' }) return fs.readFileSync(pathToFileURL(filepath), { encoding: 'utf8' })
} }

@ -1,21 +1,21 @@
import { execSync } from 'child_process' import { execSync } from 'child_process'
type ExecError = { type ExecError = {
status: number status: number
stdout: string stdout: string
} }
const ENV_VAR = 'cross-env ROOT_DIR=tests/__data__/input DATA_DIR=tests/__data__/input/data' const ENV_VAR = 'cross-env ROOT_DIR=tests/__data__/input DATA_DIR=tests/__data__/input/data'
describe('playlist:test', () => { describe('playlist:test', () => {
it('shows an error if the playlist contains a broken link', () => { it('shows an error if the playlist contains a broken link', () => {
const cmd = `${ENV_VAR} npm run playlist:test playlist_test/ag.m3u` const cmd = `${ENV_VAR} npm run playlist:test playlist_test/ag.m3u`
try { try {
execSync(cmd, { encoding: 'utf8' }) execSync(cmd, { encoding: 'utf8' })
} catch (error) { } catch (error) {
if (process.env.DEBUG === 'true') console.log(cmd, error) if (process.env.DEBUG === 'true') console.log(cmd, error)
expect((error as ExecError).stdout).toContain('playlist_test/ag.m3u') expect((error as ExecError).stdout).toContain('playlist_test/ag.m3u')
expect((error as ExecError).stdout).toContain('2 problems (1 errors, 1 warnings)') expect((error as ExecError).stdout).toContain('2 problems (1 errors, 1 warnings)')
} }
}) })
}) })

@ -1,44 +1,45 @@
import { execSync } from 'child_process' import { execSync } from 'child_process'
type ExecError = { type ExecError = {
status: number status: number
stdout: string stdout: string
} }
const ENV_VAR = 'cross-env DATA_DIR=tests/__data__/input/data ROOT_DIR=tests/__data__/input/playlist_validate' const ENV_VAR =
'cross-env DATA_DIR=tests/__data__/input/data ROOT_DIR=tests/__data__/input/playlist_validate'
describe('playlist:validate', () => {
it('show an error if channel id in the blocklist', () => { describe('playlist:validate', () => {
const cmd = `${ENV_VAR} npm run playlist:validate -- us_blocked.m3u` it('show an error if channel id in the blocklist', () => {
try { const cmd = `${ENV_VAR} npm run playlist:validate -- us_blocked.m3u`
execSync(cmd, { encoding: 'utf8' }) try {
} catch (error) { execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, error) } catch (error) {
expect((error as ExecError).stdout).toContain('us_blocked.m3u') if (process.env.DEBUG === 'true') console.log(cmd, error)
expect((error as ExecError).stdout).toContain( expect((error as ExecError).stdout).toContain('us_blocked.m3u')
'2 error "FoxSports2.us" is on the blocklist due to claims of copyright holders (https://github.com/iptv-org/iptv/issues/0002)' expect((error as ExecError).stdout).toContain(
) '2 error "FoxSports2.us" is on the blocklist due to claims of copyright holders (https://github.com/iptv-org/iptv/issues/0002)'
expect((error as ExecError).stdout).toContain( )
'4 error "TVN.pl" is on the blocklist due to NSFW content (https://github.com/iptv-org/iptv/issues/0003)' expect((error as ExecError).stdout).toContain(
) '4 error "TVN.pl" is on the blocklist due to NSFW content (https://github.com/iptv-org/iptv/issues/0003)'
expect((error as ExecError).stdout).toContain('2 problems (2 errors, 0 warnings)') )
} expect((error as ExecError).stdout).toContain('2 problems (2 errors, 0 warnings)')
}) }
})
it('show a warning if channel has wrong id', () => {
const cmd = `${ENV_VAR} npm run playlist:validate -- wrong_id.m3u` it('show a warning if channel has wrong id', () => {
try { const cmd = `${ENV_VAR} npm run playlist:validate -- wrong_id.m3u`
execSync(cmd, { encoding: 'utf8' }) try {
} catch (error) { execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, error) } catch (error) {
expect((error as ExecError).stdout).toContain( if (process.env.DEBUG === 'true') console.log(cmd, error)
'wrong_id.m3u\n 2 warning "qib22lAq1L.us" is not in the database\n\n1 problems (0 errors, 1 warnings)\n' expect((error as ExecError).stdout).toContain(
) 'wrong_id.m3u\n 2 warning "qib22lAq1L.us" is not in the database\n\n1 problems (0 errors, 1 warnings)\n'
} )
}) }
})
it('skip the file if it does not exist', () => {
const cmd = `${ENV_VAR} npm run playlist:validate -- missing.m3u` it('skip the file if it does not exist', () => {
execSync(cmd, { encoding: 'utf8' }) const cmd = `${ENV_VAR} npm run playlist:validate -- missing.m3u`
}) execSync(cmd, { encoding: 'utf8' })
}) })
})

@ -1,26 +1,26 @@
import { pathToFileURL } from 'node:url' import { pathToFileURL } from 'node:url'
import { execSync } from 'child_process' import { execSync } from 'child_process'
import fs from 'fs-extra' import fs from 'fs-extra'
const ENV_VAR = const ENV_VAR =
'cross-env DATA_DIR=tests/__data__/input/data LOGS_DIR=tests/__data__/input/readme_update ROOT_DIR=tests/__data__/output' 'cross-env DATA_DIR=tests/__data__/input/data LOGS_DIR=tests/__data__/input/readme_update ROOT_DIR=tests/__data__/output'
beforeEach(() => { beforeEach(() => {
fs.emptyDirSync('tests/__data__/output') fs.emptyDirSync('tests/__data__/output')
}) })
describe('readme:update', () => { describe('readme:update', () => {
it('can update readme.md', () => { it('can update readme.md', () => {
const cmd = `${ENV_VAR} npm run readme:update` const cmd = `${ENV_VAR} npm run readme:update`
const stdout = execSync(cmd, { encoding: 'utf8' }) const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout) if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/PLAYLISTS.md')).toEqual( expect(content('tests/__data__/output/PLAYLISTS.md')).toEqual(
content('tests/__data__/expected/readme_update/playlists.md') content('tests/__data__/expected/readme_update/playlists.md')
) )
}) })
}) })
function content(filepath: string) { function content(filepath: string) {
return JSON.stringify(fs.readFileSync(pathToFileURL(filepath), { encoding: 'utf8' })) return JSON.stringify(fs.readFileSync(pathToFileURL(filepath), { encoding: 'utf8' }))
} }

@ -1,20 +1,20 @@
{ {
"compilerOptions": { "compilerOptions": {
"strict": true, "strict": true,
"target": "es2022", "target": "es2022",
"module": "nodeNext", "module": "nodeNext",
"moduleResolution": "nodeNext", "moduleResolution": "nodeNext",
"typeRoots": [ "typeRoots": [
"./scripts/types", "./scripts/types",
"./node_modules/@types" "./node_modules/@types"
], ],
"esModuleInterop": true "esModuleInterop": true
}, },
"ts-node": { "ts-node": {
"esm": true, "esm": true,
"transpileOnly": true "transpileOnly": true
}, },
"files": [ "files": [
"node_modules/jest-expect-message/types/index.d.ts" "node_modules/jest-expect-message/types/index.d.ts"
] ]
} }
Loading…
Cancel
Save