use splitted lodash modules for better efficiency

This commit is contained in:
theofficialomega
2025-07-31 11:22:35 +02:00
parent 44cfd13bf6
commit 17e3b4ddda
57 changed files with 4688 additions and 4626 deletions

474
README.md
View File

@@ -1,237 +1,237 @@
# EPG [![update](https://github.com/iptv-org/epg/actions/workflows/update.yml/badge.svg)](https://github.com/iptv-org/epg/actions/workflows/update.yml)
Tools for downloading the EPG (Electronic Program Guide) for thousands of TV channels from hundreds of sources.
## Table of contents
- ✨ [Installation](#installation)
- 🚀 [Usage](#usage)
- 💫 [Update](#update)
- 🐋 [Docker](#docker)
- 📺 [Playlists](#playlists)
- 🗄 [Database](#database)
- 👨‍💻 [API](#api)
- 📚 [Resources](#resources)
- 💬 [Discussions](#discussions)
- 🛠 [Contribution](#contribution)
- 📄 [License](#license)
## Installation
First, you need to install [Node.js](https://nodejs.org/en) on your computer. You will also need to install [Git](https://git-scm.com/downloads) to follow these instructions.
After that open the [Console](https://en.wikipedia.org/wiki/Windows_Console) (or [Terminal](<https://en.wikipedia.org/wiki/Terminal_(macOS)>) if you have macOS) and type the following command:
```sh
git clone --depth 1 -b master https://github.com/iptv-org/epg.git
```
Then navigate to the downloaded `epg` folder:
```sh
cd epg
```
And install all the dependencies:
```sh
npm install
```
## Usage
To start the download of the guide, select one of the supported sites from [SITES.md](SITES.md) file and paste its name into the command below:
```sh
npm run grab --- --site=example.com
```
Then run it and wait for the guide to finish downloading. When finished, a new `guide.xml` file will appear in the current directory.
You can also customize the behavior of the script using this options:
```sh
Usage: npm run grab --- [options]
Options:
-s, --site <name> Name of the site to parse
-c, --channels <path> Path to *.channels.xml file (required if the "--site" attribute is
not specified)
-o, --output <path> Path to output file (default: "guide.xml")
-l, --lang <codes> Allows you to restrict downloading to channels in specified languages only (example: "en,id")
-t, --timeout <milliseconds> Timeout for each request in milliseconds (default: 0)
-d, --delay <milliseconds> Delay between request in milliseconds (default: 0)
-x, --proxy <url> Use the specified proxy (example: "socks5://username:password@127.0.0.1:1234")
--days <days> Number of days for which the program will be loaded (defaults to the value from the site config)
--maxConnections <number> Number of concurrent requests (default: 1)
--gzip Specifies whether or not to create a compressed version of the guide (default: false)
--curl Display each request as CURL (default: false)
```
### Parallel downloading
By default, the guide for each channel is downloaded one by one, but you can change this behavior by increasing the number of simultaneous requests using the `--maxConnections` attribute:
```sh
npm run grab --- --site=example.com --maxConnections=10
```
But be aware that under heavy load, some sites may start return an error or completely block your access.
### Use custom channel list
Create an XML file and copy the descriptions of all the channels you need from the [/sites](sites) into it:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<channels>
<channel site="arirang.com" lang="en" xmltv_id="ArirangTV.kr" site_id="CH_K">Arirang TV</channel>
...
</channels>
```
And then specify the path to that file via the `--channels` attribute:
```sh
npm run grab --- --channels=path/to/custom.channels.xml
```
### Run on schedule
If you want to download guides on a schedule, you can use [cron](https://en.wikipedia.org/wiki/Cron) or any other task scheduler. Currently, we use a tool called `chronos` for this purpose.
To start it, you only need to specify the necessary `grab` command and [cron expression](https://crontab.guru/):
```sh
npx chronos --execute="npm run grab --- --site=example.com" --pattern="0 0,12 * * *" --log
```
For more info go to [chronos](https://github.com/freearhey/chronos) documentation.
### Access the guide by URL
You can make the guide available via URL by running your own server. The easiest way to do this is to run this command:
```sh
npx serve
```
After that, the guide will be available at the link:
```
http://localhost:3000/guide.xml
```
In addition it will be available to other devices on the same local network at the address:
```
http://<your_local_ip_address>:3000/guide.xml
```
For more info go to [serve](https://github.com/vercel/serve) documentation.
## Update
If you have downloaded the repository code according to the instructions above, then to update it will be enough to run the command:
```sh
git pull
```
And then update all the dependencies:
```sh
npm install
```
## Docker
### Build an image
```sh
docker build -t iptv-org/epg --no-cache .
```
### Create and run container
```sh
docker run -p 3000:3000 -v /path/to/channels.xml:/epg/channels.xml iptv-org/epg
```
By default, the guide will be downloaded every day at 00:00 UTC and saved to the `/epg/public/guide.xml` file inside the container.
From the outside, it will be available at this link:
```
http://localhost:3000/guide.xml
```
or
```
http://<your_local_ip_address>:3000/guide.xml
```
### Environment Variables
To fine-tune the execution, you can pass environment variables to the container as follows:
```sh
docker run \
-p 5000:3000 \
-v /path/to/channels.xml:/epg/channels.xml \
-e CRON_SCHEDULE="0 0,12 * * *" \
-e MAX_CONNECTIONS=10 \
-e GZIP=true \
-e CURL=true \
-e PROXY="socks5://127.0.0.1:1234" \
-e DAYS=14 \
-e TIMEOUT=5 \
-e DELAY=2 \
iptv-org/epg
```
| Variable | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------ |
| CRON_SCHEDULE | A [cron expression](https://crontab.guru/) describing the schedule of the guide loadings (default: "0 0 \* \* \*") |
| MAX_CONNECTIONS | Limit on the number of concurrent requests (default: 1) |
| GZIP | Boolean value indicating whether to create a compressed version of the guide (default: false) |
| CURL | Display each request as CURL (default: false) |
| PROXY | Use the specified proxy |
| DAYS | Number of days for which the guide will be loaded (defaults to the value from the site config) |
| TIMEOUT | Timeout for each request in milliseconds (default: 0) |
| DELAY | Delay between request in milliseconds (default: 0) |
## 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.
## API
The API documentation can be found in the [iptv-org/api](https://github.com/iptv-org/api) repository.
## 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.
## Discussions
If you have a question or an idea, you can post it in the [Discussions](https://github.com/orgs/iptv-org/discussions) tab.
## Contribution
Please make sure to read the [Contributing Guide](https://github.com/iptv-org/epg/blob/master/CONTRIBUTING.md) before sending [issue](https://github.com/iptv-org/epg/issues) or a [pull request](https://github.com/iptv-org/epg/pulls).
And thank you to everyone who has already contributed!
### Backers
<a href="https://opencollective.com/iptv-org"><img src="https://opencollective.com/iptv-org/backers.svg?width=890" /></a>
### Contributors
<a href="https://github.com/iptv-org/epg/graphs/contributors"><img src="https://opencollective.com/iptv-org/contributors.svg?width=890" /></a>
## License
[![CC0](http://mirrors.creativecommons.org/presskit/buttons/88x31/svg/cc-zero.svg)](LICENSE)
# EPG [![update](https://github.com/iptv-org/epg/actions/workflows/update.yml/badge.svg)](https://github.com/iptv-org/epg/actions/workflows/update.yml)
Tools for downloading the EPG (Electronic Program Guide) for thousands of TV channels from hundreds of sources.
## Table of contents
- ✨ [Installation](#installation)
- 🚀 [Usage](#usage)
- 💫 [Update](#update)
- 🐋 [Docker](#docker)
- 📺 [Playlists](#playlists)
- 🗄 [Database](#database)
- 👨‍💻 [API](#api)
- 📚 [Resources](#resources)
- 💬 [Discussions](#discussions)
- 🛠 [Contribution](#contribution)
- 📄 [License](#license)
## Installation
First, you need to install [Node.js](https://nodejs.org/en) on your computer. You will also need to install [Git](https://git-scm.com/downloads) to follow these instructions.
After that open the [Console](https://en.wikipedia.org/wiki/Windows_Console) (or [Terminal](<https://en.wikipedia.org/wiki/Terminal_(macOS)>) if you have macOS) and type the following command:
```sh
git clone --depth 1 -b master https://github.com/iptv-org/epg.git
```
Then navigate to the downloaded `epg` folder:
```sh
cd epg
```
And install all the dependencies:
```sh
npm install
```
## Usage
To start the download of the guide, select one of the supported sites from [SITES.md](SITES.md) file and paste its name into the command below:
```sh
npm run grab --- --site=example.com
```
Then run it and wait for the guide to finish downloading. When finished, a new `guide.xml` file will appear in the current directory.
You can also customize the behavior of the script using this options:
```sh
Usage: npm run grab --- [options]
Options:
-s, --site <name> Name of the site to parse
-c, --channels <path> Path to *.channels.xml file (required if the "--site" attribute is
not specified)
-o, --output <path> Path to output file (default: "guide.xml")
-l, --lang <codes> Allows you to restrict downloading to channels in specified languages only (example: "en,id")
-t, --timeout <milliseconds> Timeout for each request in milliseconds (default: 0)
-d, --delay <milliseconds> Delay between request in milliseconds (default: 0)
-x, --proxy <url> Use the specified proxy (example: "socks5://username:password@127.0.0.1:1234")
--days <days> Number of days for which the program will be loaded (defaults to the value from the site config)
--maxConnections <number> Number of concurrent requests (default: 1)
--gzip Specifies whether or not to create a compressed version of the guide (default: false)
--curl Display each request as CURL (default: false)
```
### Parallel downloading
By default, the guide for each channel is downloaded one by one, but you can change this behavior by increasing the number of simultaneous requests using the `--maxConnections` attribute:
```sh
npm run grab --- --site=example.com --maxConnections=10
```
But be aware that under heavy load, some sites may start return an error or completely block your access.
### Use custom channel list
Create an XML file and copy the descriptions of all the channels you need from the [/sites](sites) into it:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<channels>
<channel site="arirang.com" lang="en" xmltv_id="ArirangTV.kr" site_id="CH_K">Arirang TV</channel>
...
</channels>
```
And then specify the path to that file via the `--channels` attribute:
```sh
npm run grab --- --channels=path/to/custom.channels.xml
```
### Run on schedule
If you want to download guides on a schedule, you can use [cron](https://en.wikipedia.org/wiki/Cron) or any other task scheduler. Currently, we use a tool called `chronos` for this purpose.
To start it, you only need to specify the necessary `grab` command and [cron expression](https://crontab.guru/):
```sh
npx chronos --execute="npm run grab --- --site=example.com" --pattern="0 0,12 * * *" --log
```
For more info go to [chronos](https://github.com/freearhey/chronos) documentation.
### Access the guide by URL
You can make the guide available via URL by running your own server. The easiest way to do this is to run this command:
```sh
npx serve
```
After that, the guide will be available at the link:
```
http://localhost:3000/guide.xml
```
In addition it will be available to other devices on the same local network at the address:
```
http://<your_local_ip_address>:3000/guide.xml
```
For more info go to [serve](https://github.com/vercel/serve) documentation.
## Update
If you have downloaded the repository code according to the instructions above, then to update it will be enough to run the command:
```sh
git pull
```
And then update all the dependencies:
```sh
npm install
```
## Docker
### Build an image
```sh
docker build -t iptv-org/epg --no-cache .
```
### Create and run container
```sh
docker run -p 3000:3000 -v /path/to/channels.xml:/epg/channels.xml iptv-org/epg
```
By default, the guide will be downloaded every day at 00:00 UTC and saved to the `/epg/public/guide.xml` file inside the container.
From the outside, it will be available at this link:
```
http://localhost:3000/guide.xml
```
or
```
http://<your_local_ip_address>:3000/guide.xml
```
### Environment Variables
To fine-tune the execution, you can pass environment variables to the container as follows:
```sh
docker run \
-p 5000:3000 \
-v /path/to/channels.xml:/epg/channels.xml \
-e CRON_SCHEDULE="0 0,12 * * *" \
-e MAX_CONNECTIONS=10 \
-e GZIP=true \
-e CURL=true \
-e PROXY="socks5://127.0.0.1:1234" \
-e DAYS=14 \
-e TIMEOUT=5 \
-e DELAY=2 \
iptv-org/epg
```
| Variable | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------ |
| CRON_SCHEDULE | A [cron expression](https://crontab.guru/) describing the schedule of the guide loadings (default: "0 0 \* \* \*") |
| MAX_CONNECTIONS | Limit on the number of concurrent requests (default: 1) |
| GZIP | Boolean value indicating whether to create a compressed version of the guide (default: false) |
| CURL | Display each request as CURL (default: false) |
| PROXY | Use the specified proxy |
| DAYS | Number of days for which the guide will be loaded (defaults to the value from the site config) |
| TIMEOUT | Timeout for each request in milliseconds (default: 0) |
| DELAY | Delay between request in milliseconds (default: 0) |
## 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.
## API
The API documentation can be found in the [iptv-org/api](https://github.com/iptv-org/api) repository.
## 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.
## Discussions
If you have a question or an idea, you can post it in the [Discussions](https://github.com/orgs/iptv-org/discussions) tab.
## Contribution
Please make sure to read the [Contributing Guide](https://github.com/iptv-org/epg/blob/master/CONTRIBUTING.md) before sending [issue](https://github.com/iptv-org/epg/issues) or a [pull request](https://github.com/iptv-org/epg/pulls).
And thank you to everyone who has already contributed!
### Backers
<a href="https://opencollective.com/iptv-org"><img src="https://opencollective.com/iptv-org/backers.svg?width=890" /></a>
### Contributors
<a href="https://github.com/iptv-org/epg/graphs/contributors"><img src="https://opencollective.com/iptv-org/contributors.svg?width=890" /></a>
## License
[![CC0](http://mirrors.creativecommons.org/presskit/buttons/88x31/svg/cc-zero.svg)](LICENSE)

131
package-lock.json generated
View File

@@ -26,6 +26,10 @@
"@types/inquirer": "^9.0.8",
"@types/jest": "^30.0.0",
"@types/langs": "^2.0.5",
"@types/lodash.orderby": "^4.6.9",
"@types/lodash.sortby": "^4.7.9",
"@types/lodash.startcase": "^4.4.9",
"@types/lodash.uniqby": "^4.7.9",
"@types/node": "^24.1.0",
"@types/node-cleanup": "^2.1.5",
"@types/numeral": "^2.0.5",
@@ -57,6 +61,10 @@
"jest-offline": "^1.0.1",
"langs": "^2.0.0",
"libxml2-wasm": "^0.5.0",
"lodash.orderby": "^4.6.0",
"lodash.sortby": "^4.7.0",
"lodash.startcase": "^4.4.0",
"lodash.uniqby": "^4.7.0",
"luxon": "^3.7.1",
"mockdate": "^3.0.5",
"nedb-promises": "^6.2.3",
@@ -3306,6 +3314,48 @@
"integrity": "sha512-DIUKT4mkbTBxSrX6lmnQR888ObeFVVo1uNEqBH5/ddQHpnG4CA24DibpK7aO8QAcJEZUTcIx0F96TWuzVT9Z4g==",
"license": "MIT"
},
"node_modules/@types/lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
"license": "MIT"
},
"node_modules/@types/lodash.orderby": {
"version": "4.6.9",
"resolved": "https://registry.npmjs.org/@types/lodash.orderby/-/lodash.orderby-4.6.9.tgz",
"integrity": "sha512-T9o2wkIJOmxXwVTPTmwJ59W6eTi2FseiLR369fxszG649Po/xe9vqFNhf/MtnvT5jrbDiyWKxPFPZbpSVK0SVQ==",
"license": "MIT",
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/lodash.sortby": {
"version": "4.7.9",
"resolved": "https://registry.npmjs.org/@types/lodash.sortby/-/lodash.sortby-4.7.9.tgz",
"integrity": "sha512-PDmjHnOlndLS59GofH0pnxIs+n9i4CWeXGErSB5JyNFHu2cmvW6mQOaUKjG8EDPkni14IgF8NsRW8bKvFzTm9A==",
"license": "MIT",
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/lodash.startcase": {
"version": "4.4.9",
"resolved": "https://registry.npmjs.org/@types/lodash.startcase/-/lodash.startcase-4.4.9.tgz",
"integrity": "sha512-C0M4DlN1pnn2vEEhLHkTHxiRZ+3GlTegpoAEHHGXnuJkSOXyJMHGiSc+SLRzBlFZWHsBkixe6FqvEAEU04g14g==",
"license": "MIT",
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/lodash.uniqby": {
"version": "4.7.9",
"resolved": "https://registry.npmjs.org/@types/lodash.uniqby/-/lodash.uniqby-4.7.9.tgz",
"integrity": "sha512-rjrXji/seS6BZJRgXrU2h6FqxRVufsbq/HE0Tx0SdgbtlWr2YmD/M64BlYEYYlaMcpZwy32IYVkMfUMYlPuv0w==",
"license": "MIT",
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/node": {
"version": "24.1.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz",
@@ -8486,6 +8536,30 @@
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
},
"node_modules/lodash.orderby": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.orderby/-/lodash.orderby-4.6.0.tgz",
"integrity": "sha512-T0rZxKmghOOf5YPnn8EY5iLYeWCpZq8G41FfqoVHH5QDTAFaghJRmAdLiadEDq+ztgM2q5PjA+Z1fOwGrLgmtg==",
"license": "MIT"
},
"node_modules/lodash.sortby": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
"integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==",
"license": "MIT"
},
"node_modules/lodash.startcase": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz",
"integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==",
"license": "MIT"
},
"node_modules/lodash.uniqby": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz",
"integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==",
"license": "MIT"
},
"node_modules/logform": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",
@@ -13391,6 +13465,43 @@
"resolved": "https://registry.npmjs.org/@types/langs/-/langs-2.0.5.tgz",
"integrity": "sha512-DIUKT4mkbTBxSrX6lmnQR888ObeFVVo1uNEqBH5/ddQHpnG4CA24DibpK7aO8QAcJEZUTcIx0F96TWuzVT9Z4g=="
},
"@types/lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA=="
},
"@types/lodash.orderby": {
"version": "4.6.9",
"resolved": "https://registry.npmjs.org/@types/lodash.orderby/-/lodash.orderby-4.6.9.tgz",
"integrity": "sha512-T9o2wkIJOmxXwVTPTmwJ59W6eTi2FseiLR369fxszG649Po/xe9vqFNhf/MtnvT5jrbDiyWKxPFPZbpSVK0SVQ==",
"requires": {
"@types/lodash": "*"
}
},
"@types/lodash.sortby": {
"version": "4.7.9",
"resolved": "https://registry.npmjs.org/@types/lodash.sortby/-/lodash.sortby-4.7.9.tgz",
"integrity": "sha512-PDmjHnOlndLS59GofH0pnxIs+n9i4CWeXGErSB5JyNFHu2cmvW6mQOaUKjG8EDPkni14IgF8NsRW8bKvFzTm9A==",
"requires": {
"@types/lodash": "*"
}
},
"@types/lodash.startcase": {
"version": "4.4.9",
"resolved": "https://registry.npmjs.org/@types/lodash.startcase/-/lodash.startcase-4.4.9.tgz",
"integrity": "sha512-C0M4DlN1pnn2vEEhLHkTHxiRZ+3GlTegpoAEHHGXnuJkSOXyJMHGiSc+SLRzBlFZWHsBkixe6FqvEAEU04g14g==",
"requires": {
"@types/lodash": "*"
}
},
"@types/lodash.uniqby": {
"version": "4.7.9",
"resolved": "https://registry.npmjs.org/@types/lodash.uniqby/-/lodash.uniqby-4.7.9.tgz",
"integrity": "sha512-rjrXji/seS6BZJRgXrU2h6FqxRVufsbq/HE0Tx0SdgbtlWr2YmD/M64BlYEYYlaMcpZwy32IYVkMfUMYlPuv0w==",
"requires": {
"@types/lodash": "*"
}
},
"@types/node": {
"version": "24.1.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz",
@@ -16969,6 +17080,26 @@
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
},
"lodash.orderby": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.orderby/-/lodash.orderby-4.6.0.tgz",
"integrity": "sha512-T0rZxKmghOOf5YPnn8EY5iLYeWCpZq8G41FfqoVHH5QDTAFaghJRmAdLiadEDq+ztgM2q5PjA+Z1fOwGrLgmtg=="
},
"lodash.sortby": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
"integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="
},
"lodash.startcase": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz",
"integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg=="
},
"lodash.uniqby": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz",
"integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww=="
},
"logform": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",

View File

@@ -54,6 +54,10 @@
"@types/inquirer": "^9.0.8",
"@types/jest": "^30.0.0",
"@types/langs": "^2.0.5",
"@types/lodash.orderby": "^4.6.9",
"@types/lodash.sortby": "^4.7.9",
"@types/lodash.startcase": "^4.4.9",
"@types/lodash.uniqby": "^4.7.9",
"@types/node": "^24.1.0",
"@types/node-cleanup": "^2.1.5",
"@types/numeral": "^2.0.5",
@@ -85,6 +89,10 @@
"jest-offline": "^1.0.1",
"langs": "^2.0.0",
"libxml2-wasm": "^0.5.0",
"lodash.orderby": "^4.6.0",
"lodash.sortby": "^4.7.0",
"lodash.startcase": "^4.4.0",
"lodash.uniqby": "^4.7.0",
"luxon": "^3.7.1",
"mockdate": "^3.0.5",
"nedb-promises": "^6.2.3",

View File

@@ -1,41 +1,41 @@
import { Logger, Collection, Storage } from '@freearhey/core'
import { SITES_DIR, API_DIR } from '../../constants'
import { GuideChannel } from '../../models'
import { ChannelsParser } from '../../core'
import epgGrabber from 'epg-grabber'
import path from 'path'
async function main() {
const logger = new Logger()
logger.start('staring...')
logger.info('loading channels...')
const sitesStorage = new Storage(SITES_DIR)
const parser = new ChannelsParser({
storage: sitesStorage
})
const files: string[] = await sitesStorage.list('**/*.channels.xml')
const channels = new Collection()
for (const filepath of files) {
const channelList = await parser.parse(filepath)
channelList.channels.forEach((data: epgGrabber.Channel) => {
channels.add(new GuideChannel(data))
})
}
logger.info(`found ${channels.count()} channel(s)`)
const output = channels.map((channel: GuideChannel) => channel.toJSON())
const apiStorage = new Storage(API_DIR)
const outputFilename = 'guides.json'
await apiStorage.save('guides.json', output.toJSON())
logger.info(`saved to "${path.join(API_DIR, outputFilename)}"`)
}
main()
import { Logger, Collection, Storage } from '@freearhey/core'
import { SITES_DIR, API_DIR } from '../../constants'
import { GuideChannel } from '../../models'
import { ChannelsParser } from '../../core'
import epgGrabber from 'epg-grabber'
import path from 'path'
async function main() {
const logger = new Logger()
logger.start('staring...')
logger.info('loading channels...')
const sitesStorage = new Storage(SITES_DIR)
const parser = new ChannelsParser({
storage: sitesStorage
})
const files: string[] = await sitesStorage.list('**/*.channels.xml')
const channels = new Collection()
for (const filepath of files) {
const channelList = await parser.parse(filepath)
channelList.channels.forEach((data: epgGrabber.Channel) => {
channels.add(new GuideChannel(data))
})
}
logger.info(`found ${channels.count()} channel(s)`)
const output = channels.map((channel: GuideChannel) => channel.toJSON())
const apiStorage = new Storage(API_DIR)
const outputFilename = 'guides.json'
await apiStorage.save('guides.json', output.toJSON())
logger.info(`saved to "${path.join(API_DIR, outputFilename)}"`)
}
main()

View File

@@ -1,25 +1,25 @@
import { DATA_DIR } from '../../constants'
import { Storage } from '@freearhey/core'
import { DataLoader } from '../../core'
async function main() {
const storage = new Storage(DATA_DIR)
const loader = new DataLoader({ storage })
await Promise.all([
loader.download('blocklist.json'),
loader.download('categories.json'),
loader.download('channels.json'),
loader.download('countries.json'),
loader.download('languages.json'),
loader.download('regions.json'),
loader.download('subdivisions.json'),
loader.download('feeds.json'),
loader.download('timezones.json'),
loader.download('guides.json'),
loader.download('streams.json'),
loader.download('logos.json')
])
}
main()
import { DATA_DIR } from '../../constants'
import { Storage } from '@freearhey/core'
import { DataLoader } from '../../core'
async function main() {
const storage = new Storage(DATA_DIR)
const loader = new DataLoader({ storage })
await Promise.all([
loader.download('blocklist.json'),
loader.download('categories.json'),
loader.download('channels.json'),
loader.download('countries.json'),
loader.download('languages.json'),
loader.download('regions.json'),
loader.download('subdivisions.json'),
loader.download('feeds.json'),
loader.download('timezones.json'),
loader.download('guides.json'),
loader.download('streams.json'),
loader.download('logos.json')
])
}
main()

View File

@@ -1,109 +1,109 @@
import chalk from 'chalk'
import { program } from 'commander'
import { Storage, File } from '@freearhey/core'
import { XmlDocument, XsdValidator, XmlValidateError, ErrorDetail } from 'libxml2-wasm'
const xsd = `<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="channels">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="channel"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="channel">
<xs:complexType mixed="true">
<xs:attribute use="required" ref="site"/>
<xs:attribute use="required" ref="lang"/>
<xs:attribute use="required" ref="site_id"/>
<xs:attribute name="xmltv_id" use="required" type="xs:string"/>
<xs:attribute name="logo" type="xs:string"/>
<xs:attribute name="lcn" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:attribute name="site">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="site_id">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="lang">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:schema>`
program.argument('[filepath...]', 'Path to *.channels.xml files to check').parse(process.argv)
async function main() {
const storage = new Storage()
let errors: ErrorDetail[] = []
const files = program.args.length ? program.args : await storage.list('sites/**/*.channels.xml')
for (const filepath of files) {
const file = new File(filepath)
if (file.extension() !== 'xml') continue
const xml = await storage.load(filepath)
let localErrors: ErrorDetail[] = []
try {
const schema = XmlDocument.fromString(xsd)
const validator = XsdValidator.fromDoc(schema)
const doc = XmlDocument.fromString(xml)
validator.validate(doc)
schema.dispose()
validator.dispose()
doc.dispose()
} catch (_error) {
const error = _error as XmlValidateError
localErrors = localErrors.concat(error.details)
}
xml.split('\n').forEach((line: string, lineIndex: number) => {
const found = line.match(/='/)
if (found) {
const colIndex = found.index || 0
localErrors.push({
line: lineIndex + 1,
col: colIndex + 1,
message: 'Single quotes cannot be used in attributes'
})
}
})
if (localErrors.length) {
console.log(`\n${chalk.underline(filepath)}`)
localErrors.forEach((error: ErrorDetail) => {
const position = `${error.line}:${error.col}`
console.log(` ${chalk.gray(position.padEnd(4, ' '))} ${error.message.trim()}`)
})
errors = errors.concat(localErrors)
}
}
if (errors.length) {
console.log(chalk.red(`\n${errors.length} error(s)`))
process.exit(1)
}
}
main()
import chalk from 'chalk'
import { program } from 'commander'
import { Storage, File } from '@freearhey/core'
import { XmlDocument, XsdValidator, XmlValidateError, ErrorDetail } from 'libxml2-wasm'
const xsd = `<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="channels">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="channel"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="channel">
<xs:complexType mixed="true">
<xs:attribute use="required" ref="site"/>
<xs:attribute use="required" ref="lang"/>
<xs:attribute use="required" ref="site_id"/>
<xs:attribute name="xmltv_id" use="required" type="xs:string"/>
<xs:attribute name="logo" type="xs:string"/>
<xs:attribute name="lcn" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:attribute name="site">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="site_id">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="lang">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:schema>`
program.argument('[filepath...]', 'Path to *.channels.xml files to check').parse(process.argv)
async function main() {
const storage = new Storage()
let errors: ErrorDetail[] = []
const files = program.args.length ? program.args : await storage.list('sites/**/*.channels.xml')
for (const filepath of files) {
const file = new File(filepath)
if (file.extension() !== 'xml') continue
const xml = await storage.load(filepath)
let localErrors: ErrorDetail[] = []
try {
const schema = XmlDocument.fromString(xsd)
const validator = XsdValidator.fromDoc(schema)
const doc = XmlDocument.fromString(xml)
validator.validate(doc)
schema.dispose()
validator.dispose()
doc.dispose()
} catch (_error) {
const error = _error as XmlValidateError
localErrors = localErrors.concat(error.details)
}
xml.split('\n').forEach((line: string, lineIndex: number) => {
const found = line.match(/='/)
if (found) {
const colIndex = found.index || 0
localErrors.push({
line: lineIndex + 1,
col: colIndex + 1,
message: 'Single quotes cannot be used in attributes'
})
}
})
if (localErrors.length) {
console.log(`\n${chalk.underline(filepath)}`)
localErrors.forEach((error: ErrorDetail) => {
const position = `${error.line}:${error.col}`
console.log(` ${chalk.gray(position.padEnd(4, ' '))} ${error.message.trim()}`)
})
errors = errors.concat(localErrors)
}
}
if (errors.length) {
console.log(chalk.red(`\n${errors.length} error(s)`))
process.exit(1)
}
}
main()

View File

@@ -1,76 +1,76 @@
import { IssueLoader, HTMLTable, ChannelsParser } from '../../core'
import { Logger, Storage, Collection } from '@freearhey/core'
import { ChannelList, Issue, Site } from '../../models'
import { SITES_DIR, ROOT_DIR } from '../../constants'
import { Channel } from 'epg-grabber'
async function main() {
const logger = new Logger({ level: -999 })
const issueLoader = new IssueLoader()
const sitesStorage = new Storage(SITES_DIR)
const sites = new Collection()
logger.info('loading channels...')
const channelsParser = new ChannelsParser({
storage: sitesStorage
})
logger.info('loading list of sites')
const folders = await sitesStorage.list('*/')
logger.info('loading issues...')
const issues = await issueLoader.load()
logger.info('putting the data together...')
const brokenGuideReports = issues.filter(issue =>
issue.labels.find((label: string) => label === 'broken guide')
)
for (const domain of folders) {
const filteredIssues = brokenGuideReports.filter(
(issue: Issue) => domain === issue.data.get('site')
)
const site = new Site({
domain,
issues: filteredIssues
})
const files = await sitesStorage.list(`${domain}/*.channels.xml`)
for (const filepath of files) {
const channelList: ChannelList = await channelsParser.parse(filepath)
site.totalChannels += channelList.channels.count()
site.markedChannels += channelList.channels
.filter((channel: Channel) => channel.xmltv_id)
.count()
}
sites.add(site)
}
logger.info('creating sites table...')
const tableData = new Collection()
sites.forEach((site: Site) => {
tableData.add([
{ value: `<a href="sites/${site.domain}">${site.domain}</a>` },
{ value: site.totalChannels, align: 'right' },
{ value: site.markedChannels, align: 'right' },
{ value: site.getStatus().emoji, align: 'center' },
{ value: site.getIssues().all().join(', ') }
])
})
logger.info('updating sites.md...')
const table = new HTMLTable(tableData.all(), [
{ name: 'Site', align: 'left' },
{ name: 'Channels<br>(total / with xmltv-id)', colspan: 2, align: 'left' },
{ name: 'Status', align: 'left' },
{ name: 'Notes', align: 'left' }
])
const rootStorage = new Storage(ROOT_DIR)
const sitesTemplate = await new Storage().load('scripts/templates/_sites.md')
const sitesContent = sitesTemplate.replace('_TABLE_', table.toString())
await rootStorage.save('SITES.md', sitesContent)
}
main()
import { IssueLoader, HTMLTable, ChannelsParser } from '../../core'
import { Logger, Storage, Collection } from '@freearhey/core'
import { ChannelList, Issue, Site } from '../../models'
import { SITES_DIR, ROOT_DIR } from '../../constants'
import { Channel } from 'epg-grabber'
async function main() {
const logger = new Logger({ level: -999 })
const issueLoader = new IssueLoader()
const sitesStorage = new Storage(SITES_DIR)
const sites = new Collection()
logger.info('loading channels...')
const channelsParser = new ChannelsParser({
storage: sitesStorage
})
logger.info('loading list of sites')
const folders = await sitesStorage.list('*/')
logger.info('loading issues...')
const issues = await issueLoader.load()
logger.info('putting the data together...')
const brokenGuideReports = issues.filter(issue =>
issue.labels.find((label: string) => label === 'broken guide')
)
for (const domain of folders) {
const filteredIssues = brokenGuideReports.filter(
(issue: Issue) => domain === issue.data.get('site')
)
const site = new Site({
domain,
issues: filteredIssues
})
const files = await sitesStorage.list(`${domain}/*.channels.xml`)
for (const filepath of files) {
const channelList: ChannelList = await channelsParser.parse(filepath)
site.totalChannels += channelList.channels.count()
site.markedChannels += channelList.channels
.filter((channel: Channel) => channel.xmltv_id)
.count()
}
sites.add(site)
}
logger.info('creating sites table...')
const tableData = new Collection()
sites.forEach((site: Site) => {
tableData.add([
{ value: `<a href="sites/${site.domain}">${site.domain}</a>` },
{ value: site.totalChannels, align: 'right' },
{ value: site.markedChannels, align: 'right' },
{ value: site.getStatus().emoji, align: 'center' },
{ value: site.getIssues().all().join(', ') }
])
})
logger.info('updating sites.md...')
const table = new HTMLTable(tableData.all(), [
{ name: 'Site', align: 'left' },
{ name: 'Channels<br>(total / with xmltv-id)', colspan: 2, align: 'left' },
{ name: 'Status', align: 'left' },
{ name: 'Notes', align: 'left' }
])
const rootStorage = new Storage(ROOT_DIR)
const sitesTemplate = await new Storage().load('scripts/templates/_sites.md')
const sitesContent = sitesTemplate.replace('_TABLE_', table.toString())
await rootStorage.save('SITES.md', sitesContent)
}
main()

View File

@@ -1,103 +1,103 @@
import type { DataLoaderProps, DataLoaderData } from '../types/dataLoader'
import cliProgress, { MultiBar } from 'cli-progress'
import { Storage } from '@freearhey/core'
import { ApiClient } from './apiClient'
import numeral from 'numeral'
export class DataLoader {
client: ApiClient
storage: Storage
progressBar: MultiBar
constructor(props: DataLoaderProps) {
this.client = new ApiClient()
this.storage = props.storage
this.progressBar = new cliProgress.MultiBar({
stopOnComplete: true,
hideCursor: true,
forceRedraw: true,
barsize: 36,
format(options, params, payload) {
const filename = payload.filename.padEnd(18, ' ')
const barsize = options.barsize || 40
const percent = (params.progress * 100).toFixed(2)
const speed = payload.speed ? numeral(payload.speed).format('0.0 b') + '/s' : 'N/A'
const total = numeral(params.total).format('0.0 b')
const completeSize = Math.round(params.progress * barsize)
const incompleteSize = barsize - completeSize
const bar =
options.barCompleteString && options.barIncompleteString
? options.barCompleteString.substr(0, completeSize) +
options.barGlue +
options.barIncompleteString.substr(0, incompleteSize)
: '-'.repeat(barsize)
return `${filename} [${bar}] ${percent}% | ETA: ${params.eta}s | ${total} | ${speed}`
}
})
}
async load(): Promise<DataLoaderData> {
const [
countries,
regions,
subdivisions,
languages,
categories,
blocklist,
channels,
feeds,
timezones,
guides,
streams,
logos
] = await Promise.all([
this.storage.json('countries.json'),
this.storage.json('regions.json'),
this.storage.json('subdivisions.json'),
this.storage.json('languages.json'),
this.storage.json('categories.json'),
this.storage.json('blocklist.json'),
this.storage.json('channels.json'),
this.storage.json('feeds.json'),
this.storage.json('timezones.json'),
this.storage.json('guides.json'),
this.storage.json('streams.json'),
this.storage.json('logos.json')
])
return {
countries,
regions,
subdivisions,
languages,
categories,
blocklist,
channels,
feeds,
timezones,
guides,
streams,
logos
}
}
async download(filename: string) {
if (!this.storage || !this.progressBar) return
const stream = await this.storage.createStream(filename)
const progressBar = this.progressBar.create(0, 0, { filename })
this.client
.get(filename, {
responseType: 'stream',
onDownloadProgress({ total, loaded, rate }) {
if (total) progressBar.setTotal(total)
progressBar.update(loaded, { speed: rate })
}
})
.then(response => {
response.data.pipe(stream)
})
}
}
import type { DataLoaderProps, DataLoaderData } from '../types/dataLoader'
import cliProgress, { MultiBar } from 'cli-progress'
import { Storage } from '@freearhey/core'
import { ApiClient } from './apiClient'
import numeral from 'numeral'
export class DataLoader {
client: ApiClient
storage: Storage
progressBar: MultiBar
constructor(props: DataLoaderProps) {
this.client = new ApiClient()
this.storage = props.storage
this.progressBar = new cliProgress.MultiBar({
stopOnComplete: true,
hideCursor: true,
forceRedraw: true,
barsize: 36,
format(options, params, payload) {
const filename = payload.filename.padEnd(18, ' ')
const barsize = options.barsize || 40
const percent = (params.progress * 100).toFixed(2)
const speed = payload.speed ? numeral(payload.speed).format('0.0 b') + '/s' : 'N/A'
const total = numeral(params.total).format('0.0 b')
const completeSize = Math.round(params.progress * barsize)
const incompleteSize = barsize - completeSize
const bar =
options.barCompleteString && options.barIncompleteString
? options.barCompleteString.substr(0, completeSize) +
options.barGlue +
options.barIncompleteString.substr(0, incompleteSize)
: '-'.repeat(barsize)
return `${filename} [${bar}] ${percent}% | ETA: ${params.eta}s | ${total} | ${speed}`
}
})
}
async load(): Promise<DataLoaderData> {
const [
countries,
regions,
subdivisions,
languages,
categories,
blocklist,
channels,
feeds,
timezones,
guides,
streams,
logos
] = await Promise.all([
this.storage.json('countries.json'),
this.storage.json('regions.json'),
this.storage.json('subdivisions.json'),
this.storage.json('languages.json'),
this.storage.json('categories.json'),
this.storage.json('blocklist.json'),
this.storage.json('channels.json'),
this.storage.json('feeds.json'),
this.storage.json('timezones.json'),
this.storage.json('guides.json'),
this.storage.json('streams.json'),
this.storage.json('logos.json')
])
return {
countries,
regions,
subdivisions,
languages,
categories,
blocklist,
channels,
feeds,
timezones,
guides,
streams,
logos
}
}
async download(filename: string) {
if (!this.storage || !this.progressBar) return
const stream = await this.storage.createStream(filename)
const progressBar = this.progressBar.create(0, 0, { filename })
this.client
.get(filename, {
responseType: 'stream',
onDownloadProgress({ total, loaded, rate }) {
if (total) progressBar.setTotal(total)
progressBar.update(loaded, { speed: rate })
}
})
.then(response => {
response.data.pipe(stream)
})
}
}

View File

@@ -1,14 +1,14 @@
export * from './apiClient'
export * from './channelsParser'
export * from './configLoader'
export * from './dataLoader'
export * from './dataProcessor'
export * from './grabber'
export * from './guideManager'
export * from './htmlTable'
export * from './issueLoader'
export * from './issueParser'
export * from './job'
export * from './proxyParser'
export * from './queue'
export * from './queueCreator'
export * from './apiClient'
export * from './channelsParser'
export * from './configLoader'
export * from './dataLoader'
export * from './dataProcessor'
export * from './grabber'
export * from './guideManager'
export * from './htmlTable'
export * from './issueLoader'
export * from './issueParser'
export * from './job'
export * from './proxyParser'
export * from './queue'
export * from './queueCreator'

View File

@@ -1,37 +1,37 @@
import { restEndpointMethods } from '@octokit/plugin-rest-endpoint-methods'
import { paginateRest } from '@octokit/plugin-paginate-rest'
import { TESTING, OWNER, REPO } from '../constants'
import { Collection } from '@freearhey/core'
import { Octokit } from '@octokit/core'
import { IssueParser } from './'
const CustomOctokit = Octokit.plugin(paginateRest, restEndpointMethods)
const octokit = new CustomOctokit()
export class IssueLoader {
async load(props?: { labels: string[] | string }) {
let labels = ''
if (props && props.labels) {
labels = Array.isArray(props.labels) ? props.labels.join(',') : props.labels
}
let issues: object[] = []
if (TESTING) {
issues = (await import('../../tests/__data__/input/sites_update/issues.mjs')).default
} else {
issues = await octokit.paginate(octokit.rest.issues.listForRepo, {
owner: OWNER,
repo: REPO,
per_page: 100,
labels,
state: 'open',
headers: {
'X-GitHub-Api-Version': '2022-11-28'
}
})
}
const parser = new IssueParser()
return new Collection(issues).map(parser.parse)
}
}
import { restEndpointMethods } from '@octokit/plugin-rest-endpoint-methods'
import { paginateRest } from '@octokit/plugin-paginate-rest'
import { TESTING, OWNER, REPO } from '../constants'
import { Collection } from '@freearhey/core'
import { Octokit } from '@octokit/core'
import { IssueParser } from './'
const CustomOctokit = Octokit.plugin(paginateRest, restEndpointMethods)
const octokit = new CustomOctokit()
export class IssueLoader {
async load(props?: { labels: string[] | string }) {
let labels = ''
if (props && props.labels) {
labels = Array.isArray(props.labels) ? props.labels.join(',') : props.labels
}
let issues: object[] = []
if (TESTING) {
issues = (await import('../../tests/__data__/input/sites_update/issues.mjs')).default
} else {
issues = await octokit.paginate(octokit.rest.issues.listForRepo, {
owner: OWNER,
repo: REPO,
per_page: 100,
labels,
state: 'open',
headers: {
'X-GitHub-Api-Version': '2022-11-28'
}
})
}
const parser = new IssueParser()
return new Collection(issues).map(parser.parse)
}
}

View File

@@ -1,77 +0,0 @@
/**
* Sorts an array by the result of running each element through an iteratee function.
* Creates a shallow copy of the array before sorting to avoid mutating the original.
*
* @param {Array} arr - The array to sort
* @param {Function} fn - The iteratee function to compute sort values
* @returns {Array} A new sorted array
*
* @example
* const users = [{name: 'john', age: 30}, {name: 'jane', age: 25}];
* sortBy(users, x => x.age); // [{name: 'jane', age: 25}, {name: 'john', age: 30}]
*/
export const sortBy = <T>(arr: T[], fn: (item: T) => number | string): T[] =>
[...arr].sort((a, b) => (fn(a) > fn(b) ? 1 : -1))
/**
* Sorts an array by multiple criteria with customizable sort orders.
* Supports ascending (default) and descending order for each criterion.
*
* @param {Array} arr - The array to sort
* @param {Array<Function>} fns - Array of iteratee functions to compute sort values
* @param {Array<string>} orders - Array of sort orders ('asc' or 'desc'), defaults to all 'asc'
* @returns {Array} A new sorted array
*
* @example
* const users = [{name: 'john', age: 30}, {name: 'jane', age: 25}, {name: 'bob', age: 30}];
* orderBy(users, [x => x.age, x => x.name], ['desc', 'asc']);
* // [{name: 'bob', age: 30}, {name: 'john', age: 30}, {name: 'jane', age: 25}]
*/
export const orderBy = (
arr: unknown[],
fns: ((item: unknown) => string | number)[],
orders: string[] = []
): unknown[] =>
[...arr].sort((a, b) =>
fns.reduce(
(acc, fn, i) =>
acc ||
((orders[i] === 'desc' ? fn(b) > fn(a) : fn(a) > fn(b)) ? 1 : fn(a) === fn(b) ? 0 : -1),
0
)
)
/**
* Creates a duplicate-free version of an array using an iteratee function to generate
* the criterion by which uniqueness is computed. Only the first occurrence of each
* element is kept.
*
* @param {Array} arr - The array to inspect
* @param {Function} fn - The iteratee function to compute uniqueness criterion
* @returns {Array} A new duplicate-free array
*
* @example
* const users = [{id: 1, name: 'john'}, {id: 2, name: 'jane'}, {id: 1, name: 'john'}];
* uniqBy(users, x => x.id); // [{id: 1, name: 'john'}, {id: 2, name: 'jane'}]
*/
export const uniqBy = <T>(arr: T[], fn: (item: T) => unknown): T[] =>
arr.filter((item, index) => arr.findIndex(x => fn(x) === fn(item)) === index)
/**
* Converts a string to start case (capitalizes the first letter of each word).
* Handles camelCase, snake_case, kebab-case, and regular spaces.
*
* @param {string} str - The string to convert
* @returns {string} The start case string
*
* @example
* startCase('hello_world'); // "Hello World"
* startCase('helloWorld'); // "Hello World"
* startCase('hello-world'); // "Hello World"
* startCase('hello world'); // "Hello World"
*/
export const startCase = (str: string): string =>
str
.replace(/([a-z])([A-Z])/g, '$1 $2') // Split camelCase
.replace(/[_-]/g, ' ') // Replace underscores and hyphens with spaces
.replace(/\b\w/g, c => c.toUpperCase()) // Capitalize first letter of each word

View File

@@ -1 +0,0 @@
export * from './functions'

View File

@@ -1,59 +1,59 @@
import { Dictionary } from '@freearhey/core'
import epgGrabber from 'epg-grabber'
import { Feed, Channel } from '.'
export class GuideChannel {
channelId?: string
channel?: Channel
feedId?: string
feed?: Feed
xmltvId?: string
languageCode?: string
siteId?: string
logoUrl?: string
siteDomain?: string
siteName?: string
constructor(data: epgGrabber.Channel) {
const [channelId, feedId] = data.xmltv_id ? data.xmltv_id.split('@') : [undefined, undefined]
this.channelId = channelId
this.feedId = feedId
this.xmltvId = data.xmltv_id
this.languageCode = data.lang
this.siteId = data.site_id
this.logoUrl = data.logo
this.siteDomain = data.site
this.siteName = data.name
}
withChannel(channelsKeyById: Dictionary): this {
if (this.channelId) this.channel = channelsKeyById.get(this.channelId)
return this
}
withFeed(feedsKeyByStreamId: Dictionary): this {
if (this.feedId) this.feed = feedsKeyByStreamId.get(this.getStreamId())
return this
}
getStreamId(): string {
if (!this.channelId) return ''
if (!this.feedId) return this.channelId
return `${this.channelId}@${this.feedId}`
}
toJSON() {
return {
channel: this.channelId || null,
feed: this.feedId || null,
site: this.siteDomain || '',
site_id: this.siteId || '',
site_name: this.siteName || '',
lang: this.languageCode || ''
}
}
}
import { Dictionary } from '@freearhey/core'
import epgGrabber from 'epg-grabber'
import { Feed, Channel } from '.'
export class GuideChannel {
channelId?: string
channel?: Channel
feedId?: string
feed?: Feed
xmltvId?: string
languageCode?: string
siteId?: string
logoUrl?: string
siteDomain?: string
siteName?: string
constructor(data: epgGrabber.Channel) {
const [channelId, feedId] = data.xmltv_id ? data.xmltv_id.split('@') : [undefined, undefined]
this.channelId = channelId
this.feedId = feedId
this.xmltvId = data.xmltv_id
this.languageCode = data.lang
this.siteId = data.site_id
this.logoUrl = data.logo
this.siteDomain = data.site
this.siteName = data.name
}
withChannel(channelsKeyById: Dictionary): this {
if (this.channelId) this.channel = channelsKeyById.get(this.channelId)
return this
}
withFeed(feedsKeyByStreamId: Dictionary): this {
if (this.feedId) this.feed = feedsKeyByStreamId.get(this.getStreamId())
return this
}
getStreamId(): string {
if (!this.channelId) return ''
if (!this.feedId) return this.channelId
return `${this.channelId}@${this.feedId}`
}
toJSON() {
return {
channel: this.channelId || null,
feed: this.feedId || null,
site: this.siteDomain || '',
site_id: this.siteId || '',
site_name: this.siteName || '',
lang: this.languageCode || ''
}
}
}

View File

@@ -1,9 +1,9 @@
export * from './channel'
export * from './feed'
export * from './guide'
export * from './guideChannel'
export * from './issue'
export * from './logo'
export * from './site'
export * from './stream'
export * from './channelList'
export * from './channel'
export * from './feed'
export * from './guide'
export * from './guideChannel'
export * from './issue'
export * from './logo'
export * from './site'
export * from './stream'
export * from './channelList'

View File

@@ -1,298 +1,298 @@
<?xml version="1.0" encoding="UTF-8"?>
<channels>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1165">LA CHAINE DU PERE NOEL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1921">FRANCE 3 ALPES</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1922">FRANCE 3 ALSACE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1923">FRANCE 3 AQUITAINE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1924">FRANCE 3 AUVERGNE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1925">FRANCE 3 NORMANDIE CAEN</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1926">FRANCE 3 BOURGOGNE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1927">FRANCE 3 BRETAGNE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1928">FRANCE 3 CENTRE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1929">FRANCE 3 CHAMPAGNE ARDENNE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1931">FRANCE 3 COTE D'AZUR</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1932">FRANCE 3 FRANCHE COMTE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1933">FRANCE 3 NORMANDIE ROUEN</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1934">FRANCE 3 LANGUEDOC</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1935">FRANCE 3 LIMOUSIN</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1936">FRANCE 3 LORRAINE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1937">FRANCE 3 MIDI-PYRENEES</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1938">FRANCE 3 NORD P. CALAIS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1939">FRANCE 3 PARIS IDF</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1940">FRANCE 3 PAYS DE LA LOIRE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1941">FRANCE 3 PICARDIE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1942">FRANCE 3 POITOU CHARENTES</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1943">FRANCE 3 PROVENCE ALPES</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1944">FRANCE 3 RHONE ALPES</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="2040">WARNER TV NEXT</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="2168">BOOMERANG (VO)</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="2169">TCM CINEMA (VO)</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="2527">TF1 4K</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="2913">NCI</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="2942">TECH&amp;CO</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="299">DISNEY CHANNEL +1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3106">TOP SANTE TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3170">CANAL+ LIGUE1 UBER EATS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3301">M6 4K</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3413">FRANCE 24 Arabe</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3501">CANAL+FOOT</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3504">CANAL+SPORT360</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3561">L'ESPRIT SORCIER TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3562">FRANCE 24 Espagnol</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3738">CARTOONITO</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3767">SQOOL TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3779">CANAL+BOX OFFICE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3786">TVMONACO</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3795">DAZN 1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90150">TRACE URBAN</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90156">STAR ACADEMY, LE LIVE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90159">RFM TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90161">TRACE CARIBBEAN</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90162">TRACE LATINA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90163">TRACE VANILLA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90165">CSTAR HITS FRANCE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90216">MEN'S UP TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90221">SOUVENIRS FROM EARTH</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90226">PUBLIC SENAT 24/24</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90230">B SMART</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90231">LA CHAINE METEO</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90233">SKYNEWS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90239">AFRICA 24</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90241">AL JAZEERA Arabic</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90242">MEDI 1 TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90246">TRT WORLD</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90395">CANAL 10 Guadeloupe</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90397">TAHITI NUI TELEVISION</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90398">TELE ANTILLES</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90399">MADRAS FM TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90404">TRAVEL CHANNEL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90406">FOOD NETWORK</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90408">FOXNEWS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90434">ANTENA 3</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90435">STAR TVE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90436">A3 SERIES</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90437">CANAL 24 HORAS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90438">ALL FLAMENCO</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90439">TV3 CATALUNYA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90440">ETB BASQUE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90441">TV DE GALICIA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90442">REAL MADRID TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90444">RTP 3</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90447">TVI INTERNACIONAL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90448">SIC NOTICIAS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90449">SIC INTERNACIONAL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90450">TV RECORD</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90451">TVI FICCAO</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90453">ALMA LUSA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90454">A BOLA TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90456">CORREIO DA MANHA TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90463">RAI STORIA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90464">RAI SCUOLA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90466">MEDIASET ITALIA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90501">AL ARABIYA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90503">ALARABY TELEVISION</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90504">AL AOULA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90505">CANAL ALGERIE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90507">MBC</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90508">ROTANA CLASSIC</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90509">ROTANA CLIP</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90510">ENNAHAR TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90511">ECHOROUK TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90512">NESSMA EU</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90513">EL HIWAR ETTOUNSI</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90514">AL RESALAH</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90515">IQRAA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90516">IQRAA INTERNATIONAL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90517">SAMIRA TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90519">ROTANA MUSICA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90520">ECHOROUK NEWS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90522">ROTANA KHALIJIA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90523">ROTANA CINEMA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90524">ROTANA COMEDY</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90525">ROTANA DRAMA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90527">EL BILAD TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90528">PANORAMA DRAMA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90529">MBC DRAMA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90530">MBC MASR</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90531">AL RAWDA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90548">NTD TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90551">CCTV 4</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90552">PHOENIX CNE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90553">PHOENIX INFONEWS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90554">CHINA MOVIE CHANNEL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90555">CCTV DIVERTISSEMENT</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90556">ZHEJIANG INTERNATIONAL TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90557">SHANGHAI DRAGON TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90558">BEIJING TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90559">HUNAN WORLD TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90560">JIANGSU INTERNATIONAL TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90561">GRT GBA Satellite TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90562">GREAT WALL ELITE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90590">RTS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90591">2STV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90592">ORTM</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90593">RTI1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90594">CRTV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90595">RTNC</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90596">TELE CONGO</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90597">ORTB</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90598">A+</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90600">AFRICABLE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90601">CANAL 2 INT.</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90602">TVT</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90603">RTG</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90604">TFM</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90605">TRACE AFRICA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90606">TRACE GOSPEL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90610">SEN TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90614">TRACE TERANGA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="2MMonde.ma" site_id="340">2M MONDE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="6ter.fr" site_id="1403">6TER</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="AB1.fr" site_id="5">AB1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Action.fr" site_id="10">ACTION</channel>
<channel site="chaines-tv.orange.fr" lang="en" xmltv_id="AlJazeera.qa@English" site_id="525">AL JAZEERA Anglais</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Animaux.fr" site_id="12">ANIMAUX</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="arte.fr" site_id="111">ARTE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Automotolachaine.fr" site_id="15">AUTOMOTO, la chaine</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="BBCEntertainment.uk" site_id="18">BBC ENTERTAINMENT</channel>
<channel site="chaines-tv.orange.fr" lang="en" xmltv_id="BBCNews.uk@Europe" site_id="19">BBC NEWS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="beINSports1.qa@France" site_id="1290">BEIN SPORTS 1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="beINSports2.qa@France" site_id="1304">BEIN SPORTS 2</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="beINSports3.qa@France" site_id="1335">BEIN SPORTS 3</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="beINSportsMax10.qa@France" site_id="1342">BEIN SPORTS MAX 10</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="beINSportsMax4.qa@France" site_id="1336">BEIN SPORTS MAX 4</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="beINSportsMax5.qa@France" site_id="1337">BEIN SPORTS MAX 5</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="beINSportsMax6.qa@France" site_id="1338">BEIN SPORTS MAX 6</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="beINSportsMax7.qa@France" site_id="1339">BEIN SPORTS MAX 7</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="beINSportsMax8.qa@France" site_id="1340">BEIN SPORTS MAX 8</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="beINSportsMax9.qa@France" site_id="1341">BEIN SPORTS MAX 9</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="BET.fr" site_id="1960">BET</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="BFMBusiness.fr" site_id="1073">BFM BUSINESS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="BFMTV.fr" site_id="481">BFM TV</channel>
<channel site="chaines-tv.orange.fr" lang="en" xmltv_id="BloombergTV.us@Europe" site_id="410">BLOOMBERG EUROPE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Boomerang.fr" site_id="321">BOOMERANG</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Boomerang.fr@Plus1" site_id="928">BOOMERANG +1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CanalJ.fr" site_id="32">CANAL J</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CanalPlus.fr" site_id="34">CANAL+</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="33">CANAL+CINEMA(S)</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CanalPlusDocs.fr" site_id="3347">CANAL+DOCS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CanalPlusGrandEcran.fr" site_id="3349">CANAL+GRAND ECRAN</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CanalPlusKids.fr" site_id="3348">CANAL+kids</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CanalPlusSeries.fr" site_id="1563">CANAL+SERIES</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CanalPlusSport.fr" site_id="35">CANAL+SPORT</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="ChassePeche.fr" site_id="38">CHASSE PECHE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Cherie25.fr" site_id="1399">CHERIE 25</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CinePlusClassic.fr" site_id="287">CINE+CLASSIC</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CinePlusClub.fr" site_id="285">CINE+CLUB</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CinePlusEmotion.fr" site_id="283">CINE+EMOTION</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="401">CINE+FAMIZ</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CinePlusFrisson.fr" site_id="284">CINE+FRISSON</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CinePlusPremier.fr" site_id="282">CINE+PREMIER</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="ClubbingTV.us@France" site_id="1989">CLUBBING TV</channel>
<channel site="chaines-tv.orange.fr" lang="en" xmltv_id="CNBCEurope.uk" site_id="51">CNBC</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CNews.fr" site_id="226">CNEWS</channel>
<channel site="chaines-tv.orange.fr" lang="en" xmltv_id="CNNInternational.us@MENA" site_id="53">CNN INTERNATIONAL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="ComediePlus.fr" site_id="54">COMEDIE+</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="ComedyCentral.fr" site_id="2752">COMEDY CENTRAL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CrimeDistrict.fr" site_id="2037">CRIME DISTRICT</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CStar.fr" site_id="458">CSTAR</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="DemainTV.fr" site_id="57">DEMAIN</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="DisneyChannel.fr" site_id="58">DISNEY CHANNEL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="DisneyJr.fr" site_id="300">DISNEY JUNIOR</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="DW.de@English" site_id="61">DEUTSCHE WELLE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Equidia.fr" site_id="64">EQUIDIA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Eurochannel.uk" site_id="1190">EUROCHANNEL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="EuronewsFrench.fr" site_id="140">EURONEWS Français</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="FashionTVEurope.fr" site_id="1996">FASHIONTV PARIS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="France2.fr" site_id="4">FRANCE 2</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="France24.fr@English" site_id="671">FRANCE 24 Anglais</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="France24.fr@French" site_id="529">FRANCE 24 Français</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="France3.fr" site_id="80">FRANCE 3</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="France3.fr@CorseViaStella" site_id="308">FRANCE 3 CORSE VIA STELLA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="France4.fr" site_id="78">FRANCE 4</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="France5.fr" site_id="47">FRANCE 5</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Franceinfo.fr" site_id="2111">FRANCEINFO:</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="GameOne.fr" site_id="87">GAME ONE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="GameOne.fr@Plus1" site_id="1167">GAME ONE +1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="GolfChannel.fr" site_id="1166">GOLF CHANNEL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Gulli.fr" site_id="482">GULLI</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="HistoireTV.fr" site_id="88">HISTOIRE TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="i24NEWSFrench.il" site_id="781">I24NEWS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="JOne.fr" site_id="1585">J-ONE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="KTO.fr" site_id="110">KTO</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="LCI.fr" site_id="112">LCI</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="LCP.fr" site_id="992">LCP 100%</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="LEquipe.fr" site_id="1401">LA CHAINE L'EQUIPE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="LuckyJacktv.lu" site_id="1061">LUCKY JACK</channel>
<channel site="chaines-tv.orange.fr" lang="en" xmltv_id="LuxeTV.lu" site_id="531">LUXE TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="M6.fr" site_id="118">M6</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="M6Music.fr" site_id="453">M6MUSIC</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="MaisonTravauxTV.fr" site_id="3360">MAISON ET TRAVAUX TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Mangas.fr" site_id="6">MANGAS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="MCM.fr" site_id="121">MCM</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Melody.fr" site_id="265">MELODY</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="MelodydAfrique.fr" site_id="2321">MELODY D'AFRIQUE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Mezzo.fr" site_id="125">MEZZO</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="MezzoLive.fr" site_id="907">MEZZO LIVE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="MGGTV.fr" site_id="2353">MGG TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="MTV.fr" site_id="128">MTV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="MTVHits.fr" site_id="2006">MTV HITS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1072">MUSEUM TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="MyZenTV.fr" site_id="829">MY ZEN TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="NationalGeographic.fr" site_id="508">NATIONAL GEOGRAPHIC</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="NationalGeographicWild.fr" site_id="719">NATIONAL GEOGRAPHIC WILD</channel>
<channel site="chaines-tv.orange.fr" lang="en" xmltv_id="NHKWorldJapan.jp" site_id="830">NHK WORLD - JAPAN</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Nickelodeon.fr" site_id="473">NICKELODEON</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="NickelodeonJunior.fr" site_id="888">NICKELODEON JUNIOR</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Nickelodeon.fr@Plus1" site_id="2065">NICKELODEON +1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="NickelodeonTeen.fr" site_id="1746">NICKELODEON TEEN</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="NollywoodTV.fr" site_id="1461">NOLLYWOOD TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="NovelasTV.fr" site_id="1832">NOVELAS TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="NRJHits.fr" site_id="605">NRJ HITS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="OCSChoc.fr" site_id="732">OCS PULP</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="OCSGeants.fr" site_id="734">OCS GEANTS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="OCSMax.fr" site_id="730">OCS MAX</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="OlympiaTV.fr" site_id="2958">OLYMPIA TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="ParamountChannel.fr" site_id="1562">PARAMOUNT CHANNEL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="ParamountChannelOffset.fr" site_id="2072">PARAMOUNT CHANNEL DECALE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="ParisPremiere.fr" site_id="145">PARIS PREMIERE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="PiwiPlus.fr" site_id="344">PIWI+</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="PlanetePlus.fr" site_id="147">PLANETE+</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="PlanetePlusAventure.fr" site_id="402">PLANETE+AVENTURE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="PlanetePlusCrime.fr" site_id="662">PLANETE+CRIME</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="PolarPlus.fr" site_id="2326">POLAR+</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="LCP.fr" site_id="234">LCP/PS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Rai1.it" site_id="156">RAI UNO</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Rai2.it" site_id="154">RAI DUE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Rai3.it" site_id="155">RAI TRE</channel>
<channel site="chaines-tv.orange.fr" lang="it" xmltv_id="RaiNews24.it" site_id="1129">RAI NEWS 24</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="RMCDecouverte.fr" site_id="1400">RMC DECOUVERTE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="RMCStory.fr" site_id="1402">RMC STORY</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="RTL9.lu" site_id="115">RTL9</channel>
<channel site="chaines-tv.orange.fr" lang="pt" xmltv_id="RTPInternacional.pt" site_id="169">RTPI</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="ScienceVieTV.fr" site_id="63">SCIENCE &amp; VIE TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="SerieClub.fr" site_id="49">SERIE CLUB</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="SportenFrance.fr" site_id="2837">SPORT EN FRANCE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="StingrayClassica.ca" site_id="1353">STINGRAY CLASSICA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="SunuYeuf.sn" site_id="2908">SUNU YEUF</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="T18.fr" site_id="4139">T18</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TCMCinema.fr" site_id="185">TCM CINEMA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TeletoonPlus.fr" site_id="197">TELETOON+</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TeletoonPlus.fr@1" site_id="293">TELETOON +1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Teva.fr" site_id="191">TEVA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TF1.fr" site_id="192">TF1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TF1.fr@Plus1" site_id="2441">TF1 +1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TF1SeriesFilms.fr" site_id="1404">TF1 SERIES FILMS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TFX.fr" site_id="446">TFX</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TiJi.fr" site_id="229">TIJI</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TMC.fr" site_id="195">TMC</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TMC.fr@Plus1" site_id="2442">TMC +1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="ToutelHistoire.fr" site_id="7">TOUTE L'HISTOIRE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TV5MondeFranceBelgiumSwitzerlandMonaco.fr" site_id="205">TV5MONDE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TVBreizh.fr" site_id="225">TV BREIZH</channel>
<channel site="chaines-tv.orange.fr" lang="es" xmltv_id="TVEInternacionalEuropeAsia.es" site_id="208">TVE INTERNACIONAL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TVPitchoun.fr" site_id="2803">TV PITCHOUN</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="UshuaiaTV.fr" site_id="451">USHUAIA TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="VoxAfrica.uk" site_id="1133">VOXAFRICA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="W9.fr" site_id="119">W9</channel>
</channels>
<?xml version="1.0" encoding="UTF-8"?>
<channels>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1165">LA CHAINE DU PERE NOEL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1921">FRANCE 3 ALPES</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1922">FRANCE 3 ALSACE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1923">FRANCE 3 AQUITAINE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1924">FRANCE 3 AUVERGNE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1925">FRANCE 3 NORMANDIE CAEN</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1926">FRANCE 3 BOURGOGNE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1927">FRANCE 3 BRETAGNE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1928">FRANCE 3 CENTRE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1929">FRANCE 3 CHAMPAGNE ARDENNE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1931">FRANCE 3 COTE D'AZUR</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1932">FRANCE 3 FRANCHE COMTE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1933">FRANCE 3 NORMANDIE ROUEN</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1934">FRANCE 3 LANGUEDOC</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1935">FRANCE 3 LIMOUSIN</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1936">FRANCE 3 LORRAINE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1937">FRANCE 3 MIDI-PYRENEES</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1938">FRANCE 3 NORD P. CALAIS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1939">FRANCE 3 PARIS IDF</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1940">FRANCE 3 PAYS DE LA LOIRE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1941">FRANCE 3 PICARDIE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1942">FRANCE 3 POITOU CHARENTES</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1943">FRANCE 3 PROVENCE ALPES</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1944">FRANCE 3 RHONE ALPES</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="2040">WARNER TV NEXT</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="2168">BOOMERANG (VO)</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="2169">TCM CINEMA (VO)</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="2527">TF1 4K</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="2913">NCI</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="2942">TECH&amp;CO</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="299">DISNEY CHANNEL +1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3106">TOP SANTE TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3170">CANAL+ LIGUE1 UBER EATS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3301">M6 4K</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3413">FRANCE 24 Arabe</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3501">CANAL+FOOT</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3504">CANAL+SPORT360</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3561">L'ESPRIT SORCIER TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3562">FRANCE 24 Espagnol</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3738">CARTOONITO</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3767">SQOOL TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3779">CANAL+BOX OFFICE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3786">TVMONACO</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="3795">DAZN 1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90150">TRACE URBAN</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90156">STAR ACADEMY, LE LIVE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90159">RFM TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90161">TRACE CARIBBEAN</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90162">TRACE LATINA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90163">TRACE VANILLA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90165">CSTAR HITS FRANCE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90216">MEN'S UP TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90221">SOUVENIRS FROM EARTH</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90226">PUBLIC SENAT 24/24</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90230">B SMART</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90231">LA CHAINE METEO</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90233">SKYNEWS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90239">AFRICA 24</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90241">AL JAZEERA Arabic</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90242">MEDI 1 TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90246">TRT WORLD</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90395">CANAL 10 Guadeloupe</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90397">TAHITI NUI TELEVISION</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90398">TELE ANTILLES</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90399">MADRAS FM TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90404">TRAVEL CHANNEL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90406">FOOD NETWORK</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90408">FOXNEWS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90434">ANTENA 3</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90435">STAR TVE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90436">A3 SERIES</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90437">CANAL 24 HORAS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90438">ALL FLAMENCO</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90439">TV3 CATALUNYA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90440">ETB BASQUE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90441">TV DE GALICIA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90442">REAL MADRID TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90444">RTP 3</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90447">TVI INTERNACIONAL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90448">SIC NOTICIAS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90449">SIC INTERNACIONAL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90450">TV RECORD</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90451">TVI FICCAO</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90453">ALMA LUSA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90454">A BOLA TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90456">CORREIO DA MANHA TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90463">RAI STORIA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90464">RAI SCUOLA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90466">MEDIASET ITALIA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90501">AL ARABIYA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90503">ALARABY TELEVISION</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90504">AL AOULA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90505">CANAL ALGERIE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90507">MBC</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90508">ROTANA CLASSIC</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90509">ROTANA CLIP</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90510">ENNAHAR TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90511">ECHOROUK TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90512">NESSMA EU</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90513">EL HIWAR ETTOUNSI</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90514">AL RESALAH</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90515">IQRAA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90516">IQRAA INTERNATIONAL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90517">SAMIRA TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90519">ROTANA MUSICA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90520">ECHOROUK NEWS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90522">ROTANA KHALIJIA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90523">ROTANA CINEMA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90524">ROTANA COMEDY</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90525">ROTANA DRAMA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90527">EL BILAD TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90528">PANORAMA DRAMA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90529">MBC DRAMA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90530">MBC MASR</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90531">AL RAWDA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90548">NTD TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90551">CCTV 4</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90552">PHOENIX CNE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90553">PHOENIX INFONEWS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90554">CHINA MOVIE CHANNEL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90555">CCTV DIVERTISSEMENT</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90556">ZHEJIANG INTERNATIONAL TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90557">SHANGHAI DRAGON TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90558">BEIJING TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90559">HUNAN WORLD TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90560">JIANGSU INTERNATIONAL TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90561">GRT GBA Satellite TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90562">GREAT WALL ELITE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90590">RTS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90591">2STV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90592">ORTM</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90593">RTI1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90594">CRTV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90595">RTNC</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90596">TELE CONGO</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90597">ORTB</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90598">A+</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90600">AFRICABLE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90601">CANAL 2 INT.</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90602">TVT</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90603">RTG</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90604">TFM</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90605">TRACE AFRICA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90606">TRACE GOSPEL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90610">SEN TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="90614">TRACE TERANGA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="2MMonde.ma" site_id="340">2M MONDE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="6ter.fr" site_id="1403">6TER</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="AB1.fr" site_id="5">AB1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Action.fr" site_id="10">ACTION</channel>
<channel site="chaines-tv.orange.fr" lang="en" xmltv_id="AlJazeera.qa@English" site_id="525">AL JAZEERA Anglais</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Animaux.fr" site_id="12">ANIMAUX</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="arte.fr" site_id="111">ARTE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Automotolachaine.fr" site_id="15">AUTOMOTO, la chaine</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="BBCEntertainment.uk" site_id="18">BBC ENTERTAINMENT</channel>
<channel site="chaines-tv.orange.fr" lang="en" xmltv_id="BBCNews.uk@Europe" site_id="19">BBC NEWS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="beINSports1.qa@France" site_id="1290">BEIN SPORTS 1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="beINSports2.qa@France" site_id="1304">BEIN SPORTS 2</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="beINSports3.qa@France" site_id="1335">BEIN SPORTS 3</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="beINSportsMax10.qa@France" site_id="1342">BEIN SPORTS MAX 10</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="beINSportsMax4.qa@France" site_id="1336">BEIN SPORTS MAX 4</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="beINSportsMax5.qa@France" site_id="1337">BEIN SPORTS MAX 5</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="beINSportsMax6.qa@France" site_id="1338">BEIN SPORTS MAX 6</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="beINSportsMax7.qa@France" site_id="1339">BEIN SPORTS MAX 7</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="beINSportsMax8.qa@France" site_id="1340">BEIN SPORTS MAX 8</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="beINSportsMax9.qa@France" site_id="1341">BEIN SPORTS MAX 9</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="BET.fr" site_id="1960">BET</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="BFMBusiness.fr" site_id="1073">BFM BUSINESS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="BFMTV.fr" site_id="481">BFM TV</channel>
<channel site="chaines-tv.orange.fr" lang="en" xmltv_id="BloombergTV.us@Europe" site_id="410">BLOOMBERG EUROPE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Boomerang.fr" site_id="321">BOOMERANG</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Boomerang.fr@Plus1" site_id="928">BOOMERANG +1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CanalJ.fr" site_id="32">CANAL J</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CanalPlus.fr" site_id="34">CANAL+</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="33">CANAL+CINEMA(S)</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CanalPlusDocs.fr" site_id="3347">CANAL+DOCS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CanalPlusGrandEcran.fr" site_id="3349">CANAL+GRAND ECRAN</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CanalPlusKids.fr" site_id="3348">CANAL+kids</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CanalPlusSeries.fr" site_id="1563">CANAL+SERIES</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CanalPlusSport.fr" site_id="35">CANAL+SPORT</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="ChassePeche.fr" site_id="38">CHASSE PECHE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Cherie25.fr" site_id="1399">CHERIE 25</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CinePlusClassic.fr" site_id="287">CINE+CLASSIC</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CinePlusClub.fr" site_id="285">CINE+CLUB</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CinePlusEmotion.fr" site_id="283">CINE+EMOTION</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="401">CINE+FAMIZ</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CinePlusFrisson.fr" site_id="284">CINE+FRISSON</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CinePlusPremier.fr" site_id="282">CINE+PREMIER</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="ClubbingTV.us@France" site_id="1989">CLUBBING TV</channel>
<channel site="chaines-tv.orange.fr" lang="en" xmltv_id="CNBCEurope.uk" site_id="51">CNBC</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CNews.fr" site_id="226">CNEWS</channel>
<channel site="chaines-tv.orange.fr" lang="en" xmltv_id="CNNInternational.us@MENA" site_id="53">CNN INTERNATIONAL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="ComediePlus.fr" site_id="54">COMEDIE+</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="ComedyCentral.fr" site_id="2752">COMEDY CENTRAL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CrimeDistrict.fr" site_id="2037">CRIME DISTRICT</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="CStar.fr" site_id="458">CSTAR</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="DemainTV.fr" site_id="57">DEMAIN</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="DisneyChannel.fr" site_id="58">DISNEY CHANNEL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="DisneyJr.fr" site_id="300">DISNEY JUNIOR</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="DW.de@English" site_id="61">DEUTSCHE WELLE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Equidia.fr" site_id="64">EQUIDIA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Eurochannel.uk" site_id="1190">EUROCHANNEL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="EuronewsFrench.fr" site_id="140">EURONEWS Français</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="FashionTVEurope.fr" site_id="1996">FASHIONTV PARIS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="France2.fr" site_id="4">FRANCE 2</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="France24.fr@English" site_id="671">FRANCE 24 Anglais</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="France24.fr@French" site_id="529">FRANCE 24 Français</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="France3.fr" site_id="80">FRANCE 3</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="France3.fr@CorseViaStella" site_id="308">FRANCE 3 CORSE VIA STELLA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="France4.fr" site_id="78">FRANCE 4</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="France5.fr" site_id="47">FRANCE 5</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Franceinfo.fr" site_id="2111">FRANCEINFO:</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="GameOne.fr" site_id="87">GAME ONE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="GameOne.fr@Plus1" site_id="1167">GAME ONE +1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="GolfChannel.fr" site_id="1166">GOLF CHANNEL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Gulli.fr" site_id="482">GULLI</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="HistoireTV.fr" site_id="88">HISTOIRE TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="i24NEWSFrench.il" site_id="781">I24NEWS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="JOne.fr" site_id="1585">J-ONE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="KTO.fr" site_id="110">KTO</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="LCI.fr" site_id="112">LCI</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="LCP.fr" site_id="992">LCP 100%</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="LEquipe.fr" site_id="1401">LA CHAINE L'EQUIPE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="LuckyJacktv.lu" site_id="1061">LUCKY JACK</channel>
<channel site="chaines-tv.orange.fr" lang="en" xmltv_id="LuxeTV.lu" site_id="531">LUXE TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="M6.fr" site_id="118">M6</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="M6Music.fr" site_id="453">M6MUSIC</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="MaisonTravauxTV.fr" site_id="3360">MAISON ET TRAVAUX TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Mangas.fr" site_id="6">MANGAS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="MCM.fr" site_id="121">MCM</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Melody.fr" site_id="265">MELODY</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="MelodydAfrique.fr" site_id="2321">MELODY D'AFRIQUE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Mezzo.fr" site_id="125">MEZZO</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="MezzoLive.fr" site_id="907">MEZZO LIVE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="MGGTV.fr" site_id="2353">MGG TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="MTV.fr" site_id="128">MTV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="MTVHits.fr" site_id="2006">MTV HITS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="" site_id="1072">MUSEUM TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="MyZenTV.fr" site_id="829">MY ZEN TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="NationalGeographic.fr" site_id="508">NATIONAL GEOGRAPHIC</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="NationalGeographicWild.fr" site_id="719">NATIONAL GEOGRAPHIC WILD</channel>
<channel site="chaines-tv.orange.fr" lang="en" xmltv_id="NHKWorldJapan.jp" site_id="830">NHK WORLD - JAPAN</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Nickelodeon.fr" site_id="473">NICKELODEON</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="NickelodeonJunior.fr" site_id="888">NICKELODEON JUNIOR</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Nickelodeon.fr@Plus1" site_id="2065">NICKELODEON +1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="NickelodeonTeen.fr" site_id="1746">NICKELODEON TEEN</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="NollywoodTV.fr" site_id="1461">NOLLYWOOD TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="NovelasTV.fr" site_id="1832">NOVELAS TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="NRJHits.fr" site_id="605">NRJ HITS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="OCSChoc.fr" site_id="732">OCS PULP</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="OCSGeants.fr" site_id="734">OCS GEANTS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="OCSMax.fr" site_id="730">OCS MAX</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="OlympiaTV.fr" site_id="2958">OLYMPIA TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="ParamountChannel.fr" site_id="1562">PARAMOUNT CHANNEL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="ParamountChannelOffset.fr" site_id="2072">PARAMOUNT CHANNEL DECALE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="ParisPremiere.fr" site_id="145">PARIS PREMIERE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="PiwiPlus.fr" site_id="344">PIWI+</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="PlanetePlus.fr" site_id="147">PLANETE+</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="PlanetePlusAventure.fr" site_id="402">PLANETE+AVENTURE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="PlanetePlusCrime.fr" site_id="662">PLANETE+CRIME</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="PolarPlus.fr" site_id="2326">POLAR+</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="LCP.fr" site_id="234">LCP/PS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Rai1.it" site_id="156">RAI UNO</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Rai2.it" site_id="154">RAI DUE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Rai3.it" site_id="155">RAI TRE</channel>
<channel site="chaines-tv.orange.fr" lang="it" xmltv_id="RaiNews24.it" site_id="1129">RAI NEWS 24</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="RMCDecouverte.fr" site_id="1400">RMC DECOUVERTE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="RMCStory.fr" site_id="1402">RMC STORY</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="RTL9.lu" site_id="115">RTL9</channel>
<channel site="chaines-tv.orange.fr" lang="pt" xmltv_id="RTPInternacional.pt" site_id="169">RTPI</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="ScienceVieTV.fr" site_id="63">SCIENCE &amp; VIE TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="SerieClub.fr" site_id="49">SERIE CLUB</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="SportenFrance.fr" site_id="2837">SPORT EN FRANCE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="StingrayClassica.ca" site_id="1353">STINGRAY CLASSICA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="SunuYeuf.sn" site_id="2908">SUNU YEUF</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="T18.fr" site_id="4139">T18</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TCMCinema.fr" site_id="185">TCM CINEMA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TeletoonPlus.fr" site_id="197">TELETOON+</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TeletoonPlus.fr@1" site_id="293">TELETOON +1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="Teva.fr" site_id="191">TEVA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TF1.fr" site_id="192">TF1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TF1.fr@Plus1" site_id="2441">TF1 +1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TF1SeriesFilms.fr" site_id="1404">TF1 SERIES FILMS</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TFX.fr" site_id="446">TFX</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TiJi.fr" site_id="229">TIJI</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TMC.fr" site_id="195">TMC</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TMC.fr@Plus1" site_id="2442">TMC +1</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="ToutelHistoire.fr" site_id="7">TOUTE L'HISTOIRE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TV5MondeFranceBelgiumSwitzerlandMonaco.fr" site_id="205">TV5MONDE</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TVBreizh.fr" site_id="225">TV BREIZH</channel>
<channel site="chaines-tv.orange.fr" lang="es" xmltv_id="TVEInternacionalEuropeAsia.es" site_id="208">TVE INTERNACIONAL</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="TVPitchoun.fr" site_id="2803">TV PITCHOUN</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="UshuaiaTV.fr" site_id="451">USHUAIA TV</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="VoxAfrica.uk" site_id="1133">VOXAFRICA</channel>
<channel site="chaines-tv.orange.fr" lang="fr" xmltv_id="W9.fr" site_id="119">W9</channel>
</channels>

View File

@@ -1,79 +1,79 @@
const axios = require('axios')
const dayjs = require('dayjs')
module.exports = {
site: 'chaines-tv.orange.fr',
days: 2,
url({ channel, date }) {
return `https://rp-ott-mediation-tv.woopic.com/api-gw/live/v3/applications/STB4PC/programs?groupBy=channel&includeEmptyChannels=false&period=${date.valueOf()},${date
.add(1, 'd')
.valueOf()}&after=${channel.site_id}&limit=1`
},
parser: function ({ content, channel }) {
let programs = []
const items = parseItems(content, channel)
items.forEach(item => {
const start = parseStart(item)
const stop = parseStop(item, start)
programs.push({
title: item.title,
subTitle: item.season?.serie?.title,
category: item.genreDetailed,
description: item.synopsis,
season: parseSeason(item),
episode: parseEpisode(item),
image: parseImage(item),
start: start.toJSON(),
stop: stop.toJSON()
})
})
return programs
},
async channels() {
const html = await axios
.get('https://chaines-tv.orange.fr/programme-tv?filtres=all')
.then(r => r.data)
.catch(console.log)
const [, nuxtFunc] = html.match(/window\.__NUXT__=([^<]+)/) || [null, null]
const func = new Function(`"use strict";return ${nuxtFunc}`)
const data = func()
const items = data.state.channels.channels
return items.map(item => {
return {
lang: 'fr',
site_id: item.idEPG,
name: item.name
}
})
}
}
function parseImage(item) {
return item.covers && item.covers.length ? item.covers[0].url : null
}
function parseStart(item) {
return dayjs.unix(item.diffusionDate)
}
function parseStop(item, start) {
return start.add(item.duration, 's')
}
function parseSeason(item) {
return item.season?.number
}
function parseEpisode(item) {
return item.episodeNumber
}
function parseItems(content, channel) {
const data = JSON.parse(content)
return data && data[channel.site_id] ? data[channel.site_id] : []
}
const axios = require('axios')
const dayjs = require('dayjs')
module.exports = {
site: 'chaines-tv.orange.fr',
days: 2,
url({ channel, date }) {
return `https://rp-ott-mediation-tv.woopic.com/api-gw/live/v3/applications/STB4PC/programs?groupBy=channel&includeEmptyChannels=false&period=${date.valueOf()},${date
.add(1, 'd')
.valueOf()}&after=${channel.site_id}&limit=1`
},
parser: function ({ content, channel }) {
let programs = []
const items = parseItems(content, channel)
items.forEach(item => {
const start = parseStart(item)
const stop = parseStop(item, start)
programs.push({
title: item.title,
subTitle: item.season?.serie?.title,
category: item.genreDetailed,
description: item.synopsis,
season: parseSeason(item),
episode: parseEpisode(item),
image: parseImage(item),
start: start.toJSON(),
stop: stop.toJSON()
})
})
return programs
},
async channels() {
const html = await axios
.get('https://chaines-tv.orange.fr/programme-tv?filtres=all')
.then(r => r.data)
.catch(console.log)
const [, nuxtFunc] = html.match(/window\.__NUXT__=([^<]+)/) || [null, null]
const func = new Function(`"use strict";return ${nuxtFunc}`)
const data = func()
const items = data.state.channels.channels
return items.map(item => {
return {
lang: 'fr',
site_id: item.idEPG,
name: item.name
}
})
}
}
function parseImage(item) {
return item.covers && item.covers.length ? item.covers[0].url : null
}
function parseStart(item) {
return dayjs.unix(item.diffusionDate)
}
function parseStop(item, start) {
return start.add(item.duration, 's')
}
function parseSeason(item) {
return item.season?.number
}
function parseEpisode(item) {
return item.episodeNumber
}
function parseItems(content, channel) {
const data = JSON.parse(content)
return data && data[channel.site_id] ? data[channel.site_id] : []
}

View File

@@ -1,95 +1,95 @@
const dayjs = require('dayjs')
module.exports = {
site: 'ctc.ru',
days: 1,
url: ({ date }) => `https://ctc.ru/api/page/v2/programm/?date=${formatDate(date)}`,
parser({ content }) {
const programs = []
const items = parseItems(content)
for (const item of items) {
programs.push({
title: item.bubbleTitle,
// more like "films", "shows", "cartoons" - not a genre
category: item.bubbleSubTitle,
icons: parseIcons(item),
images: parseImages(item),
start: parseStart(item),
stop: parseStop(item),
// not sure if CTC uses this more like `premiere` but I don't have any
// additional info to use in the `premiere` field so I'm using this
// instead.
new: item.isPremiere ?? false,
url: item.bubbleUrl ? `https://ctc.ru${item.bubbleUrl}` : undefined,
rating: parseRating(item),
})
}
return programs
}
}
function formatDate(date) {
return dayjs(date).format('DD-MM-YYYY')
}
function parseIcons(item) {
const images = item.bubbleImage ?? []
// biggest first
const sorted = images.sort((a, b) => b.height - a.height)
return sorted.map((image) => ({
src: image.url,
width: image.width,
height: image.height,
}))
}
function parseImages(item) {
const images = item.trackImageUrl ?? []
// biggest first
const sorted = images.sort((a, b) => b.height - a.height)
// compile one image of each size since the content should be the same
const sizes = {}
for (const image of sorted) {
const maxRes = Math.max(image.width, image.height)
const item = {
type: 'backdrop',
// https://github.com/ektotv/xmltv/blob/801417b4b7aae38f13caa82d8bbfbed0a254ee5f/src/types.ts#L40-L48
size: maxRes > 400 ? '3' : maxRes > 200 ? '2' : '1',
orient: image.height > image.width ? 'P' : 'L',
value: image.url,
}
if (sizes[item.size]) continue
sizes[item.size] = item
}
// re-sort so the size 3 images are first
return Object
.values(sizes)
.sort((a, b) => Number(b.size) - Number(a.size))
}
function parseStart(item) {
return dayjs(item.startTime)
}
function parseStop(item) {
return dayjs(item.endTime)
}
function parseRating(item) {
if (item.ageLimit == null) return null
return {
// Not sure what the Russian system is actually called (if anything)
system: 'Russia',
value: `${item.ageLimit}+`,
}
}
function parseItems(content) {
const node = JSON.parse(content).content.find(n => n.type === 'tv-program')
if (node) return node.widgets
return []
}
const dayjs = require('dayjs')
module.exports = {
site: 'ctc.ru',
days: 1,
url: ({ date }) => `https://ctc.ru/api/page/v2/programm/?date=${formatDate(date)}`,
parser({ content }) {
const programs = []
const items = parseItems(content)
for (const item of items) {
programs.push({
title: item.bubbleTitle,
// more like "films", "shows", "cartoons" - not a genre
category: item.bubbleSubTitle,
icons: parseIcons(item),
images: parseImages(item),
start: parseStart(item),
stop: parseStop(item),
// not sure if CTC uses this more like `premiere` but I don't have any
// additional info to use in the `premiere` field so I'm using this
// instead.
new: item.isPremiere ?? false,
url: item.bubbleUrl ? `https://ctc.ru${item.bubbleUrl}` : undefined,
rating: parseRating(item),
})
}
return programs
}
}
function formatDate(date) {
return dayjs(date).format('DD-MM-YYYY')
}
function parseIcons(item) {
const images = item.bubbleImage ?? []
// biggest first
const sorted = images.sort((a, b) => b.height - a.height)
return sorted.map((image) => ({
src: image.url,
width: image.width,
height: image.height,
}))
}
function parseImages(item) {
const images = item.trackImageUrl ?? []
// biggest first
const sorted = images.sort((a, b) => b.height - a.height)
// compile one image of each size since the content should be the same
const sizes = {}
for (const image of sorted) {
const maxRes = Math.max(image.width, image.height)
const item = {
type: 'backdrop',
// https://github.com/ektotv/xmltv/blob/801417b4b7aae38f13caa82d8bbfbed0a254ee5f/src/types.ts#L40-L48
size: maxRes > 400 ? '3' : maxRes > 200 ? '2' : '1',
orient: image.height > image.width ? 'P' : 'L',
value: image.url,
}
if (sizes[item.size]) continue
sizes[item.size] = item
}
// re-sort so the size 3 images are first
return Object
.values(sizes)
.sort((a, b) => Number(b.size) - Number(a.size))
}
function parseStart(item) {
return dayjs(item.startTime)
}
function parseStop(item) {
return dayjs(item.endTime)
}
function parseRating(item) {
if (item.ageLimit == null) return null
return {
// Not sure what the Russian system is actually called (if anything)
system: 'Russia',
value: `${item.ageLimit}+`,
}
}
function parseItems(content) {
const node = JSON.parse(content).content.find(n => n.type === 'tv-program')
if (node) return node.widgets
return []
}

View File

@@ -1,91 +1,91 @@
const { parser, url } = require('./ctc.ru.config.js')
const fs = require('fs')
const path = require('path')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat')
dayjs.extend(customParseFormat)
dayjs.extend(utc)
const date = dayjs.utc('2025-06-22')
it('can generate valid url', () => {
expect(url({ date })).toBe('https://ctc.ru/api/page/v2/programm/?date=22-06-2025')
})
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
let results = parser({ content, date })
results = results.map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
expect(results[0]).toMatchObject({
start: '2025-06-22T03:00:00.000Z',
stop: '2025-06-22T03:55:00.000Z',
title: 'Три кота',
category: 'Мультфильмы',
new: false,
url: 'https://ctc.ru/collections/multiki/',
icons: [
{
src: 'https://mgf-static-ssl.ctc.ru/images/ctc-entity-dictionary-category/5/iconurl/web/5f9027fcba9ac-125x125.png',
width: 125,
height: 125,
},
{
src: 'https://mgf-static-ssl.ctc.ru/images/ctc-entity-dictionary-category/5/iconurl/web/5f9027fc99587-60x60.png',
width: 60,
height: 60,
},
],
images: [
{
type: 'backdrop',
size: '3',
orient: 'L',
value: 'https://mgf-static-ssl.ctc.ru/images/ctc-entity-project/1129/horizontalcover/web/66c319f0a31b5-1740x978.jpeg',
},
{
type: 'backdrop',
size: '2',
orient: 'L',
value: 'https://mgf-static-ssl.ctc.ru/images/ctc-entity-project/1129/horizontalcover/web/66c319f20bac2-400x225.jpeg',
},
{
type: 'backdrop',
size: '1',
orient: 'L',
value: 'https://mgf-static-ssl.ctc.ru/images/ctc-entity-project/1129/horizontalcover/web/66c319f244f92-150x84.jpeg',
},
],
rating: {
system: 'Russia',
value: '0+',
},
})
})
it('can handle empty guide', () => {
const results = parser({
content: JSON.stringify({
isActive: true,
url: '/programm/',
header: [],
sidebar: [],
footer: [],
content: [],
seoTags: {},
ogMarkup: {},
userGeo: null,
userData: null,
meta: {},
activeFrom: null,
activeTo: null,
type: 'tv-program-page',
})
})
expect(results).toMatchObject([])
})
const { parser, url } = require('./ctc.ru.config.js')
const fs = require('fs')
const path = require('path')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat')
dayjs.extend(customParseFormat)
dayjs.extend(utc)
const date = dayjs.utc('2025-06-22')
it('can generate valid url', () => {
expect(url({ date })).toBe('https://ctc.ru/api/page/v2/programm/?date=22-06-2025')
})
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
let results = parser({ content, date })
results = results.map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
expect(results[0]).toMatchObject({
start: '2025-06-22T03:00:00.000Z',
stop: '2025-06-22T03:55:00.000Z',
title: 'Три кота',
category: 'Мультфильмы',
new: false,
url: 'https://ctc.ru/collections/multiki/',
icons: [
{
src: 'https://mgf-static-ssl.ctc.ru/images/ctc-entity-dictionary-category/5/iconurl/web/5f9027fcba9ac-125x125.png',
width: 125,
height: 125,
},
{
src: 'https://mgf-static-ssl.ctc.ru/images/ctc-entity-dictionary-category/5/iconurl/web/5f9027fc99587-60x60.png',
width: 60,
height: 60,
},
],
images: [
{
type: 'backdrop',
size: '3',
orient: 'L',
value: 'https://mgf-static-ssl.ctc.ru/images/ctc-entity-project/1129/horizontalcover/web/66c319f0a31b5-1740x978.jpeg',
},
{
type: 'backdrop',
size: '2',
orient: 'L',
value: 'https://mgf-static-ssl.ctc.ru/images/ctc-entity-project/1129/horizontalcover/web/66c319f20bac2-400x225.jpeg',
},
{
type: 'backdrop',
size: '1',
orient: 'L',
value: 'https://mgf-static-ssl.ctc.ru/images/ctc-entity-project/1129/horizontalcover/web/66c319f244f92-150x84.jpeg',
},
],
rating: {
system: 'Russia',
value: '0+',
},
})
})
it('can handle empty guide', () => {
const results = parser({
content: JSON.stringify({
isActive: true,
url: '/programm/',
header: [],
sidebar: [],
footer: [],
content: [],
seoTags: {},
ogMarkup: {},
userGeo: null,
userData: null,
meta: {},
activeFrom: null,
activeTo: null,
type: 'tv-program-page',
})
})
expect(results).toMatchObject([])
})

View File

@@ -3,7 +3,7 @@ const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const parseDuration = require('parse-duration').default
const timezone = require('dayjs/plugin/timezone')
const { sortBy } = require('../../scripts/functions')
const sortBy = require('lodash.sortby')
dayjs.extend(customParseFormat)
dayjs.extend(utc)

View File

@@ -3,7 +3,7 @@ const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const { uniqBy } = require('../../scripts/functions')
const uniqBy = require('lodash.uniqby')
dayjs.extend(utc)
dayjs.extend(timezone)

View File

@@ -1,135 +1,135 @@
# epgshare01.online
https://epgshare01.online/epgshare01/
| Tag |
| ------------------- |
| `ALJAZEERA1` |
| `AR1` |
| `ASIANTELEVISION1` |
| `AU1` |
| `BA1` |
| `BE2` |
| `BEIN1` |
| `BG1` |
| `BR1` |
| `CA1` |
| `CH1` |
| `CL1` |
| `CO1` |
| `CR1` |
| `CY1` |
| `CZ1` |
| `DE1` |
| `DELUXEMUSIC1` |
| `DIRECTVSPORTS1` |
| `DISTROTV1` |
| `DK1` |
| `DO1` |
| `DRAFTKINGS1` |
| `DUMMY_CHANNELS` |
| `EC1` |
| `EG1` |
| `ES1` |
| `EUROSPORT1` |
| `FANDUEL1` |
| `FI1` |
| `FR1` |
| `GR1` |
| `HK1` |
| `HR1` |
| `HU1` |
| `ID1` |
| `IE1` |
| `IL1` |
| `IN4` |
| `IT1` |
| `JM1` |
| `JP1` |
| `JP2` |
| `KE1` |
| `KR1` |
| `MT1` |
| `MX1` |
| `MY1` |
| `NAUTICAL_CHANNEL1` |
| `NG1` |
| `NL1` |
| `NO1` |
| `NZ1` |
| `OPTUSSPORTS1` |
| `PA1` |
| `PAC-12` |
| `PE1` |
| `PH1` |
| `PH2` |
| `PK1` |
| `PL1` |
| `PLEX1` |
| `POWERNATION1` |
| `PT1` |
| `RAKUTEN_DE1` |
| `RAKUTEN_EN1` |
| `RAKUTEN_ES1` |
| `RAKUTEN_FR1` |
| `RAKUTEN_IT1` |
| `RAKUTEN_NL1` |
| `RAKUTEN_PL1` |
| `RALLY_TV1` |
| `RO1` |
| `RO2` |
| `SA1` |
| `SA2` |
| `SAMSUNG1` |
| `SE1` |
| `SG1` |
| `SK1` |
| `SPORTKLUB1` |
| `SSPORTPLUS1` |
| `SV1` |
| `TBNPLUS1` |
| `TENNIS1` |
| `THESPORTPLUS1` |
| `TR1` |
| `TR3` |
| `UK1` |
| `US1` |
| `US_LOCALS2` |
| `US_SPORTS1` |
| `UY1` |
| `VN1` |
| `VOA1` |
| `ZA1` |
| `viva-russia.ru` |
### Download the guide
Windows (Command Prompt):
```sh
SET "NODE_OPTIONS=--max-old-space-size=6000" && npm run grab --- --channels=sites/epgshare01.online/epgshare01.online_<TAG>.channels.xml
```
Windows (PowerShell):
```sh
$env:NODE_OPTIONS="--max-old-space-size=6000"; npm run grab --- --channels=sites/epgshare01.online/epgshare01.online_<TAG>.channels.xml
```
Linux and macOS:
```sh
NODE_OPTIONS=--max-old-space-size=6000 npm run grab --- --channels=sites/epgshare01.online/epgshare01.online_<TAG>.channels.xml
```
### Update channel list
```sh
npm run channels:parse --- --config=./sites/epgshare01.online/epgshare01.online.config.js --output=./sites/epgshare01.online/epgshare01.online_<TAG>.channels.xml --set=tag:<TAG>
```
### Test
```sh
npm test --- epgshare01.online
```
# epgshare01.online
https://epgshare01.online/epgshare01/
| Tag |
| ------------------- |
| `ALJAZEERA1` |
| `AR1` |
| `ASIANTELEVISION1` |
| `AU1` |
| `BA1` |
| `BE2` |
| `BEIN1` |
| `BG1` |
| `BR1` |
| `CA1` |
| `CH1` |
| `CL1` |
| `CO1` |
| `CR1` |
| `CY1` |
| `CZ1` |
| `DE1` |
| `DELUXEMUSIC1` |
| `DIRECTVSPORTS1` |
| `DISTROTV1` |
| `DK1` |
| `DO1` |
| `DRAFTKINGS1` |
| `DUMMY_CHANNELS` |
| `EC1` |
| `EG1` |
| `ES1` |
| `EUROSPORT1` |
| `FANDUEL1` |
| `FI1` |
| `FR1` |
| `GR1` |
| `HK1` |
| `HR1` |
| `HU1` |
| `ID1` |
| `IE1` |
| `IL1` |
| `IN4` |
| `IT1` |
| `JM1` |
| `JP1` |
| `JP2` |
| `KE1` |
| `KR1` |
| `MT1` |
| `MX1` |
| `MY1` |
| `NAUTICAL_CHANNEL1` |
| `NG1` |
| `NL1` |
| `NO1` |
| `NZ1` |
| `OPTUSSPORTS1` |
| `PA1` |
| `PAC-12` |
| `PE1` |
| `PH1` |
| `PH2` |
| `PK1` |
| `PL1` |
| `PLEX1` |
| `POWERNATION1` |
| `PT1` |
| `RAKUTEN_DE1` |
| `RAKUTEN_EN1` |
| `RAKUTEN_ES1` |
| `RAKUTEN_FR1` |
| `RAKUTEN_IT1` |
| `RAKUTEN_NL1` |
| `RAKUTEN_PL1` |
| `RALLY_TV1` |
| `RO1` |
| `RO2` |
| `SA1` |
| `SA2` |
| `SAMSUNG1` |
| `SE1` |
| `SG1` |
| `SK1` |
| `SPORTKLUB1` |
| `SSPORTPLUS1` |
| `SV1` |
| `TBNPLUS1` |
| `TENNIS1` |
| `THESPORTPLUS1` |
| `TR1` |
| `TR3` |
| `UK1` |
| `US1` |
| `US_LOCALS2` |
| `US_SPORTS1` |
| `UY1` |
| `VN1` |
| `VOA1` |
| `ZA1` |
| `viva-russia.ru` |
### Download the guide
Windows (Command Prompt):
```sh
SET "NODE_OPTIONS=--max-old-space-size=6000" && npm run grab --- --channels=sites/epgshare01.online/epgshare01.online_<TAG>.channels.xml
```
Windows (PowerShell):
```sh
$env:NODE_OPTIONS="--max-old-space-size=6000"; npm run grab --- --channels=sites/epgshare01.online/epgshare01.online_<TAG>.channels.xml
```
Linux and macOS:
```sh
NODE_OPTIONS=--max-old-space-size=6000 npm run grab --- --channels=sites/epgshare01.online/epgshare01.online_<TAG>.channels.xml
```
### Update channel list
```sh
npm run channels:parse --- --config=./sites/epgshare01.online/epgshare01.online.config.js --output=./sites/epgshare01.online/epgshare01.online_<TAG>.channels.xml --set=tag:<TAG>
```
### Test
```sh
npm test --- epgshare01.online
```

View File

@@ -4,7 +4,7 @@ const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const { uniqBy } = require('../../scripts/functions')
const uniqBy = require('lodash.uniqby')
dayjs.extend(utc)
dayjs.extend(timezone)

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@ const doFetch = require('@ntlab/sfetch')
const axios = require('axios')
const dayjs = require('dayjs')
const crypto = require('crypto')
const { sortBy } = require('../../scripts/functions')
const sortBy = require('lodash.sortby')
// API Configuration Constants
const NATCO_CODE = 'hr'

View File

@@ -3,7 +3,7 @@ const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const { sortBy } = require('../../scripts/functions')
const sortBy = require('lodash.sortby')
dayjs.extend(utc)
dayjs.extend(timezone)

View File

@@ -4,7 +4,7 @@ const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const { uniqBy } = require('../../scripts/functions')
const uniqBy = require('lodash.uniqby')
dayjs.extend(utc)
dayjs.extend(timezone)

View File

@@ -1,127 +1,127 @@
const durationParser = require('parse-duration').default
const cheerio = require('cheerio')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat')
dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(customParseFormat)
module.exports = {
site: 'programme-tv.net',
days: 2,
request: {
headers: {
cookie: 'authId=b7154156fe4fb8acdb6f38e1207c6231'
}
},
url: function ({ date, channel }) {
return `https://www.programme-tv.net/programme/chaine/${date.format('YYYY-MM-DD')}/programme-${
channel.site_id
}.html`
},
parser: function ({ content, date }) {
const programs = []
const items = parseItems(content)
items.forEach(item => {
const $item = cheerio.load(item)
const title = parseTitle($item)
const subTitle = parseSubtitle($item)
const image = parseImage($item)
const category = parseCategory($item)
const start = parseStart($item, date)
const duration = parseDuration($item)
const stop = start.add(duration, 'ms')
programs.push({ title, subTitle, image, category, start, stop })
})
return programs
},
async channels() {
const axios = require('axios')
const data = await axios
.get(
`https://www.programme-tv.net/_esi/channel-list/${dayjs().format(
'YYYY-MM-DD'
)}/?bouquet=perso&modal=0`,
{
headers: {
cookie: 'authId=b7154156fe4fb8acdb6f38e1207c6231'
}
}
)
.then(r => r.data)
.catch(console.error)
let channels = []
const $ = cheerio.load(data)
$('.channelList-listItemsLink').each((i, el) => {
const name = $(el).attr('title')
const url = $(el).attr('href')
const [, site_id] = url.match(/\/programme-(.*)\.html$/i)
channels.push({
lang: 'fr',
site_id,
name
})
})
return channels
}
}
function parseStart($item, date) {
let time = $item('.mainBroadcastCard-startingHour').first().text().trim()
time = `${date.format('MM/DD/YYYY')} ${time.replace('h', ':')}`
return dayjs.tz(time, 'MM/DD/YYYY HH:mm', 'Europe/Paris')
}
function parseDuration($item) {
const duration = $item('.mainBroadcastCard-durationContent').first().text().trim()
return durationParser(duration)
}
function parseImage($item) {
const img = $item('.mainBroadcastCard-imageContent').first().find('img')
const value = img.attr('srcset') || img.data('srcset')
let url = null
if (value) {
const sources = value.split(',').map(s => s.trim())
for (const source of sources) {
const [src, descriptor] = source.split(/\s+/)
if (descriptor === '128w') {
url = src.replace('128x180', '960x540')
break
}
}
}
return url
}
function parseCategory($item) {
return $item('.mainBroadcastCard-format').first().text().trim()
}
function parseTitle($item) {
return $item('.mainBroadcastCard-title').first().text().trim()
}
function parseItems(content) {
const $ = cheerio.load(content)
return $('.mainBroadcastCard').toArray()
}
function parseSubtitle($item) {
return $item('.mainBroadcastCard-subtitle').text().trim() || null
const durationParser = require('parse-duration').default
const cheerio = require('cheerio')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat')
dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(customParseFormat)
module.exports = {
site: 'programme-tv.net',
days: 2,
request: {
headers: {
cookie: 'authId=b7154156fe4fb8acdb6f38e1207c6231'
}
},
url: function ({ date, channel }) {
return `https://www.programme-tv.net/programme/chaine/${date.format('YYYY-MM-DD')}/programme-${
channel.site_id
}.html`
},
parser: function ({ content, date }) {
const programs = []
const items = parseItems(content)
items.forEach(item => {
const $item = cheerio.load(item)
const title = parseTitle($item)
const subTitle = parseSubtitle($item)
const image = parseImage($item)
const category = parseCategory($item)
const start = parseStart($item, date)
const duration = parseDuration($item)
const stop = start.add(duration, 'ms')
programs.push({ title, subTitle, image, category, start, stop })
})
return programs
},
async channels() {
const axios = require('axios')
const data = await axios
.get(
`https://www.programme-tv.net/_esi/channel-list/${dayjs().format(
'YYYY-MM-DD'
)}/?bouquet=perso&modal=0`,
{
headers: {
cookie: 'authId=b7154156fe4fb8acdb6f38e1207c6231'
}
}
)
.then(r => r.data)
.catch(console.error)
let channels = []
const $ = cheerio.load(data)
$('.channelList-listItemsLink').each((i, el) => {
const name = $(el).attr('title')
const url = $(el).attr('href')
const [, site_id] = url.match(/\/programme-(.*)\.html$/i)
channels.push({
lang: 'fr',
site_id,
name
})
})
return channels
}
}
function parseStart($item, date) {
let time = $item('.mainBroadcastCard-startingHour').first().text().trim()
time = `${date.format('MM/DD/YYYY')} ${time.replace('h', ':')}`
return dayjs.tz(time, 'MM/DD/YYYY HH:mm', 'Europe/Paris')
}
function parseDuration($item) {
const duration = $item('.mainBroadcastCard-durationContent').first().text().trim()
return durationParser(duration)
}
function parseImage($item) {
const img = $item('.mainBroadcastCard-imageContent').first().find('img')
const value = img.attr('srcset') || img.data('srcset')
let url = null
if (value) {
const sources = value.split(',').map(s => s.trim())
for (const source of sources) {
const [src, descriptor] = source.split(/\s+/)
if (descriptor === '128w') {
url = src.replace('128x180', '960x540')
break
}
}
}
return url
}
function parseCategory($item) {
return $item('.mainBroadcastCard-format').first().text().trim()
}
function parseTitle($item) {
return $item('.mainBroadcastCard-title').first().text().trim()
}
function parseItems(content) {
const $ = cheerio.load(content)
return $('.mainBroadcastCard').toArray()
}
function parseSubtitle($item) {
return $item('.mainBroadcastCard-subtitle').text().trim() || null
}

View File

@@ -5,7 +5,7 @@ const cheerio = require('cheerio')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const { startCase } = require('../../scripts/functions')
const startCase = require('lodash.startcase')
dayjs.extend(utc)
dayjs.extend(timezone)

View File

@@ -3,7 +3,7 @@ const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const doFetch = require('@ntlab/sfetch')
const debug = require('debug')('site:sky.com')
const { sortBy } = require('../../scripts/functions')
const sortBy = require('lodash.sortby')
dayjs.extend(utc)

View File

@@ -2,7 +2,8 @@ const cheerio = require('cheerio')
const dayjs = require('dayjs')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const timezone = require('dayjs/plugin/timezone')
const { sortBy, uniqBy } = require('../../scripts/functions')
const sortBy = require('lodash.sortby')
const uniqBy = require('lodash.uniqby')
dayjs.extend(customParseFormat)
dayjs.extend(timezone)

View File

@@ -1,141 +1,141 @@
const axios = require('axios')
const cheerio = require('cheerio')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const doFetch = require('@ntlab/sfetch')
const debug = require('debug')('site:tivie.id')
dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(customParseFormat)
doFetch.setDebugger(debug)
const tz = 'Asia/Jakarta'
module.exports = {
site: 'tivie.id',
days: 2,
url({ channel, date }) {
return `https://tivie.id/channel/${channel.site_id}/${date.format('YYYYMMDD')}`
},
async parser({ content, date }) {
const programs = []
if (content) {
const $ = cheerio.load(content)
const items = $('ul[x-data] > li[id*="event-"] > div.w-full')
.toArray()
.map(item => {
const $item = $(item)
const time = $item.find('div:nth-child(1) span:nth-child(1)')
const info = $item.find('div:nth-child(2) h5')
const detail = info.find('a')
const p = {
start: dayjs.tz(`${date.format('YYYY-MM-DD')} ${time.html()}`, 'YYYY-MM-DD HH:mm', tz)
}
if (detail.length) {
const subtitle = detail.find('div')
p.title = parseText(subtitle.length ? subtitle : detail)
p.url = detail.attr('href')
} else {
p.title = parseText(info)
}
if (p.title) {
const [, , season, episode] = p.title.match(/( S(\d+))?, Ep\. (\d+)/) || [
null,
null,
null,
null
]
if (season) {
p.season = parseInt(season)
}
if (episode) {
p.episode = parseInt(episode)
}
}
return p
})
// fetch detailed guide if necessary
const queues = items
.filter(i => i.url)
.map(i => {
const url = i.url
delete i.url
return { i, url }
})
if (queues.length) {
await doFetch(queues, (queue, res) => {
const $ = cheerio.load(res)
const img = $('#main-content > div > div:nth-child(1) img')
const info = $('#main-content > div > div:nth-child(2)')
const title = parseText(info.find('h2:nth-child(2)'))
if (!queue.i.title.startsWith(title) && !queue.i.title.startsWith('LIVE ')) {
queue.i.subTitle = parseText(info.find('h2:nth-child(2)'))
}
const desc1 = parseText(info.find('div[class=""]:nth-child(3)'))
const desc2 = parseText(info.find('div[class=""]:nth-child(4)'))
if (desc2 == '') {
queue.i.description = desc1.replace('TiViE.id | ', '')
} else {
queue.i.description = desc2.replace('TiViE.id | ', '')
queue.i.date = parseText(info.find('h2:nth-child(3)'))
}
queue.i.categories = parseText(info.find('div[class=""]:nth-child(1)')).split(', ')
queue.i.image = img.length ? img.attr('src') : null
})
}
// fill start-stop
for (let i = 0; i < items.length; i++) {
if (i < items.length - 1) {
items[i].stop = items[i + 1].start
} else {
items[i].stop = dayjs.tz(
`${date.add(1, 'd').format('YYYY-MM-DD')} 00:00`,
'YYYY-MM-DD HH:mm',
tz
)
}
}
// add programs
programs.push(...items)
}
return programs
},
async channels({ lang = 'id' }) {
const result = await axios
.get('https://tivie.id/channel')
.then(response => response.data)
.catch(console.error)
const $ = cheerio.load(result)
const items = $('ul[x-data] li[x-data] div header h2 a').toArray()
const channels = items.map(item => {
const $item = $(item)
const url = $item.attr('href')
return {
lang,
site_id: url.substr(url.lastIndexOf('/') + 1),
name: $item.find('strong').text()
}
})
return channels
}
}
function parseText($item) {
let text = $item.text().replace(/\t/g, '').replace(/\n/g, ' ').trim()
while (true) {
if (text.match(/\s\s/)) {
text = text.replace(/\s\s/g, ' ')
continue
}
break
}
return text
}
const axios = require('axios')
const cheerio = require('cheerio')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const doFetch = require('@ntlab/sfetch')
const debug = require('debug')('site:tivie.id')
dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(customParseFormat)
doFetch.setDebugger(debug)
const tz = 'Asia/Jakarta'
module.exports = {
site: 'tivie.id',
days: 2,
url({ channel, date }) {
return `https://tivie.id/channel/${channel.site_id}/${date.format('YYYYMMDD')}`
},
async parser({ content, date }) {
const programs = []
if (content) {
const $ = cheerio.load(content)
const items = $('ul[x-data] > li[id*="event-"] > div.w-full')
.toArray()
.map(item => {
const $item = $(item)
const time = $item.find('div:nth-child(1) span:nth-child(1)')
const info = $item.find('div:nth-child(2) h5')
const detail = info.find('a')
const p = {
start: dayjs.tz(`${date.format('YYYY-MM-DD')} ${time.html()}`, 'YYYY-MM-DD HH:mm', tz)
}
if (detail.length) {
const subtitle = detail.find('div')
p.title = parseText(subtitle.length ? subtitle : detail)
p.url = detail.attr('href')
} else {
p.title = parseText(info)
}
if (p.title) {
const [, , season, episode] = p.title.match(/( S(\d+))?, Ep\. (\d+)/) || [
null,
null,
null,
null
]
if (season) {
p.season = parseInt(season)
}
if (episode) {
p.episode = parseInt(episode)
}
}
return p
})
// fetch detailed guide if necessary
const queues = items
.filter(i => i.url)
.map(i => {
const url = i.url
delete i.url
return { i, url }
})
if (queues.length) {
await doFetch(queues, (queue, res) => {
const $ = cheerio.load(res)
const img = $('#main-content > div > div:nth-child(1) img')
const info = $('#main-content > div > div:nth-child(2)')
const title = parseText(info.find('h2:nth-child(2)'))
if (!queue.i.title.startsWith(title) && !queue.i.title.startsWith('LIVE ')) {
queue.i.subTitle = parseText(info.find('h2:nth-child(2)'))
}
const desc1 = parseText(info.find('div[class=""]:nth-child(3)'))
const desc2 = parseText(info.find('div[class=""]:nth-child(4)'))
if (desc2 == '') {
queue.i.description = desc1.replace('TiViE.id | ', '')
} else {
queue.i.description = desc2.replace('TiViE.id | ', '')
queue.i.date = parseText(info.find('h2:nth-child(3)'))
}
queue.i.categories = parseText(info.find('div[class=""]:nth-child(1)')).split(', ')
queue.i.image = img.length ? img.attr('src') : null
})
}
// fill start-stop
for (let i = 0; i < items.length; i++) {
if (i < items.length - 1) {
items[i].stop = items[i + 1].start
} else {
items[i].stop = dayjs.tz(
`${date.add(1, 'd').format('YYYY-MM-DD')} 00:00`,
'YYYY-MM-DD HH:mm',
tz
)
}
}
// add programs
programs.push(...items)
}
return programs
},
async channels({ lang = 'id' }) {
const result = await axios
.get('https://tivie.id/channel')
.then(response => response.data)
.catch(console.error)
const $ = cheerio.load(result)
const items = $('ul[x-data] li[x-data] div header h2 a').toArray()
const channels = items.map(item => {
const $item = $(item)
const url = $item.attr('href')
return {
lang,
site_id: url.substr(url.lastIndexOf('/') + 1),
name: $item.find('strong').text()
}
})
return channels
}
}
function parseText($item) {
let text = $item.text().replace(/\t/g, '').replace(/\n/g, ' ').trim()
while (true) {
if (text.match(/\s\s/)) {
text = text.replace(/\s\s/g, ' ')
continue
}
break
}
return text
}

View File

@@ -1,6 +1,6 @@
const { DateTime } = require('luxon')
const axios = require('axios')
const { uniqBy } = require('../../scripts/functions')
const uniqBy = require('lodash.uniqby')
module.exports = {
site: 'tv.mail.ru',

View File

@@ -1,492 +1,492 @@
<?xml version="1.0" encoding="UTF-8"?>
<channels>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TF1.fr" site_id="192">TF1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France2.fr" site_id="4">France 2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr" site_id="80">France 3</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France4.fr" site_id="78">France 4</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France5.fr" site_id="47">France 5</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="M6.fr" site_id="118">M6</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="arte.fr" site_id="111">Arte</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LCP.fr" site_id="234">LCP-Public Sénat</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="W9.fr" site_id="119">W9</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TMC.fr" site_id="195">TMC</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TFX.fr" site_id="446">TFX</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Gulli.fr" site_id="482">Gulli</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMTV.fr" site_id="481">BFM TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CNews.fr" site_id="226">CNews</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LCI.fr" site_id="112">LCI</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Franceinfo.fr" site_id="2111">franceinfo:</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CStar.fr" site_id="458">CStar</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="T18.fr" site_id="4139">T18</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NOVO19.fr" site_id="4138">NOVO19</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TF1SeriesFilms.fr" site_id="1404">TF1 Séries-Films</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LEquipe.fr" site_id="1401">L'équipe</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="6ter.fr" site_id="1403">6ter</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RMCStory.fr" site_id="1402">RMC Story</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RMCDecouverte.fr" site_id="1400">RMC Découverte</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Cherie25.fr" site_id="1399">Chérie 25</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="i24NEWSFrench.il" site_id="781">i24 News</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3946">AFTER FOOT TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMBusiness.fr" site_id="1073">BFM Business</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TechCo.fr" site_id="2942">TECH&amp;CO</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RMCSport1.fr" site_id="2665">RMC Sport 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RMCSport2.fr" site_id="2666">RMC Sport Live 2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMMarseille.fr" site_id="3289">BFM MARSEILLE PROVENCE</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMLyon.fr" site_id="116">BFM Lyon</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3947">RMC Talk Info</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DiscoveryChannel.fr" site_id="400">Discovery Channel</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TLC.fr" site_id="1374">TLC</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DiscoveryChannel.fr" site_id="2184">Discovery Investigation</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3948">BFM Grands Reportages</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France24.fr@French" site_id="529">France 24</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="EuronewsFrench.fr" site_id="140">Euronews Fra</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3949">RMC Alerte Secours</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="13emeRue.fr" site_id="2">13ème rue</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Syfy.fr" site_id="479">Syfy</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="E.fr" site_id="405">E! Entertainment</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="WarnerTV.fr" site_id="2334">WARNER TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MTV.fr" site_id="128">MTV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MCM.fr" site_id="121">MCM</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AB1.fr" site_id="5">AB1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SerieClub.fr" site_id="49">SERIE CLUB</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="GameOne.fr" site_id="87">Game One</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="GameOne.fr@Plus1" site_id="1167">Game One+1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="WarnerTVNext.fr" site_id="2040">Warner TV Next</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="JOne.fr" site_id="1585">J-One</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BET.fr" site_id="1960">BET</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ComedyCentral.fr" site_id="2752">Comedy Central</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="1765">Netflix</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="2941">Prime Vidéo</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="2974">Disney+</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ParisPremiere.fr" site_id="145">Paris Première</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Teva.fr" site_id="191">Téva</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RTL9.lu" site_id="115">RTL9</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVBreizh.fr" site_id="225">TV Breizh</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TV5MondeFranceBelgiumSwitzerlandMonaco.fr" site_id="205">TV5 Monde</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TF1.fr@4K" site_id="3425">TF1 4K</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France2.fr@4K" site_id="4012">France 2 UHD</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CanalPlusSport360.fr" site_id="3504">CANAL+ SPORT 360</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CanalPlusFoot.fr" site_id="3501">CANAL+ FOOT</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CanalPlusBoxOffice.fr" site_id="3779">CANAL+ BOX OFFICE</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CanalPlusGrandEcran.fr" site_id="3349">CANAL+ GRAND ECRAN</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CanalPlusDocs.fr" site_id="3347">CANAL+ DOCS</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CanalPlusKids.fr" site_id="3348">CANAL+ KIDS</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFM2.fr" site_id="4092">BFM2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="PublicSenat2424.fr" site_id="992">LCP-AN 24/24</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="PublicSenat2424.fr" site_id="2013">Public Sénat 24/24</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LaChaineMeteo.fr" site_id="124">LA CHAINE METEO</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RMCSportAccess1.fr" site_id="2095">RMC Sport Access</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3953">RMC Mecanic</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINSports1.qa@France" site_id="1290">beIN SPORTS 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINSports2.qa@France" site_id="1304">beIN SPORTS 2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINSports3.qa@France" site_id="1335">beIN SPORTS 3</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DAZN1.uk" site_id="3795">DAZN 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Equidia.fr" site_id="64">Equidia</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MGGTV.fr" site_id="2353">MGG TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Automotolachaine.fr" site_id="15">Auto Moto</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="JournalDuGolf.fr" site_id="3629">Journal du Golf TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SportenFrance.fr" site_id="2837">Sport en France</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="OLTV.fr" site_id="463">OLPLAY</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Eurosport1.fr" site_id="76">EUROSPORT 1 HD</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Eurosport2.fr" site_id="439">EUROSPORT 2 HD</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CinePlusOCS.fr" site_id="282">OCS</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CinePlusFrisson.fr" site_id="284">CINE+ frisson</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CinePlusEmotion.fr" site_id="283">CINE+ émotion</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CinePlusFamily.fr" site_id="401">CINE+ family</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CinePlusFestival.fr" site_id="285">CINE+ festival</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CinePlusClassic.fr" site_id="287">CINE+ classic</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DisneyChannel.fr" site_id="58">DISNEY CHANNEL</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DisneyChannel.fr@Plus1" site_id="299">DISNEY CHANNEL +1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ParamountChannel.fr" site_id="1562">Paramount Network</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ParamountChannelOffset.fr" site_id="2072">Paramount Network Décalé</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TCMCinema.fr" site_id="185">TCM Cinéma</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Action.fr" site_id="10">Action</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="InsomniacTV.us" site_id="4101">INSOMNIA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3950">RMC WOW</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3952">RMC Mystère</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3951">J'irai dormir chez vous</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="UshuaiaTV.fr" site_id="451">Ushuaia TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Trek.fr" site_id="1776">TREK</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CrimeDistrict.fr" site_id="2037">Crime District</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MarmitonTV.fr" site_id="3155">Marmiton TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="HistoireTV.fr" site_id="88">Histoire TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ToutelHistoire.fr" site_id="7">Toute l'histoire</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="KTO.fr" site_id="110">KTO</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Animaux.fr" site_id="12">Animaux</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ChassePeche.fr" site_id="38">Chasse et Pêche</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ScienceVieTV.fr" site_id="63">Science et Vie TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LuxeTV.lu" site_id="531">Luxe TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="FashionTVEurope.fr" site_id="357">Fashion TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MensUPTV.fr" site_id="1452">Men's Up TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AstrocenterTV.fr" site_id="394">Astrocenter.tv</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MyZenTV.fr" site_id="829">My Zen TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MuseumTVFrench.fr" site_id="1072">MUSEUM TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MuseumTVFrench.fr@4K" site_id="3177">Museum TV 4K</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Exploreorg.us" site_id="3782">EXPLORE</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LeFigaroLive.fr" site_id="3768">Le Figaro TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Seasons.fr" site_id="173">SEASONS</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3762">KITCHEN MANIA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TopSanteTV.fr" site_id="3106">Top Santé TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MaisonTravauxTV.fr" site_id="3360">Maison &amp; Travaux TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AutoPlus.ru" site_id="3287">Auto Plus TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NickelodeonJunior.fr" site_id="888">Nickelodeon Junior</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Boomerang.fr" site_id="321">Boomerang</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Boomerang.fr@Plus1" site_id="928">Boomerang+1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TiJi.fr" site_id="229">Tiji</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DreamWorks.fr" site_id="3597">DREAMWORKS</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Nickelodeon.fr" site_id="473">Nickelodeon</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Nickelodeon.fr@Plus1" site_id="2065">Nickelodeon+1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CanalJ.fr" site_id="32">Canal J</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CartoonNetworkWesternEurope.uk" site_id="36">Cartoon Network</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NickelodeonTeen.fr" site_id="1746">Nickelodeon Teen</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CartoonitoWesternEurope.uk" site_id="3738">Cartoonito</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TraceVanillaIslands.fr" site_id="2977">Trace Vanilla</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Mangas.fr" site_id="6">Mangas</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LuckyJacktv.lu" site_id="1061">Lucky Jack</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MTVHits.fr" site_id="2006">MTV Hits</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="M6Music.fr" site_id="453">M6 Music</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RFMTV.fr" site_id="241">RFM TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NRJHits.fr" site_id="605">NRJ Hits</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TraceLatina.fr" site_id="2744">Trace Latina</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Mezzo.fr" site_id="125">Mezzo</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MezzoLive.fr" site_id="907">Mezzo Live</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Melody.fr" site_id="265">Melody TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TraceUrban.fr" site_id="325">Trace Urban</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TraceToca.fr" site_id="1948">Trace Toca</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TraceCaribbean.fr" site_id="753">TRACE CARIBBEAN</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TraceGospel.fr" site_id="2022">Trace Gospel</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MelodydAfrique.fr" site_id="2321">Melody d'Afrique</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMGrandLille.fr" site_id="930">BFM Grand Lille</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMGrandLittoral.fr" site_id="2317">BFM Grand Littoral</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMCotedAzur.fr" site_id="3291">BFM NICE COTE D'AZUR</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMVar.fr" site_id="3290">BFM TOULON VAR</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMDICIAlpesduSud.fr" site_id="3165">BFM DICI ALPES DU SUD</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMDICIHauteProvence.fr" site_id="3164">BFM DICI HAUTE-PROVENCE</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMAlsace.fr" site_id="3460">BFM ALSACE</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMNormandie.fr" site_id="3557">BFM Normandie</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ViaOccitaniePaysGardois.fr" site_id="538">vià30</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ViaOccitanieToulouse.fr" site_id="2302">vià31</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ViaOccitanieMontpellier.fr" site_id="704">vià34</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ViaOccitaniePaysCatalan.fr" site_id="2303">vià66</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINSportsMax4.qa@France" site_id="1336">beIN SPORTS MAX 4</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINSportsMax5.qa@France" site_id="1337">beIN SPORTS MAX 5</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINSportsMax6.qa@France" site_id="1338">beIN SPORTS MAX 6</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINSportsMax7.qa@France" site_id="1339">beIN SPORTS MAX 7</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINSportsMax8.qa@France" site_id="1340">beIN SPORTS MAX 8</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINSportsMax9.qa@France" site_id="1341">beIN SPORTS MAX 9</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINSportsMax10.qa@France" site_id="1342">beIN SPORTS MAX 10</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="GolfporMovistarPlusPlus.es" site_id="1295">GOLF+ HD</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="4068">CANAL+LIVE 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="4069">CANAL+LIVE 2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="4070">CANAL+LIVE 3</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="4071">CANAL+LIVE 4</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="4072">CANAL+LIVE 5</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="4073">CANAL+LIVE 6</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="4074">CANAL+LIVE 7</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RMCSport3.fr" site_id="3171">RMC Sport Live 3</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RMCSport4.fr" site_id="3172">RMC Sport Live 4</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="XXL.fr" site_id="218">XXL</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DorcelTV.nl" site_id="560">Dorcel TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DorcelXXX.nl" site_id="1474">Dorcel XXX</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="PrivateTV.nl" site_id="558">Private TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="HustlerTVEurope.nl" site_id="416">Hustler TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="VixenHD.us" site_id="3169">VIXEN</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="PinkTV.fr" site_id="406">Pink TV / Pink X</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ManX.be" site_id="683">Man-X</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="UnionTV.py" site_id="2376">Union TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DorcelTVAfrica.nl" site_id="2838">DORCEL TV AFRICA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MentTV.be" site_id="3994">MEN TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="PenthouseHD1.us" site_id="953">PENTHOUSE HD</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DasErste.de" site_id="13">Das Erste</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Sport1.hu" site_id="60">SPORT 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Alpes" site_id="1921">France 3 Alpes</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Alsace" site_id="1922">France 3 Alsace</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Aquitaine" site_id="1923">France 3 Aquitaine</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Auvergne" site_id="1924">France 3 Auvergne</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@NormandieCaen" site_id="1925">France 3 Basse-Normandie</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Bourgogne" site_id="1926">France 3 Bourgogne</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Bretagne" site_id="1927">France 3 Bretagne</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Centre" site_id="1928">France 3 Centre</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@ChampagneArdenne" site_id="1929">France 3 Champagne-Ardenne</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@CorseViaStella" site_id="308">France 3 via Stella</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@CoteDAzur" site_id="1931">France 3 Côte d'Azur</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@FrancheComte" site_id="1932">France 3 Franche-Comté</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@NormandieRouen" site_id="1933">France 3 Haute-Normandie</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Languedoc" site_id="1934">France 3 Languedoc</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Limousin" site_id="1935">France 3 Limousin</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Lorraine" site_id="1936">France 3 Lorraine</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@MidiPyrenees" site_id="1937">France 3 Midi-Pyrénées</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@NordPCalais" site_id="1938">France 3 Nord-Pas-de-Calais</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@ParisIDF" site_id="1939">France 3 Paris IDF</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@PaysDeLaLoire" site_id="1940">France 3 Pays de la Loire</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Picardie" site_id="1941">France 3 Picardie</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@PoitouCharentes" site_id="1942">France 3 Poitou-Charentes</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@ProvenceAlpes" site_id="1943">France 3 Provence-Alpes</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@RhoneAlpes" site_id="1944">France 3 Rhône-Alpes</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@NoA" site_id="2730">France 3 Nouvelle Aquitaine</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Corse" site_id="1930">France 3 - Corse</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="20MinutesTV.fr" site_id="701">20 Minutes TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TeleBocal.fr" site_id="1111">Télé Bocal</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Via93.fr" site_id="1738">vià93</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LeFigaroTVIledeFrance.fr" site_id="863">Figaro TV IDF</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TV78.fr" site_id="1023">TV78</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LyonCapitaleTV.fr" site_id="1197">Lyon Capitale TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TeleGrenoble.fr" site_id="537">Télé Grenoble</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TL7.fr" site_id="1151">TL7 Saint Etienne</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TV8MontBlanc.fr" site_id="421">8 Mont-Blanc</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ILTV.fr" site_id="1724">ILTV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ASTV.fr" site_id="1721">ASTV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="WeoPicardie.fr" site_id="3778">Wéo Picardie</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Weo.fr" site_id="809">Wéo TV, La voix du nord</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3797">CRESPIN TELEVISION</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Matele.be" site_id="393">Vià MATÉLÉ</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="7ALimoges.fr" site_id="1719">7A Limoges</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TV7Bordeaux.fr" site_id="273">TV7 Bordeaux</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVPI.fr" site_id="2275">TVPI (TV Biarritz)</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ETB1.es" site_id="71">ETB1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ETB2.es" site_id="72">ETB2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ETB3.es" site_id="1723">ETB3</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Kanaldude.fr" site_id="3563">KANALDUDE</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CanalLocalGrosblie.fr" site_id="2369">Grosbliederstroff</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TV2COM.fr" site_id="2373">TV2COM</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="2372">TRESSANGE TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NATV.fr" site_id="3760">NA TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Canal32.fr" site_id="534">Canal 32</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MoselleTV.fr" site_id="1045">vià Mirabelle</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="PuissanceTV.fr" site_id="3771">Puissance TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CanalSchilick.fr" site_id="1735">Télé Schiltigheim</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVMonaco.mc" site_id="3786">TVMonaco</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MosaikCristal.fr" site_id="1700">Mosaik Cristal</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TV8MoselleEst.fr" site_id="1638">TV8 Moselle-Est</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="VosgesTV.fr" site_id="1095">vià Vosges</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CannesLerinsTV.fr" site_id="3770">Cannes Lérins TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MaritimaTV.fr" site_id="1185">Maritima TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MaurienneTV.fr" site_id="3137">MAURIENNE TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AngersTele.fr" site_id="1615">Angers Télé</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Telenantes.fr" site_id="491">Télé Nantes</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVVendee.fr" site_id="1268">TV Vendée</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LMTVSarthe.fr" site_id="535">LMtv Sarthe</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TebeoTV.fr" site_id="1321">Tébéo</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVR.fr" site_id="539">TV Rennes 35</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVTours.fr" site_id="540">Val de Loire TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ZoukTV.mq" site_id="1199">Zouk TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="viaTelePaese.fr" site_id="1750">Télé Paese</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CNNInternational.us@MENA" site_id="53">CNN International</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BBCNews.uk@Europe" site_id="19">BBC NEWS</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France24.fr@English" site_id="671">France 24 ENG</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CNBCEurope.uk" site_id="51">CNBC Europe</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BloombergTV.us@Europe" site_id="410">Bloomberg</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlJazeera.qa@English" site_id="525">Al Jazeera English</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="i24NEWSFrench.il@English" site_id="1717">i24 News Anglais</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NHKWorldJapan.jp" site_id="830">NHK WORLD-JAPAN</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SkyNews.uk" site_id="368">Sky News</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TGCom24.it" site_id="2274">TGCOM24</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="i24NEWSFrench.il@Arabe" site_id="1718">i24 News Arabe</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France24.fr@Arabe" site_id="814">France 24 ARA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlJazeera.qa" site_id="345">Al Jazeera</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Medi1TVAfrique.ma" site_id="665">Medi 1 TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Alarabiya.ae" site_id="1540">Al Arabiya</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="EchoroukTV.dz" site_id="1550">Echorouk TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="EnnaharTV.dz" site_id="1551">Ennahar TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SICNoticias.pt" site_id="1555">SIC Noticias</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Canal11.pt" site_id="3761">Canal 11</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RaiNews24.it" site_id="1129">Rai News 24</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France24.fr@Espagnol" site_id="3562">France 24 Espanol</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="24Horas.es" site_id="713">24 Horas</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVEInternacionalEuropeAsia.es" site_id="208">TVE</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DW.de" site_id="61">DW-TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="WELT.de" site_id="1726">WELT</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVN24.pl" site_id="871">TVN24</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RecordNews.br" site_id="793">Record News</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CGTN.cn" site_id="318">CGTN</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Africa24.fr" site_id="561">Africa 24</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Canal2International.cm" site_id="609">Canal 2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVIFiccao.pt" site_id="4100">V+</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="PortoCanal.pt" site_id="2050">Porto Canal</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LocalVisaoTV.pt" site_id="2347">Local Visao</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BenficaTV.pt" site_id="1623">Benfica TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ABolaTV.pt" site_id="2345">A BOLA TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RecordTVEuropa.pt" site_id="805">TV Record</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RTP3.pt" site_id="2313">RTP 3</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVIInternacional.pt" site_id="1637">TVI Internacional</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SICInternacional.pt" site_id="807">SIC Internacional</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CanalQ.pt" site_id="2046">Canal Q</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RTPInternacional.pt" site_id="169">RTPI</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Rai1.it" site_id="156">Rai Uno</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Rai2.it" site_id="154">Rai Due</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Rai3.it" site_id="155">Rai Tre</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RaiScuola.it" site_id="789">RAI SCUOLA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RaiStoria.it" site_id="790">RAI STORIA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MediasetItalia.it" site_id="1288">Mediaset Italia</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RealMadridTV.es" site_id="3600">REAL MADRID TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVEInternacionalEuropeAsia.es" site_id="2292">STAR TVE</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Antena3.es" site_id="1702">Antena 3</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Atreseries.es" site_id="1761">Atres Series</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AllFlamenco.es" site_id="3075">ALL FLAMENCO</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="GaliciaTVEuropa.es" site_id="1704">TVG EUROPA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TV3CAT.es" site_id="203">TV3 Catalunya</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ETBBasque.es" site_id="592">etb basque</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Telemadrid.es" site_id="3572">TELE MADRID</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AndaluciaTelevision.es" site_id="2530">ANDALUCIA TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Boomerang.us@English" site_id="2168">Boomerang Anglais</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="FilmBoxArthouse.nl" site_id="2446">FilmBox Arthouse</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TCMCinema.fr@English" site_id="2169">TCM Anglais</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3993">TinyTeen</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="4003">Lang Lab</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="4004">Lingo Toons</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DocuBox.nl" site_id="2444">DocuBox HD</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="FashionBox.nl" site_id="2445">FashionBox HD</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ProSieben.de" site_id="964">Pro Sieben</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ntv.de" site_id="1031">N-TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RTL.de" site_id="166">RTL Television</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RTLZwei.de" site_id="966">RTL2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SAT1.de" site_id="366">Sat 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RTLSuper.de" site_id="1854">Super RTL</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SWRFernsehenHD.de" site_id="182">SWR</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="VOX.de" site_id="971">Vox</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ZDF.de" site_id="219">ZDF</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="kabeleins.de" site_id="981">KABEL EINS</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="KiKA.de" site_id="982">KIKA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Nitro.de" site_id="1729">RTL NITRO TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="arte.fr@Deutsh" site_id="1189">Arte Allemande</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="3sat.de" site_id="960">3 SAT</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="1760">VIVA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ITVNEurope.pl" site_id="1010">iTVN</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ITVNExtra.pl" site_id="1975">iTVN Extra</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVPPolonia.pl" site_id="875">TVP Polonia</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Armenia1.am" site_id="724">Armenia 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="1026">Antenna 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ARTCinema.sa" site_id="625">ART CINEMA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ARTAflam1.sa" site_id="3541">ART AFLAM 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ARTAflam2.sa" site_id="579">ART AFLAM 2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ARTHekayat.sa" site_id="3542">AL HEKAYAT 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ARTHekayat2.sa" site_id="3543">AL HEKAYAT 2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="InfoTVRomania.ro" site_id="619">TV Romania International</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BahiaTV.dz" site_id="3727">Bahia</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TeleMaroc.ma" site_id="3380">Télé Maroc</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SamiraTV.dz" site_id="2299">Samira TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TV2.dz" site_id="346">Canal Algérie</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TV3.dz" site_id="1654">A3</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BeurTV.fr" site_id="608">Beur TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="2MMonde.ma" site_id="340">2M Maroc</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlAoula.ma" site_id="769">Al Aoula</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Arryadia.ma" site_id="956">Arryadia</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Assadissa.ma" site_id="959">Assadissa</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ElhiwarEttounsiTV.tn" site_id="2064">El Hiwar Ettounsi</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ElWatania2.tn" site_id="3728">Watania 2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TT1.tn" site_id="371">Tunisia 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AsharqNews.sa" site_id="3726">Asharq News</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BerbereJeunesse.fr" site_id="1113">Berbère Jeunesse</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BerbereMusic.fr" site_id="1112">Berbère Musique</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BerbereTV.fr" site_id="574">Berbère TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SkyNewsArabia.ae" site_id="3725">Sky News Arabia</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ChadaTV.ma" site_id="3774">CHADA TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RotanaComedy.sa" site_id="3902">Rotana Comedy</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SyriaTV.sy" site_id="4141">Syria TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlResalah.sa" site_id="1022">Al Resalah</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlArabyTV.qa" site_id="4093">Alaraby 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="EchoroukNews.dz" site_id="2832">Echorouk News</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ElBiladTV.dz" site_id="2957">El Bilad TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DiziSmartMax.tr" site_id="3736">Dizi</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlArabyTV2.qa" site_id="3020">Alaraby 2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RotanaAflamPlus.sa" site_id="3540">Rotana Aflam+</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3539">Rotana Cinéma+ FR</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RotanaMusic.sa" site_id="1019">Rotana Music</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RotanaDrama.sa" site_id="1763">Rotana Drama</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RotanaKids.sa" site_id="3548">Rotana Kids</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RotanaCinemaKSA.sa" site_id="795">Rotana Cinema</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RotanaClassic.sa" site_id="2878">Rotana Classic</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NessmaElJadida.tn" site_id="1096">Nessma</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DMC.eg" site_id="3537">DMC</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="FixFoxi.de" site_id="3381">Fix et Foxy</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CarthagePlus.tn" site_id="3376">Carthage+</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DMCDrama.eg" site_id="3544">DMC Drama</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="IqraaArabic.sa" site_id="3550">Iqraa TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="IqraaAfricaEurope.sa" site_id="3549">Iqraa International</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlMajdHolyQuran.sa" site_id="1698">Al Majd Holy Quran</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlMaghribia.ma" site_id="957">Al Maghribia</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Athaqafia.ma" site_id="958">Arrabiâ</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlJadeed.lb" site_id="1283">Al Jadeed</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="2218">LBC Sat</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LanaTV.lb" site_id="3800">Lana TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NBN.lb" site_id="1275">NBN</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="OTV.lb" site_id="1285">OTV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="945">Murr TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MPlus.sa" site_id="3810">Rotana M+</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DubaiTV.ae" site_id="589">Dubaï TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="OneNationTV.sn" site_id="3538">ON TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="HannibalTV.tn" site_id="3551">HANNIBAL TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="1614">Panorama Drama</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="1613">Panorama Film</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlMasriyah.eg" site_id="356">Al Masriya</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TheIsraeliNetwork.il" site_id="864">The Israeli Network</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="JordanTV.jo" site_id="603">Jordan Satellite Channel</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="EuroStar.tr" site_id="594">Euro Star</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="EuroD.tr" site_id="593">Euro D</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="HaberturkTV.tr" site_id="624">Habertürk</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINMoviesTurk.tr" site_id="2756">BEIN MOVIES</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ShowMax.tr" site_id="2778">SHOW MAX</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ShowTurk.tr" site_id="854">Show Turk</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ATVAvrupa.tr" site_id="2804">ATV Avrupa</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Kanal7Avrupa.tr" site_id="2777">KANAL 7 AVRUPA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TV8.tr" site_id="1949">TV8 International</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlSaudiya.sa" site_id="1733">Saudi Channel 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="APlus.fr" site_id="1990">A+</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ORTBTV.bj" site_id="1003">ORTB</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NovelasTV.fr" site_id="3769">NOVELAS</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CRTV.cm" site_id="571">CRTV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="EquinoxeTV.cm" site_id="984">Equinoxe TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CheriflaTV.ml" site_id="2784">CHERIFLA TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ORTCTV.km" site_id="1042">ORTC</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TeleCongo.cg" site_id="858">TV Congo</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MabokeTV.cg" site_id="3372">Maboke TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RTNC.cd" site_id="1097">RTNC</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NCI.ci" site_id="2913">NCI</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RTI1.ci" site_id="846">RTI 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CDirect.fr" site_id="3273">CDIRECT</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Gabon1ere.ga" site_id="985">Gabon 1ère</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RTG.mx" site_id="1071">RTG</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVMInternacional.mz" site_id="1041">TVM</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ORTM1.ml" site_id="697">ORTM</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NollywoodTVEpic.fr" site_id="2343">Nollywood TV Epic</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NollywoodTV.fr" site_id="1461">Nollywood TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="PULAAGU.sn" site_id="3970">Pulaagu</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TraceAfrica.fr" site_id="1179">Trace Africa</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="VoxAfrica.uk" site_id="1133">Vox Africa</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="2STV.sn" site_id="573">2STV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RTS.ec" site_id="847">RTS 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SenTV.sn" site_id="1640">SEN TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TFM.sn" site_id="1154">TFM</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SunuYeuf.sn" site_id="2908">SUNU YEUF</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BeijingSatelliteTV.cn" site_id="771">Beijing TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="773">CCTV YULE</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CCTV4Europe.cn" site_id="772">CCTV-4</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ChinaMovieChannel.cn" site_id="1138">China Movie Channel (CMC)</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="HunanTV.cn" site_id="782">Hunan TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="1196">JSBC International</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="PhoenixCNEChannel.hk" site_id="1128">Phoenix CNE</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="PhoenixInfoNewsChannel.hk" site_id="369">Phoenix Infonews</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DragonTV.cn" site_id="586">Shangaï Dragon TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="GreatWallElite.cn" site_id="880">Great Wall Elite</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ZTVWorld.cn" site_id="882">ZTV World (Zhejiang Star TV)</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="GRT.md" site_id="876">GRT GBA Satellite TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CGTNFrench.cn" site_id="774">CGTN-Français</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NTDTVEurope.us" site_id="1115">NTD</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="KBSWorld.kr" site_id="944">KBS World</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Colors.in" site_id="1180">Colors</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="UtsavBharat.uk" site_id="1974">Utsav Bharat</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ColorsRishteyEurope.in" site_id="1973">Rishtey</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="UtsavPlus.uk" site_id="1145">Utsav Plus</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ZeeTV.in" site_id="526">Zee TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NHKWorldPremium.jp" site_id="3799">NHK World Premium</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="B4UMovies.in" site_id="952">B4U Movies</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="GeoNews.pk" site_id="951">Geo News</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="GEOTelevision.de" site_id="950">Geo TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SonyMax.uk" site_id="1538">Sony Max</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LasEstrellas.mx" site_id="588">Las Estrellas</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DistritoComedia.mx" site_id="583">Distrito Comedia</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DePeliculaEuropa.mx" site_id="585">De pelicula</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RMS.mx" site_id="843">RMS</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TeleHit.mx" site_id="859">Telehit</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TlnovelasEuropa.mx" site_id="860">tlnovelas</channel>
<?xml version="1.0" encoding="UTF-8"?>
<channels>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TF1.fr" site_id="192">TF1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France2.fr" site_id="4">France 2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr" site_id="80">France 3</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France4.fr" site_id="78">France 4</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France5.fr" site_id="47">France 5</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="M6.fr" site_id="118">M6</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="arte.fr" site_id="111">Arte</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LCP.fr" site_id="234">LCP-Public Sénat</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="W9.fr" site_id="119">W9</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TMC.fr" site_id="195">TMC</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TFX.fr" site_id="446">TFX</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Gulli.fr" site_id="482">Gulli</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMTV.fr" site_id="481">BFM TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CNews.fr" site_id="226">CNews</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LCI.fr" site_id="112">LCI</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Franceinfo.fr" site_id="2111">franceinfo:</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CStar.fr" site_id="458">CStar</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="T18.fr" site_id="4139">T18</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NOVO19.fr" site_id="4138">NOVO19</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TF1SeriesFilms.fr" site_id="1404">TF1 Séries-Films</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LEquipe.fr" site_id="1401">L'équipe</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="6ter.fr" site_id="1403">6ter</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RMCStory.fr" site_id="1402">RMC Story</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RMCDecouverte.fr" site_id="1400">RMC Découverte</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Cherie25.fr" site_id="1399">Chérie 25</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="i24NEWSFrench.il" site_id="781">i24 News</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3946">AFTER FOOT TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMBusiness.fr" site_id="1073">BFM Business</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TechCo.fr" site_id="2942">TECH&amp;CO</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RMCSport1.fr" site_id="2665">RMC Sport 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RMCSport2.fr" site_id="2666">RMC Sport Live 2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMMarseille.fr" site_id="3289">BFM MARSEILLE PROVENCE</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMLyon.fr" site_id="116">BFM Lyon</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3947">RMC Talk Info</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DiscoveryChannel.fr" site_id="400">Discovery Channel</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TLC.fr" site_id="1374">TLC</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DiscoveryChannel.fr" site_id="2184">Discovery Investigation</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3948">BFM Grands Reportages</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France24.fr@French" site_id="529">France 24</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="EuronewsFrench.fr" site_id="140">Euronews Fra</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3949">RMC Alerte Secours</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="13emeRue.fr" site_id="2">13ème rue</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Syfy.fr" site_id="479">Syfy</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="E.fr" site_id="405">E! Entertainment</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="WarnerTV.fr" site_id="2334">WARNER TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MTV.fr" site_id="128">MTV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MCM.fr" site_id="121">MCM</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AB1.fr" site_id="5">AB1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SerieClub.fr" site_id="49">SERIE CLUB</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="GameOne.fr" site_id="87">Game One</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="GameOne.fr@Plus1" site_id="1167">Game One+1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="WarnerTVNext.fr" site_id="2040">Warner TV Next</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="JOne.fr" site_id="1585">J-One</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BET.fr" site_id="1960">BET</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ComedyCentral.fr" site_id="2752">Comedy Central</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="1765">Netflix</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="2941">Prime Vidéo</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="2974">Disney+</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ParisPremiere.fr" site_id="145">Paris Première</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Teva.fr" site_id="191">Téva</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RTL9.lu" site_id="115">RTL9</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVBreizh.fr" site_id="225">TV Breizh</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TV5MondeFranceBelgiumSwitzerlandMonaco.fr" site_id="205">TV5 Monde</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TF1.fr@4K" site_id="3425">TF1 4K</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France2.fr@4K" site_id="4012">France 2 UHD</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CanalPlusSport360.fr" site_id="3504">CANAL+ SPORT 360</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CanalPlusFoot.fr" site_id="3501">CANAL+ FOOT</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CanalPlusBoxOffice.fr" site_id="3779">CANAL+ BOX OFFICE</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CanalPlusGrandEcran.fr" site_id="3349">CANAL+ GRAND ECRAN</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CanalPlusDocs.fr" site_id="3347">CANAL+ DOCS</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CanalPlusKids.fr" site_id="3348">CANAL+ KIDS</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFM2.fr" site_id="4092">BFM2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="PublicSenat2424.fr" site_id="992">LCP-AN 24/24</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="PublicSenat2424.fr" site_id="2013">Public Sénat 24/24</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LaChaineMeteo.fr" site_id="124">LA CHAINE METEO</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RMCSportAccess1.fr" site_id="2095">RMC Sport Access</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3953">RMC Mecanic</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINSports1.qa@France" site_id="1290">beIN SPORTS 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINSports2.qa@France" site_id="1304">beIN SPORTS 2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINSports3.qa@France" site_id="1335">beIN SPORTS 3</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DAZN1.uk" site_id="3795">DAZN 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Equidia.fr" site_id="64">Equidia</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MGGTV.fr" site_id="2353">MGG TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Automotolachaine.fr" site_id="15">Auto Moto</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="JournalDuGolf.fr" site_id="3629">Journal du Golf TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SportenFrance.fr" site_id="2837">Sport en France</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="OLTV.fr" site_id="463">OLPLAY</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Eurosport1.fr" site_id="76">EUROSPORT 1 HD</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Eurosport2.fr" site_id="439">EUROSPORT 2 HD</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CinePlusOCS.fr" site_id="282">OCS</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CinePlusFrisson.fr" site_id="284">CINE+ frisson</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CinePlusEmotion.fr" site_id="283">CINE+ émotion</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CinePlusFamily.fr" site_id="401">CINE+ family</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CinePlusFestival.fr" site_id="285">CINE+ festival</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CinePlusClassic.fr" site_id="287">CINE+ classic</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DisneyChannel.fr" site_id="58">DISNEY CHANNEL</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DisneyChannel.fr@Plus1" site_id="299">DISNEY CHANNEL +1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ParamountChannel.fr" site_id="1562">Paramount Network</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ParamountChannelOffset.fr" site_id="2072">Paramount Network Décalé</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TCMCinema.fr" site_id="185">TCM Cinéma</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Action.fr" site_id="10">Action</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="InsomniacTV.us" site_id="4101">INSOMNIA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3950">RMC WOW</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3952">RMC Mystère</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3951">J'irai dormir chez vous</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="UshuaiaTV.fr" site_id="451">Ushuaia TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Trek.fr" site_id="1776">TREK</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CrimeDistrict.fr" site_id="2037">Crime District</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MarmitonTV.fr" site_id="3155">Marmiton TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="HistoireTV.fr" site_id="88">Histoire TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ToutelHistoire.fr" site_id="7">Toute l'histoire</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="KTO.fr" site_id="110">KTO</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Animaux.fr" site_id="12">Animaux</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ChassePeche.fr" site_id="38">Chasse et Pêche</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ScienceVieTV.fr" site_id="63">Science et Vie TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LuxeTV.lu" site_id="531">Luxe TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="FashionTVEurope.fr" site_id="357">Fashion TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MensUPTV.fr" site_id="1452">Men's Up TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AstrocenterTV.fr" site_id="394">Astrocenter.tv</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MyZenTV.fr" site_id="829">My Zen TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MuseumTVFrench.fr" site_id="1072">MUSEUM TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MuseumTVFrench.fr@4K" site_id="3177">Museum TV 4K</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Exploreorg.us" site_id="3782">EXPLORE</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LeFigaroLive.fr" site_id="3768">Le Figaro TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Seasons.fr" site_id="173">SEASONS</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3762">KITCHEN MANIA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TopSanteTV.fr" site_id="3106">Top Santé TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MaisonTravauxTV.fr" site_id="3360">Maison &amp; Travaux TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AutoPlus.ru" site_id="3287">Auto Plus TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NickelodeonJunior.fr" site_id="888">Nickelodeon Junior</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Boomerang.fr" site_id="321">Boomerang</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Boomerang.fr@Plus1" site_id="928">Boomerang+1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TiJi.fr" site_id="229">Tiji</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DreamWorks.fr" site_id="3597">DREAMWORKS</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Nickelodeon.fr" site_id="473">Nickelodeon</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Nickelodeon.fr@Plus1" site_id="2065">Nickelodeon+1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CanalJ.fr" site_id="32">Canal J</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CartoonNetworkWesternEurope.uk" site_id="36">Cartoon Network</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NickelodeonTeen.fr" site_id="1746">Nickelodeon Teen</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CartoonitoWesternEurope.uk" site_id="3738">Cartoonito</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TraceVanillaIslands.fr" site_id="2977">Trace Vanilla</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Mangas.fr" site_id="6">Mangas</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LuckyJacktv.lu" site_id="1061">Lucky Jack</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MTVHits.fr" site_id="2006">MTV Hits</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="M6Music.fr" site_id="453">M6 Music</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RFMTV.fr" site_id="241">RFM TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NRJHits.fr" site_id="605">NRJ Hits</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TraceLatina.fr" site_id="2744">Trace Latina</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Mezzo.fr" site_id="125">Mezzo</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MezzoLive.fr" site_id="907">Mezzo Live</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Melody.fr" site_id="265">Melody TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TraceUrban.fr" site_id="325">Trace Urban</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TraceToca.fr" site_id="1948">Trace Toca</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TraceCaribbean.fr" site_id="753">TRACE CARIBBEAN</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TraceGospel.fr" site_id="2022">Trace Gospel</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MelodydAfrique.fr" site_id="2321">Melody d'Afrique</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMGrandLille.fr" site_id="930">BFM Grand Lille</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMGrandLittoral.fr" site_id="2317">BFM Grand Littoral</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMCotedAzur.fr" site_id="3291">BFM NICE COTE D'AZUR</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMVar.fr" site_id="3290">BFM TOULON VAR</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMDICIAlpesduSud.fr" site_id="3165">BFM DICI ALPES DU SUD</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMDICIHauteProvence.fr" site_id="3164">BFM DICI HAUTE-PROVENCE</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMAlsace.fr" site_id="3460">BFM ALSACE</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BFMNormandie.fr" site_id="3557">BFM Normandie</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ViaOccitaniePaysGardois.fr" site_id="538">vià30</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ViaOccitanieToulouse.fr" site_id="2302">vià31</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ViaOccitanieMontpellier.fr" site_id="704">vià34</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ViaOccitaniePaysCatalan.fr" site_id="2303">vià66</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINSportsMax4.qa@France" site_id="1336">beIN SPORTS MAX 4</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINSportsMax5.qa@France" site_id="1337">beIN SPORTS MAX 5</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINSportsMax6.qa@France" site_id="1338">beIN SPORTS MAX 6</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINSportsMax7.qa@France" site_id="1339">beIN SPORTS MAX 7</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINSportsMax8.qa@France" site_id="1340">beIN SPORTS MAX 8</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINSportsMax9.qa@France" site_id="1341">beIN SPORTS MAX 9</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINSportsMax10.qa@France" site_id="1342">beIN SPORTS MAX 10</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="GolfporMovistarPlusPlus.es" site_id="1295">GOLF+ HD</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="4068">CANAL+LIVE 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="4069">CANAL+LIVE 2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="4070">CANAL+LIVE 3</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="4071">CANAL+LIVE 4</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="4072">CANAL+LIVE 5</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="4073">CANAL+LIVE 6</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="4074">CANAL+LIVE 7</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RMCSport3.fr" site_id="3171">RMC Sport Live 3</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RMCSport4.fr" site_id="3172">RMC Sport Live 4</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="XXL.fr" site_id="218">XXL</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DorcelTV.nl" site_id="560">Dorcel TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DorcelXXX.nl" site_id="1474">Dorcel XXX</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="PrivateTV.nl" site_id="558">Private TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="HustlerTVEurope.nl" site_id="416">Hustler TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="VixenHD.us" site_id="3169">VIXEN</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="PinkTV.fr" site_id="406">Pink TV / Pink X</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ManX.be" site_id="683">Man-X</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="UnionTV.py" site_id="2376">Union TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DorcelTVAfrica.nl" site_id="2838">DORCEL TV AFRICA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MentTV.be" site_id="3994">MEN TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="PenthouseHD1.us" site_id="953">PENTHOUSE HD</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DasErste.de" site_id="13">Das Erste</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Sport1.hu" site_id="60">SPORT 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Alpes" site_id="1921">France 3 Alpes</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Alsace" site_id="1922">France 3 Alsace</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Aquitaine" site_id="1923">France 3 Aquitaine</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Auvergne" site_id="1924">France 3 Auvergne</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@NormandieCaen" site_id="1925">France 3 Basse-Normandie</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Bourgogne" site_id="1926">France 3 Bourgogne</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Bretagne" site_id="1927">France 3 Bretagne</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Centre" site_id="1928">France 3 Centre</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@ChampagneArdenne" site_id="1929">France 3 Champagne-Ardenne</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@CorseViaStella" site_id="308">France 3 via Stella</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@CoteDAzur" site_id="1931">France 3 Côte d'Azur</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@FrancheComte" site_id="1932">France 3 Franche-Comté</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@NormandieRouen" site_id="1933">France 3 Haute-Normandie</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Languedoc" site_id="1934">France 3 Languedoc</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Limousin" site_id="1935">France 3 Limousin</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Lorraine" site_id="1936">France 3 Lorraine</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@MidiPyrenees" site_id="1937">France 3 Midi-Pyrénées</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@NordPCalais" site_id="1938">France 3 Nord-Pas-de-Calais</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@ParisIDF" site_id="1939">France 3 Paris IDF</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@PaysDeLaLoire" site_id="1940">France 3 Pays de la Loire</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Picardie" site_id="1941">France 3 Picardie</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@PoitouCharentes" site_id="1942">France 3 Poitou-Charentes</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@ProvenceAlpes" site_id="1943">France 3 Provence-Alpes</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@RhoneAlpes" site_id="1944">France 3 Rhône-Alpes</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@NoA" site_id="2730">France 3 Nouvelle Aquitaine</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France3.fr@Corse" site_id="1930">France 3 - Corse</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="20MinutesTV.fr" site_id="701">20 Minutes TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TeleBocal.fr" site_id="1111">Télé Bocal</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Via93.fr" site_id="1738">vià93</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LeFigaroTVIledeFrance.fr" site_id="863">Figaro TV IDF</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TV78.fr" site_id="1023">TV78</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LyonCapitaleTV.fr" site_id="1197">Lyon Capitale TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TeleGrenoble.fr" site_id="537">Télé Grenoble</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TL7.fr" site_id="1151">TL7 Saint Etienne</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TV8MontBlanc.fr" site_id="421">8 Mont-Blanc</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ILTV.fr" site_id="1724">ILTV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ASTV.fr" site_id="1721">ASTV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="WeoPicardie.fr" site_id="3778">Wéo Picardie</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Weo.fr" site_id="809">Wéo TV, La voix du nord</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3797">CRESPIN TELEVISION</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Matele.be" site_id="393">Vià MATÉLÉ</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="7ALimoges.fr" site_id="1719">7A Limoges</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TV7Bordeaux.fr" site_id="273">TV7 Bordeaux</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVPI.fr" site_id="2275">TVPI (TV Biarritz)</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ETB1.es" site_id="71">ETB1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ETB2.es" site_id="72">ETB2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ETB3.es" site_id="1723">ETB3</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Kanaldude.fr" site_id="3563">KANALDUDE</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CanalLocalGrosblie.fr" site_id="2369">Grosbliederstroff</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TV2COM.fr" site_id="2373">TV2COM</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="2372">TRESSANGE TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NATV.fr" site_id="3760">NA TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Canal32.fr" site_id="534">Canal 32</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MoselleTV.fr" site_id="1045">vià Mirabelle</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="PuissanceTV.fr" site_id="3771">Puissance TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CanalSchilick.fr" site_id="1735">Télé Schiltigheim</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVMonaco.mc" site_id="3786">TVMonaco</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MosaikCristal.fr" site_id="1700">Mosaik Cristal</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TV8MoselleEst.fr" site_id="1638">TV8 Moselle-Est</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="VosgesTV.fr" site_id="1095">vià Vosges</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CannesLerinsTV.fr" site_id="3770">Cannes Lérins TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MaritimaTV.fr" site_id="1185">Maritima TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MaurienneTV.fr" site_id="3137">MAURIENNE TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AngersTele.fr" site_id="1615">Angers Télé</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Telenantes.fr" site_id="491">Télé Nantes</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVVendee.fr" site_id="1268">TV Vendée</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LMTVSarthe.fr" site_id="535">LMtv Sarthe</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TebeoTV.fr" site_id="1321">Tébéo</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVR.fr" site_id="539">TV Rennes 35</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVTours.fr" site_id="540">Val de Loire TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ZoukTV.mq" site_id="1199">Zouk TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="viaTelePaese.fr" site_id="1750">Télé Paese</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CNNInternational.us@MENA" site_id="53">CNN International</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BBCNews.uk@Europe" site_id="19">BBC NEWS</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France24.fr@English" site_id="671">France 24 ENG</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CNBCEurope.uk" site_id="51">CNBC Europe</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BloombergTV.us@Europe" site_id="410">Bloomberg</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlJazeera.qa@English" site_id="525">Al Jazeera English</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="i24NEWSFrench.il@English" site_id="1717">i24 News Anglais</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NHKWorldJapan.jp" site_id="830">NHK WORLD-JAPAN</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SkyNews.uk" site_id="368">Sky News</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TGCom24.it" site_id="2274">TGCOM24</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="i24NEWSFrench.il@Arabe" site_id="1718">i24 News Arabe</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France24.fr@Arabe" site_id="814">France 24 ARA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlJazeera.qa" site_id="345">Al Jazeera</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Medi1TVAfrique.ma" site_id="665">Medi 1 TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Alarabiya.ae" site_id="1540">Al Arabiya</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="EchoroukTV.dz" site_id="1550">Echorouk TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="EnnaharTV.dz" site_id="1551">Ennahar TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SICNoticias.pt" site_id="1555">SIC Noticias</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Canal11.pt" site_id="3761">Canal 11</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RaiNews24.it" site_id="1129">Rai News 24</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="France24.fr@Espagnol" site_id="3562">France 24 Espanol</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="24Horas.es" site_id="713">24 Horas</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVEInternacionalEuropeAsia.es" site_id="208">TVE</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DW.de" site_id="61">DW-TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="WELT.de" site_id="1726">WELT</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVN24.pl" site_id="871">TVN24</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RecordNews.br" site_id="793">Record News</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CGTN.cn" site_id="318">CGTN</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Africa24.fr" site_id="561">Africa 24</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Canal2International.cm" site_id="609">Canal 2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVIFiccao.pt" site_id="4100">V+</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="PortoCanal.pt" site_id="2050">Porto Canal</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LocalVisaoTV.pt" site_id="2347">Local Visao</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BenficaTV.pt" site_id="1623">Benfica TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ABolaTV.pt" site_id="2345">A BOLA TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RecordTVEuropa.pt" site_id="805">TV Record</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RTP3.pt" site_id="2313">RTP 3</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVIInternacional.pt" site_id="1637">TVI Internacional</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SICInternacional.pt" site_id="807">SIC Internacional</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CanalQ.pt" site_id="2046">Canal Q</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RTPInternacional.pt" site_id="169">RTPI</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Rai1.it" site_id="156">Rai Uno</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Rai2.it" site_id="154">Rai Due</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Rai3.it" site_id="155">Rai Tre</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RaiScuola.it" site_id="789">RAI SCUOLA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RaiStoria.it" site_id="790">RAI STORIA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MediasetItalia.it" site_id="1288">Mediaset Italia</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RealMadridTV.es" site_id="3600">REAL MADRID TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVEInternacionalEuropeAsia.es" site_id="2292">STAR TVE</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Antena3.es" site_id="1702">Antena 3</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Atreseries.es" site_id="1761">Atres Series</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AllFlamenco.es" site_id="3075">ALL FLAMENCO</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="GaliciaTVEuropa.es" site_id="1704">TVG EUROPA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TV3CAT.es" site_id="203">TV3 Catalunya</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ETBBasque.es" site_id="592">etb basque</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Telemadrid.es" site_id="3572">TELE MADRID</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AndaluciaTelevision.es" site_id="2530">ANDALUCIA TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Boomerang.us@English" site_id="2168">Boomerang Anglais</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="FilmBoxArthouse.nl" site_id="2446">FilmBox Arthouse</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TCMCinema.fr@English" site_id="2169">TCM Anglais</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3993">TinyTeen</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="4003">Lang Lab</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="4004">Lingo Toons</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DocuBox.nl" site_id="2444">DocuBox HD</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="FashionBox.nl" site_id="2445">FashionBox HD</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ProSieben.de" site_id="964">Pro Sieben</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ntv.de" site_id="1031">N-TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RTL.de" site_id="166">RTL Television</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RTLZwei.de" site_id="966">RTL2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SAT1.de" site_id="366">Sat 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RTLSuper.de" site_id="1854">Super RTL</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SWRFernsehenHD.de" site_id="182">SWR</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="VOX.de" site_id="971">Vox</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ZDF.de" site_id="219">ZDF</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="kabeleins.de" site_id="981">KABEL EINS</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="KiKA.de" site_id="982">KIKA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Nitro.de" site_id="1729">RTL NITRO TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="arte.fr@Deutsh" site_id="1189">Arte Allemande</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="3sat.de" site_id="960">3 SAT</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="1760">VIVA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ITVNEurope.pl" site_id="1010">iTVN</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ITVNExtra.pl" site_id="1975">iTVN Extra</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVPPolonia.pl" site_id="875">TVP Polonia</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Armenia1.am" site_id="724">Armenia 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="1026">Antenna 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ARTCinema.sa" site_id="625">ART CINEMA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ARTAflam1.sa" site_id="3541">ART AFLAM 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ARTAflam2.sa" site_id="579">ART AFLAM 2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ARTHekayat.sa" site_id="3542">AL HEKAYAT 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ARTHekayat2.sa" site_id="3543">AL HEKAYAT 2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="InfoTVRomania.ro" site_id="619">TV Romania International</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BahiaTV.dz" site_id="3727">Bahia</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TeleMaroc.ma" site_id="3380">Télé Maroc</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SamiraTV.dz" site_id="2299">Samira TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TV2.dz" site_id="346">Canal Algérie</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TV3.dz" site_id="1654">A3</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BeurTV.fr" site_id="608">Beur TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="2MMonde.ma" site_id="340">2M Maroc</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlAoula.ma" site_id="769">Al Aoula</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Arryadia.ma" site_id="956">Arryadia</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Assadissa.ma" site_id="959">Assadissa</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ElhiwarEttounsiTV.tn" site_id="2064">El Hiwar Ettounsi</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ElWatania2.tn" site_id="3728">Watania 2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TT1.tn" site_id="371">Tunisia 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AsharqNews.sa" site_id="3726">Asharq News</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BerbereJeunesse.fr" site_id="1113">Berbère Jeunesse</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BerbereMusic.fr" site_id="1112">Berbère Musique</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BerbereTV.fr" site_id="574">Berbère TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SkyNewsArabia.ae" site_id="3725">Sky News Arabia</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ChadaTV.ma" site_id="3774">CHADA TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RotanaComedy.sa" site_id="3902">Rotana Comedy</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SyriaTV.sy" site_id="4141">Syria TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlResalah.sa" site_id="1022">Al Resalah</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlArabyTV.qa" site_id="4093">Alaraby 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="EchoroukNews.dz" site_id="2832">Echorouk News</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ElBiladTV.dz" site_id="2957">El Bilad TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DiziSmartMax.tr" site_id="3736">Dizi</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlArabyTV2.qa" site_id="3020">Alaraby 2</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RotanaAflamPlus.sa" site_id="3540">Rotana Aflam+</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="3539">Rotana Cinéma+ FR</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RotanaMusic.sa" site_id="1019">Rotana Music</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RotanaDrama.sa" site_id="1763">Rotana Drama</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RotanaKids.sa" site_id="3548">Rotana Kids</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RotanaCinemaKSA.sa" site_id="795">Rotana Cinema</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RotanaClassic.sa" site_id="2878">Rotana Classic</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NessmaElJadida.tn" site_id="1096">Nessma</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DMC.eg" site_id="3537">DMC</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="FixFoxi.de" site_id="3381">Fix et Foxy</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CarthagePlus.tn" site_id="3376">Carthage+</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DMCDrama.eg" site_id="3544">DMC Drama</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="IqraaArabic.sa" site_id="3550">Iqraa TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="IqraaAfricaEurope.sa" site_id="3549">Iqraa International</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlMajdHolyQuran.sa" site_id="1698">Al Majd Holy Quran</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlMaghribia.ma" site_id="957">Al Maghribia</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Athaqafia.ma" site_id="958">Arrabiâ</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlJadeed.lb" site_id="1283">Al Jadeed</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="2218">LBC Sat</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LanaTV.lb" site_id="3800">Lana TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NBN.lb" site_id="1275">NBN</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="OTV.lb" site_id="1285">OTV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="945">Murr TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MPlus.sa" site_id="3810">Rotana M+</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DubaiTV.ae" site_id="589">Dubaï TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="OneNationTV.sn" site_id="3538">ON TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="HannibalTV.tn" site_id="3551">HANNIBAL TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="1614">Panorama Drama</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="1613">Panorama Film</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlMasriyah.eg" site_id="356">Al Masriya</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TheIsraeliNetwork.il" site_id="864">The Israeli Network</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="JordanTV.jo" site_id="603">Jordan Satellite Channel</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="EuroStar.tr" site_id="594">Euro Star</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="EuroD.tr" site_id="593">Euro D</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="HaberturkTV.tr" site_id="624">Habertürk</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="beINMoviesTurk.tr" site_id="2756">BEIN MOVIES</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ShowMax.tr" site_id="2778">SHOW MAX</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ShowTurk.tr" site_id="854">Show Turk</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ATVAvrupa.tr" site_id="2804">ATV Avrupa</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Kanal7Avrupa.tr" site_id="2777">KANAL 7 AVRUPA</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TV8.tr" site_id="1949">TV8 International</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="AlSaudiya.sa" site_id="1733">Saudi Channel 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="APlus.fr" site_id="1990">A+</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ORTBTV.bj" site_id="1003">ORTB</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NovelasTV.fr" site_id="3769">NOVELAS</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CRTV.cm" site_id="571">CRTV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="EquinoxeTV.cm" site_id="984">Equinoxe TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CheriflaTV.ml" site_id="2784">CHERIFLA TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ORTCTV.km" site_id="1042">ORTC</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TeleCongo.cg" site_id="858">TV Congo</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="MabokeTV.cg" site_id="3372">Maboke TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RTNC.cd" site_id="1097">RTNC</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NCI.ci" site_id="2913">NCI</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RTI1.ci" site_id="846">RTI 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CDirect.fr" site_id="3273">CDIRECT</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Gabon1ere.ga" site_id="985">Gabon 1ère</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RTG.mx" site_id="1071">RTG</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TVMInternacional.mz" site_id="1041">TVM</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ORTM1.ml" site_id="697">ORTM</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NollywoodTVEpic.fr" site_id="2343">Nollywood TV Epic</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NollywoodTV.fr" site_id="1461">Nollywood TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="PULAAGU.sn" site_id="3970">Pulaagu</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TraceAfrica.fr" site_id="1179">Trace Africa</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="VoxAfrica.uk" site_id="1133">Vox Africa</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="2STV.sn" site_id="573">2STV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RTS.ec" site_id="847">RTS 1</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SenTV.sn" site_id="1640">SEN TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TFM.sn" site_id="1154">TFM</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SunuYeuf.sn" site_id="2908">SUNU YEUF</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="BeijingSatelliteTV.cn" site_id="771">Beijing TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="773">CCTV YULE</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CCTV4Europe.cn" site_id="772">CCTV-4</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ChinaMovieChannel.cn" site_id="1138">China Movie Channel (CMC)</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="HunanTV.cn" site_id="782">Hunan TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="" site_id="1196">JSBC International</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="PhoenixCNEChannel.hk" site_id="1128">Phoenix CNE</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="PhoenixInfoNewsChannel.hk" site_id="369">Phoenix Infonews</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DragonTV.cn" site_id="586">Shangaï Dragon TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="GreatWallElite.cn" site_id="880">Great Wall Elite</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ZTVWorld.cn" site_id="882">ZTV World (Zhejiang Star TV)</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="GRT.md" site_id="876">GRT GBA Satellite TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="CGTNFrench.cn" site_id="774">CGTN-Français</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NTDTVEurope.us" site_id="1115">NTD</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="KBSWorld.kr" site_id="944">KBS World</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="Colors.in" site_id="1180">Colors</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="UtsavBharat.uk" site_id="1974">Utsav Bharat</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ColorsRishteyEurope.in" site_id="1973">Rishtey</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="UtsavPlus.uk" site_id="1145">Utsav Plus</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="ZeeTV.in" site_id="526">Zee TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="NHKWorldPremium.jp" site_id="3799">NHK World Premium</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="B4UMovies.in" site_id="952">B4U Movies</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="GeoNews.pk" site_id="951">Geo News</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="GEOTelevision.de" site_id="950">Geo TV</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="SonyMax.uk" site_id="1538">Sony Max</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="LasEstrellas.mx" site_id="588">Las Estrellas</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DistritoComedia.mx" site_id="583">Distrito Comedia</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="DePeliculaEuropa.mx" site_id="585">De pelicula</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="RMS.mx" site_id="843">RMS</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TeleHit.mx" site_id="859">Telehit</channel>
<channel site="tv.sfr.fr" lang="fr" xmltv_id="TlnovelasEuropa.mx" site_id="860">tlnovelas</channel>
</channels>

View File

@@ -1,65 +1,65 @@
const axios = require('axios')
const dayjs = require('dayjs')
module.exports = {
site: 'tv.sfr.fr',
days: 2,
url({ date }) {
return `https://static-cdn.tv.sfr.net/data/epg/gen8/guide_web_${date.format('YYYYMMDD')}.json`
},
request: {
maxContentLength: 20 * 1024 * 1024, // 20Mb
cache: {
ttl: 24 * 60 * 60 * 1000 // 1 day
}
},
parser({ content, channel }) {
let programs = []
let items = parseItems(content, channel)
items.forEach(item => {
programs.push({
start: dayjs(item.startDate),
stop: dayjs(item.endDate),
title: item.title,
subTitle: item.subTitle || null,
category: item.genre,
description: item.longSynopsis,
images: item.images.map(img => img.url),
season: item.seasonNumber || null,
episode: item.episodeNumber || null
})
})
return programs
},
async channels() {
const data = await axios
.get('https://api.sfr.fr/service-channel/api/rest/v2/channels')
.then(r => r.data)
.catch(console.error)
let channels = {}
Object.values(data.data.chaines).forEach(channel => {
if (!channels[channel.epg_id]) {
channels[channel.epg_id] = {
lang: 'fr',
site_id: channel.epg_id,
name: channel.nom_chaine
}
}
})
return Object.values(channels)
}
}
function parseItems(content, channel) {
try {
const data = JSON.parse(content)
if (!data || !data.epg || !Array.isArray(data.epg[channel.site_id])) return []
return data.epg[channel.site_id]
} catch {
return []
}
}
const axios = require('axios')
const dayjs = require('dayjs')
module.exports = {
site: 'tv.sfr.fr',
days: 2,
url({ date }) {
return `https://static-cdn.tv.sfr.net/data/epg/gen8/guide_web_${date.format('YYYYMMDD')}.json`
},
request: {
maxContentLength: 20 * 1024 * 1024, // 20Mb
cache: {
ttl: 24 * 60 * 60 * 1000 // 1 day
}
},
parser({ content, channel }) {
let programs = []
let items = parseItems(content, channel)
items.forEach(item => {
programs.push({
start: dayjs(item.startDate),
stop: dayjs(item.endDate),
title: item.title,
subTitle: item.subTitle || null,
category: item.genre,
description: item.longSynopsis,
images: item.images.map(img => img.url),
season: item.seasonNumber || null,
episode: item.episodeNumber || null
})
})
return programs
},
async channels() {
const data = await axios
.get('https://api.sfr.fr/service-channel/api/rest/v2/channels')
.then(r => r.data)
.catch(console.error)
let channels = {}
Object.values(data.data.chaines).forEach(channel => {
if (!channels[channel.epg_id]) {
channels[channel.epg_id] = {
lang: 'fr',
site_id: channel.epg_id,
name: channel.nom_chaine
}
}
})
return Object.values(channels)
}
}
function parseItems(content, channel) {
try {
const data = JSON.parse(content)
if (!data || !data.epg || !Array.isArray(data.epg[channel.site_id])) return []
return data.epg[channel.site_id]
} catch {
return []
}
}

View File

@@ -1,71 +1,71 @@
const { parser, url } = require('./tv.sfr.fr.config.js')
const fs = require('fs')
const path = require('path')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat')
dayjs.extend(customParseFormat)
dayjs.extend(utc)
const date = dayjs.utc('2025-01-18', 'YYYY-MM-DD').startOf('d')
const channel = {
site_id: '192',
xmltv_id: 'TF1.fr'
}
it('can generate valid url', () => {
expect(url({ date, channel })).toBe(
'https://static-cdn.tv.sfr.net/data/epg/gen8/guide_web_20250118.json'
)
})
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8')
let results = parser({ content, channel })
results = results.map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
expect(results.length).toBe(23)
expect(results[0]).toMatchObject({
start: '2025-01-18T02:05:00.000Z',
stop: '2025-01-18T05:00:00.000Z',
title: 'Programmes de la nuit',
subTitle: null,
category: 'Programme indéterminé',
description: 'Retrouvez tous vos programmes de nuit.',
images: [
'http://static-cdn.tv.sfr.net/data/img/pl/3/6/9/5757963.jpg',
'http://static-cdn.tv.sfr.net/data/img/pl/5/0/8/7616805.jpg'
],
season: null,
episode: null
})
expect(results[22]).toMatchObject({
start: '2025-01-18T22:40:00.000Z',
stop: '2025-01-19T00:00:00.000Z',
title: 'Star Academy',
subTitle: 'Retour au château',
category: 'Téléréalité',
description:
"C'est en direct du plateau que Nikos Aliagas revient sur les prestations des différents académiciens en compagnie du corps professoral. L'occasion de revenir en détails sur le déroulement du prime avec les aspects positifs mais également les éléments sur lesquels les élèves doivent progresser pour espérer faire la différence sur cette fin d'aventure.",
images: [
'http://static-cdn.tv.sfr.net/data/img/pl/1/0/0/9517001.jpg',
'http://static-cdn.tv.sfr.net/data/img/pl/6/7/2/9992276.jpg',
'http://static-cdn.tv.sfr.net/data/img/pl/9/0/1/9985109.jpg',
'http://static-cdn.tv.sfr.net/data/img/pl/6/7/5/9491576.jpg'
],
season: 12,
episode: 15
})
})
it('can handle empty guide', () => {
const results = parser({
content: ''
})
expect(results).toMatchObject([])
})
const { parser, url } = require('./tv.sfr.fr.config.js')
const fs = require('fs')
const path = require('path')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat')
dayjs.extend(customParseFormat)
dayjs.extend(utc)
const date = dayjs.utc('2025-01-18', 'YYYY-MM-DD').startOf('d')
const channel = {
site_id: '192',
xmltv_id: 'TF1.fr'
}
it('can generate valid url', () => {
expect(url({ date, channel })).toBe(
'https://static-cdn.tv.sfr.net/data/epg/gen8/guide_web_20250118.json'
)
})
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8')
let results = parser({ content, channel })
results = results.map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
expect(results.length).toBe(23)
expect(results[0]).toMatchObject({
start: '2025-01-18T02:05:00.000Z',
stop: '2025-01-18T05:00:00.000Z',
title: 'Programmes de la nuit',
subTitle: null,
category: 'Programme indéterminé',
description: 'Retrouvez tous vos programmes de nuit.',
images: [
'http://static-cdn.tv.sfr.net/data/img/pl/3/6/9/5757963.jpg',
'http://static-cdn.tv.sfr.net/data/img/pl/5/0/8/7616805.jpg'
],
season: null,
episode: null
})
expect(results[22]).toMatchObject({
start: '2025-01-18T22:40:00.000Z',
stop: '2025-01-19T00:00:00.000Z',
title: 'Star Academy',
subTitle: 'Retour au château',
category: 'Téléréalité',
description:
"C'est en direct du plateau que Nikos Aliagas revient sur les prestations des différents académiciens en compagnie du corps professoral. L'occasion de revenir en détails sur le déroulement du prime avec les aspects positifs mais également les éléments sur lesquels les élèves doivent progresser pour espérer faire la différence sur cette fin d'aventure.",
images: [
'http://static-cdn.tv.sfr.net/data/img/pl/1/0/0/9517001.jpg',
'http://static-cdn.tv.sfr.net/data/img/pl/6/7/2/9992276.jpg',
'http://static-cdn.tv.sfr.net/data/img/pl/9/0/1/9985109.jpg',
'http://static-cdn.tv.sfr.net/data/img/pl/6/7/5/9491576.jpg'
],
season: 12,
episode: 15
})
})
it('can handle empty guide', () => {
const results = parser({
content: ''
})
expect(results).toMatchObject([])
})

View File

@@ -3,7 +3,7 @@ const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const uniqBy = require('../../scripts/functions')
const uniqBy = require('lodash.uniqby')
dayjs.extend(utc)
dayjs.extend(timezone)

View File

@@ -1,7 +1,7 @@
const cheerio = require('cheerio')
const axios = require('axios')
const { DateTime } = require('luxon')
const { uniqBy } = require('../../scripts/functions')
const uniqBy = require('lodash.uniqby')
module.exports = {
site: 'tvhebdo.com',

View File

@@ -3,7 +3,7 @@ const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const { uniqBy } = require('../../scripts/functions')
const uniqBy = require('lodash.uniqby')
dayjs.extend(utc)
dayjs.extend(timezone)

View File

@@ -1,6 +1,6 @@
const axios = require('axios')
const dayjs = require('dayjs')
const { uniqBy } = require('../../scripts/functions')
const uniqBy = require('lodash.uniqby')
module.exports = {
site: 'tvmusor.hu',

View File

@@ -1,60 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<channels>
<channel site="vidio.com" lang="id" xmltv_id="ABCAustralia.au" site_id="7150">ABC Australia</channel>
<channel site="vidio.com" lang="id" xmltv_id="AfricanewsEnglish.fr" site_id="12784">AFRICANEWS TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="AjwaTV.id" site_id="7464">Ajwa TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="AlJazeera.qa@English" site_id="6410">Aljazeera</channel>
<channel site="vidio.com" lang="id" xmltv_id="ANTV.id" site_id="782">ANTV</channel>
<channel site="vidio.com" lang="id" xmltv_id="ArirangWorld.kr" site_id="6784">Arirang</channel>
<channel site="vidio.com" lang="id" xmltv_id="beINSports1.qa@Indonesia" site_id="6299">Bein 1</channel>
<channel site="vidio.com" lang="id" xmltv_id="" site_id="17875">Bein 2</channel>
<channel site="vidio.com" lang="id" xmltv_id="beINSports3.qa@Indonesia" site_id="6317">Bein 3</channel>
<channel site="vidio.com" lang="id" xmltv_id="BeritaSatuEnglish.id" site_id="18280">BeritaSatu</channel>
<channel site="vidio.com" lang="id" xmltv_id="BTV.id" site_id="6165">BTV</channel>
<channel site="vidio.com" lang="id" xmltv_id="ChampionsTV1.id" site_id="6685">CTV 1</channel>
<channel site="vidio.com" lang="id" xmltv_id="ChampionsTV2.id" site_id="6686">CTV 2</channel>
<channel site="vidio.com" lang="id" xmltv_id="ChampionsTV3.id" site_id="6786">CTV 3</channel>
<channel site="vidio.com" lang="id" xmltv_id="ChampionsTV5.id" site_id="9182">CTV 5</channel>
<channel site="vidio.com" lang="id" xmltv_id="ChampionsTV6.id" site_id="9183">CTV 6</channel>
<channel site="vidio.com" lang="id" xmltv_id="ChampionsTVEPL.id" site_id="9353">Premier League TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="" site_id="18189">Champions Golf 1</channel>
<channel site="vidio.com" lang="id" xmltv_id="" site_id="18190">Champions Golf 2</channel>
<channel site="vidio.com" lang="id" xmltv_id="CNA.sg" site_id="6411">News Asia</channel>
<channel site="vidio.com" lang="id" xmltv_id="DAAITV.id" site_id="6482">DAAI TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="DaystarTV.us" site_id="18622">Daystar TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="DW.de@English" site_id="5075">DW English</channel>
<channel site="vidio.com" lang="id" xmltv_id="ElshintaTV.id" site_id="10975">Elshinta TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="EuronewsEnglish.fr" site_id="6412">Euro News</channel>
<channel site="vidio.com" lang="id" xmltv_id="" site_id="18105">GGS TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="HipHipHoree.id" site_id="7052">Hip Hip Horee!</channel>
<channel site="vidio.com" lang="id" xmltv_id="Horee.id" site_id="6397">Horee</channel>
<channel site="vidio.com" lang="id" xmltv_id="Indosiar.id" site_id="205">Indosiar</channel>
<channel site="vidio.com" lang="id" xmltv_id="JakTV.id" site_id="5415">Jaktv</channel>
<channel site="vidio.com" lang="id" xmltv_id="JPMTV.id" site_id="9714">jawaposTV</channel>
<channel site="vidio.com" lang="id" xmltv_id="JTV.id" site_id="9713">JTV</channel>
<channel site="vidio.com" lang="id" xmltv_id="KompasTV.id" site_id="874">Kompas TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="MagnaChannel.id" site_id="7230">Magna TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="MakkahTV.sa" site_id="6852">Makkah TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="NET.id" site_id="875">MDTV</channel>
<channel site="vidio.com" lang="id" xmltv_id="MetroTV.id" site_id="777">Metro TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="Moji.id" site_id="206">Moji</channel>
<channel site="vidio.com" lang="id" xmltv_id="" site_id="7619">MUSICA</channel>
<channel site="vidio.com" lang="id" xmltv_id="NBATV.us" site_id="6717">NBA TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="NHKWorldJapan.jp" site_id="7968">NHK World Japan</channel>
<channel site="vidio.com" lang="id" xmltv_id="NusantaraTV.id" site_id="7432">Nusantara TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="RajawaliTV.id" site_id="1561">RTV</channel>
<channel site="vidio.com" lang="id" xmltv_id="ROCKEntertainment.sg" site_id="8120">ROCK Entertainment</channel>
<channel site="vidio.com" lang="id" xmltv_id="ROCKExtreme.sg" site_id="8121">Rock Action</channel>
<channel site="vidio.com" lang="id" xmltv_id="SCTV.id" site_id="204">SCTV</channel>
<channel site="vidio.com" lang="id" xmltv_id="SPOTV2.id" site_id="17140">SPOTV 2</channel>
<channel site="vidio.com" lang="id" xmltv_id="SPOTV.id" site_id="17139">SPOTV</channel>
<channel site="vidio.com" lang="id" xmltv_id="TawafTV.id" site_id="12607">Tawaf TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="Trans7.id" site_id="734">Trans7</channel>
<channel site="vidio.com" lang="id" xmltv_id="TransTV.id" site_id="733">TRANS TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="TV5MondeAsia.fr" site_id="17278">TV5Monde</channel>
<channel site="vidio.com" lang="id" xmltv_id="tvNAsia.hk" site_id="6362">TVN</channel>
<channel site="vidio.com" lang="id" xmltv_id="tvOne.id" site_id="783">TVOne</channel>
<channel site="vidio.com" lang="id" xmltv_id="TVRI.id" site_id="6441">TVRI</channel>
<channel site="vidio.com" lang="id" xmltv_id="UChannel.id" site_id="6898">U-Channel TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="ZooMoo.sg" site_id="6533">Zoomoo</channel>
</channels>
<?xml version="1.0" encoding="UTF-8"?>
<channels>
<channel site="vidio.com" lang="id" xmltv_id="ABCAustralia.au" site_id="7150">ABC Australia</channel>
<channel site="vidio.com" lang="id" xmltv_id="AfricanewsEnglish.fr" site_id="12784">AFRICANEWS TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="AjwaTV.id" site_id="7464">Ajwa TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="AlJazeera.qa@English" site_id="6410">Aljazeera</channel>
<channel site="vidio.com" lang="id" xmltv_id="ANTV.id" site_id="782">ANTV</channel>
<channel site="vidio.com" lang="id" xmltv_id="ArirangWorld.kr" site_id="6784">Arirang</channel>
<channel site="vidio.com" lang="id" xmltv_id="beINSports1.qa@Indonesia" site_id="6299">Bein 1</channel>
<channel site="vidio.com" lang="id" xmltv_id="" site_id="17875">Bein 2</channel>
<channel site="vidio.com" lang="id" xmltv_id="beINSports3.qa@Indonesia" site_id="6317">Bein 3</channel>
<channel site="vidio.com" lang="id" xmltv_id="BeritaSatuEnglish.id" site_id="18280">BeritaSatu</channel>
<channel site="vidio.com" lang="id" xmltv_id="BTV.id" site_id="6165">BTV</channel>
<channel site="vidio.com" lang="id" xmltv_id="ChampionsTV1.id" site_id="6685">CTV 1</channel>
<channel site="vidio.com" lang="id" xmltv_id="ChampionsTV2.id" site_id="6686">CTV 2</channel>
<channel site="vidio.com" lang="id" xmltv_id="ChampionsTV3.id" site_id="6786">CTV 3</channel>
<channel site="vidio.com" lang="id" xmltv_id="ChampionsTV5.id" site_id="9182">CTV 5</channel>
<channel site="vidio.com" lang="id" xmltv_id="ChampionsTV6.id" site_id="9183">CTV 6</channel>
<channel site="vidio.com" lang="id" xmltv_id="ChampionsTVEPL.id" site_id="9353">Premier League TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="" site_id="18189">Champions Golf 1</channel>
<channel site="vidio.com" lang="id" xmltv_id="" site_id="18190">Champions Golf 2</channel>
<channel site="vidio.com" lang="id" xmltv_id="CNA.sg" site_id="6411">News Asia</channel>
<channel site="vidio.com" lang="id" xmltv_id="DAAITV.id" site_id="6482">DAAI TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="DaystarTV.us" site_id="18622">Daystar TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="DW.de@English" site_id="5075">DW English</channel>
<channel site="vidio.com" lang="id" xmltv_id="ElshintaTV.id" site_id="10975">Elshinta TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="EuronewsEnglish.fr" site_id="6412">Euro News</channel>
<channel site="vidio.com" lang="id" xmltv_id="" site_id="18105">GGS TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="HipHipHoree.id" site_id="7052">Hip Hip Horee!</channel>
<channel site="vidio.com" lang="id" xmltv_id="Horee.id" site_id="6397">Horee</channel>
<channel site="vidio.com" lang="id" xmltv_id="Indosiar.id" site_id="205">Indosiar</channel>
<channel site="vidio.com" lang="id" xmltv_id="JakTV.id" site_id="5415">Jaktv</channel>
<channel site="vidio.com" lang="id" xmltv_id="JPMTV.id" site_id="9714">jawaposTV</channel>
<channel site="vidio.com" lang="id" xmltv_id="JTV.id" site_id="9713">JTV</channel>
<channel site="vidio.com" lang="id" xmltv_id="KompasTV.id" site_id="874">Kompas TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="MagnaChannel.id" site_id="7230">Magna TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="MakkahTV.sa" site_id="6852">Makkah TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="NET.id" site_id="875">MDTV</channel>
<channel site="vidio.com" lang="id" xmltv_id="MetroTV.id" site_id="777">Metro TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="Moji.id" site_id="206">Moji</channel>
<channel site="vidio.com" lang="id" xmltv_id="" site_id="7619">MUSICA</channel>
<channel site="vidio.com" lang="id" xmltv_id="NBATV.us" site_id="6717">NBA TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="NHKWorldJapan.jp" site_id="7968">NHK World Japan</channel>
<channel site="vidio.com" lang="id" xmltv_id="NusantaraTV.id" site_id="7432">Nusantara TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="RajawaliTV.id" site_id="1561">RTV</channel>
<channel site="vidio.com" lang="id" xmltv_id="ROCKEntertainment.sg" site_id="8120">ROCK Entertainment</channel>
<channel site="vidio.com" lang="id" xmltv_id="ROCKExtreme.sg" site_id="8121">Rock Action</channel>
<channel site="vidio.com" lang="id" xmltv_id="SCTV.id" site_id="204">SCTV</channel>
<channel site="vidio.com" lang="id" xmltv_id="SPOTV2.id" site_id="17140">SPOTV 2</channel>
<channel site="vidio.com" lang="id" xmltv_id="SPOTV.id" site_id="17139">SPOTV</channel>
<channel site="vidio.com" lang="id" xmltv_id="TawafTV.id" site_id="12607">Tawaf TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="Trans7.id" site_id="734">Trans7</channel>
<channel site="vidio.com" lang="id" xmltv_id="TransTV.id" site_id="733">TRANS TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="TV5MondeAsia.fr" site_id="17278">TV5Monde</channel>
<channel site="vidio.com" lang="id" xmltv_id="tvNAsia.hk" site_id="6362">TVN</channel>
<channel site="vidio.com" lang="id" xmltv_id="tvOne.id" site_id="783">TVOne</channel>
<channel site="vidio.com" lang="id" xmltv_id="TVRI.id" site_id="6441">TVRI</channel>
<channel site="vidio.com" lang="id" xmltv_id="UChannel.id" site_id="6898">U-Channel TV</channel>
<channel site="vidio.com" lang="id" xmltv_id="ZooMoo.sg" site_id="6533">Zoomoo</channel>
</channels>

View File

@@ -1,89 +1,89 @@
const axios = require('axios')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const crypto = require('crypto')
dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(customParseFormat)
const WEB_CLIENT_SECRET = Buffer.from('dPr0QImQ7bc5o9LMntNba2DOsSbZcjUh')
const WEB_CLIENT_IV = Buffer.from('C8RWsrtFsoeyCyPt')
module.exports = {
site: 'vidio.com',
days: 2,
url({ date, channel }) {
return `https://api.vidio.com/livestreamings/${channel.site_id}/schedules?filter[date]=${date.format('YYYY-MM-DD')}`
},
request: {
async headers() {
const session = await loadSessionDetails()
if (!session || !session.api_key) return null
var cipher = crypto.createCipheriv('aes-256-cbc', WEB_CLIENT_SECRET, WEB_CLIENT_IV)
return {
'X-API-Key': cipher.update(session.api_key, 'utf8', 'base64') + cipher.final('base64'),
'X-Secure-Level': 2
}
}
},
parser({ content }) {
const programs = []
const json = JSON.parse(content)
if (Array.isArray(json?.data)) {
for (const program of json.data) {
programs.push({
title: program.attributes.title,
description: program.attributes.description,
start: dayjs(program.attributes.start_time),
stop: dayjs(program.attributes.end_time),
image: program.attributes.image_landscape_url
})
}
}
return programs
},
async channels() {
const channels = []
const json = await axios
.get(
'https://api.vidio.com/livestreamings?stream_type=tv_stream',
{
headers: await this.request.headers()
}
)
.then(response => response.data)
.catch(console.error)
if (Array.isArray(json?.data)) {
for (const channel of json.data) {
channels.push({
lang: 'id',
site_id: channel.id,
name: channel.attributes.title
})
}
}
return channels
}
}
function loadSessionDetails() {
return axios
.post(
'https://www.vidio.com/auth',
{},
{
headers: {
'Content-Type': 'application/json'
}
}
)
.then(r => r.data)
.catch(console.log)
const axios = require('axios')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const crypto = require('crypto')
dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(customParseFormat)
const WEB_CLIENT_SECRET = Buffer.from('dPr0QImQ7bc5o9LMntNba2DOsSbZcjUh')
const WEB_CLIENT_IV = Buffer.from('C8RWsrtFsoeyCyPt')
module.exports = {
site: 'vidio.com',
days: 2,
url({ date, channel }) {
return `https://api.vidio.com/livestreamings/${channel.site_id}/schedules?filter[date]=${date.format('YYYY-MM-DD')}`
},
request: {
async headers() {
const session = await loadSessionDetails()
if (!session || !session.api_key) return null
var cipher = crypto.createCipheriv('aes-256-cbc', WEB_CLIENT_SECRET, WEB_CLIENT_IV)
return {
'X-API-Key': cipher.update(session.api_key, 'utf8', 'base64') + cipher.final('base64'),
'X-Secure-Level': 2
}
}
},
parser({ content }) {
const programs = []
const json = JSON.parse(content)
if (Array.isArray(json?.data)) {
for (const program of json.data) {
programs.push({
title: program.attributes.title,
description: program.attributes.description,
start: dayjs(program.attributes.start_time),
stop: dayjs(program.attributes.end_time),
image: program.attributes.image_landscape_url
})
}
}
return programs
},
async channels() {
const channels = []
const json = await axios
.get(
'https://api.vidio.com/livestreamings?stream_type=tv_stream',
{
headers: await this.request.headers()
}
)
.then(response => response.data)
.catch(console.error)
if (Array.isArray(json?.data)) {
for (const channel of json.data) {
channels.push({
lang: 'id',
site_id: channel.id,
name: channel.attributes.title
})
}
}
return channels
}
}
function loadSessionDetails() {
return axios
.post(
'https://www.vidio.com/auth',
{},
{
headers: {
'Content-Type': 'application/json'
}
}
)
.then(r => r.data)
.catch(console.log)
}

View File

@@ -1,67 +1,67 @@
const { parser, url, request } = require('./vidio.com.config.js')
const fs = require('fs')
const path = require('path')
const axios = require('axios')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat')
dayjs.extend(customParseFormat)
dayjs.extend(utc)
jest.mock('axios')
const date = dayjs.utc('2025-07-01', 'YYYY-MM-DD').startOf('d')
const channel = {
site_id: '204',
xmltv_id: 'SCTV.id'
}
it('can generate valid url', () => {
expect(url({ channel, date })).toBe(
'https://api.vidio.com/livestreamings/204/schedules?filter[date]=2025-07-01'
)
})
it('can generate valid request headers', async () => {
axios.post.mockImplementation(url => {
if (url === 'https://www.vidio.com/auth') {
return Promise.resolve({
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/auth.json')))
})
} else {
return Promise.resolve({ data: '' })
}
})
const result = await request.headers()
expect(result).toMatchObject({
'X-API-Key':
'CH1ZFsN4N/MIfAds1DL9mP151CNqIpWHqZGRr+LkvUyiq3FRPuP1Kt6aK+pG3nEC1FXt0ZAAJ5FKP8QU8CZ5/jQdSYLVeFwl9NoIkegVpR6b7W2ZwbaF00OPr6ON1/FpLQ3RiUzTPpAqe7f+fwhOr0KrKy8PpCa54OHogaEjI3w=',
'X-Secure-Level': 2,
})
})
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
const results = parser({ content, channel, date }).map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
expect(results.length).toBe(21)
expect(results[0]).toMatchObject({
start: '2025-06-30T15:57:00.000Z',
stop: '2025-06-30T17:29:00.000Z',
title: 'Ftv PrimeTime : Cinta Dodol Inilah Yang Membuatku Lengket Padamu',
description: 'Film televisi yang mengangkat kisah romantisme kehidupan dengan konflik yang menarik. tayang setiap hari'
})
})
it('can handle empty guide', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
const results = parser({ content, channel })
expect(results).toMatchObject([])
})
const { parser, url, request } = require('./vidio.com.config.js')
const fs = require('fs')
const path = require('path')
const axios = require('axios')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat')
dayjs.extend(customParseFormat)
dayjs.extend(utc)
jest.mock('axios')
const date = dayjs.utc('2025-07-01', 'YYYY-MM-DD').startOf('d')
const channel = {
site_id: '204',
xmltv_id: 'SCTV.id'
}
it('can generate valid url', () => {
expect(url({ channel, date })).toBe(
'https://api.vidio.com/livestreamings/204/schedules?filter[date]=2025-07-01'
)
})
it('can generate valid request headers', async () => {
axios.post.mockImplementation(url => {
if (url === 'https://www.vidio.com/auth') {
return Promise.resolve({
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/auth.json')))
})
} else {
return Promise.resolve({ data: '' })
}
})
const result = await request.headers()
expect(result).toMatchObject({
'X-API-Key':
'CH1ZFsN4N/MIfAds1DL9mP151CNqIpWHqZGRr+LkvUyiq3FRPuP1Kt6aK+pG3nEC1FXt0ZAAJ5FKP8QU8CZ5/jQdSYLVeFwl9NoIkegVpR6b7W2ZwbaF00OPr6ON1/FpLQ3RiUzTPpAqe7f+fwhOr0KrKy8PpCa54OHogaEjI3w=',
'X-Secure-Level': 2,
})
})
it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
const results = parser({ content, channel, date }).map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
expect(results.length).toBe(21)
expect(results[0]).toMatchObject({
start: '2025-06-30T15:57:00.000Z',
stop: '2025-06-30T17:29:00.000Z',
title: 'Ftv PrimeTime : Cinta Dodol Inilah Yang Membuatku Lengket Padamu',
description: 'Film televisi yang mengangkat kisah romantisme kehidupan dengan konflik yang menarik. tayang setiap hari'
})
})
it('can handle empty guide', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
const results = parser({ content, channel })
expect(results).toMatchObject([])
})

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8" ?><tv date="20221020">
<channel id="Channel2.us"><display-name>Channel 2</display-name><icon src="https://i.imgur.com/qmRnD0M.png"/><url>https://example.com</url><lcn>36</lcn></channel>
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example.com</url></channel>
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example.com</url></channel>
<programme start="20221019043000 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221019043100 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
<programme start="20221020043000 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221020043100 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
<?xml version="1.0" encoding="UTF-8" ?><tv date="20221020">
<channel id="Channel2.us"><display-name>Channel 2</display-name><icon src="https://i.imgur.com/qmRnD0M.png"/><url>https://example.com</url><lcn>36</lcn></channel>
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example.com</url></channel>
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example.com</url></channel>
<programme start="20221019043000 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221019043100 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
<programme start="20221020043000 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221020043100 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
</tv>

View File

@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?><tv date="20221020">
<channel id="Channel1.us"><display-name>Custom Channel 1</display-name><url>https://example.com</url></channel>
<channel id="Channel2.us"><display-name>Custom Channel 2</display-name><icon src="https://i.imgur.com/qmRnD0M.png"/><url>https://example.com</url></channel>
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example.com</url></channel>
<channel id="Channel3.us@Wrong"><display-name>Channel 3</display-name><icon src="https://upload.wikimedia.org/wikipedia/commons/6/64/6%27eren_2015.png"/><url>https://example2.com</url></channel>
<channel id="Channel4.us@HD"><display-name>Channel 4</display-name><icon src="https://i.imgur.com/BPzH88J.png"/><url>https://example2.com</url></channel>
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example2.com</url></channel>
<programme start="20221019043000 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221019043100 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
<programme start="20221019044000 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example2.com)</title></programme>
<programme start="20221020043000 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221020043100 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
<programme start="20221019043100 +0000" stop="20221019071000 +0000" channel="Channel3.us@Wrong"><title lang="en">Program1 (example2.com)</title></programme>
<programme start="20221019043100 +0000" stop="20221019071000 +0000" channel="Channel4.us@HD"><title lang="en">Program1 (example2.com)</title></programme>
<?xml version="1.0" encoding="UTF-8" ?><tv date="20221020">
<channel id="Channel1.us"><display-name>Custom Channel 1</display-name><url>https://example.com</url></channel>
<channel id="Channel2.us"><display-name>Custom Channel 2</display-name><icon src="https://i.imgur.com/qmRnD0M.png"/><url>https://example.com</url></channel>
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example.com</url></channel>
<channel id="Channel3.us@Wrong"><display-name>Channel 3</display-name><icon src="https://upload.wikimedia.org/wikipedia/commons/6/64/6%27eren_2015.png"/><url>https://example2.com</url></channel>
<channel id="Channel4.us@HD"><display-name>Channel 4</display-name><icon src="https://i.imgur.com/BPzH88J.png"/><url>https://example2.com</url></channel>
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example2.com</url></channel>
<programme start="20221019043000 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221019043100 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
<programme start="20221019044000 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example2.com)</title></programme>
<programme start="20221020043000 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221020043100 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
<programme start="20221019043100 +0000" stop="20221019071000 +0000" channel="Channel3.us@Wrong"><title lang="en">Program1 (example2.com)</title></programme>
<programme start="20221019043100 +0000" stop="20221019071000 +0000" channel="Channel4.us@HD"><title lang="en">Program1 (example2.com)</title></programme>
</tv>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?><tv date="20221020">
<channel id="Channel2.us"><display-name>Channel 2</display-name><icon src="https://i.imgur.com/qmRnD0M.png"/><url>https://example.com</url><lcn>36</lcn></channel>
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example.com</url></channel>
<programme start="20221019043100 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
<programme start="20221020043100 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
<?xml version="1.0" encoding="UTF-8" ?><tv date="20221020">
<channel id="Channel2.us"><display-name>Channel 2</display-name><icon src="https://i.imgur.com/qmRnD0M.png"/><url>https://example.com</url><lcn>36</lcn></channel>
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example.com</url></channel>
<programme start="20221019043100 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
<programme start="20221020043100 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
</tv>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?><tv date="20221020">
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example.com</url></channel>
<channel id="Channel3.us"><display-name>Channel 3</display-name><icon src="https://upload.wikimedia.org/wikipedia/commons/6/64/6%27eren_2015.png"/><url>https://example.com</url></channel>
<programme start="20221019043000 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221020043000 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221019043100 +0000" stop="20221019071000 +0000" channel="Channel3.us"><title lang="it">Program1 (example.com)</title></programme>
<programme start="20221020043100 +0000" stop="20221020071000 +0000" channel="Channel3.us"><title lang="it">Program1 (example.com)</title></programme>
<?xml version="1.0" encoding="UTF-8" ?><tv date="20221020">
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example.com</url></channel>
<channel id="Channel3.us"><display-name>Channel 3</display-name><icon src="https://upload.wikimedia.org/wikipedia/commons/6/64/6%27eren_2015.png"/><url>https://example.com</url></channel>
<programme start="20221019043000 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221020043000 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221019043100 +0000" stop="20221019071000 +0000" channel="Channel3.us"><title lang="it">Program1 (example.com)</title></programme>
<programme start="20221020043100 +0000" stop="20221020071000 +0000" channel="Channel3.us"><title lang="it">Program1 (example.com)</title></programme>
</tv>

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8" ?><tv date="20221020">
<channel id="Channel2.us"><display-name>Channel 2</display-name><icon src="https://i.imgur.com/qmRnD0M.png"/><url>https://example.com</url><lcn>36</lcn></channel>
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example.com</url></channel>
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example.com</url></channel>
<programme start="20221019043000 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221019043100 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
<programme start="20221020043000 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221020043100 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
<?xml version="1.0" encoding="UTF-8" ?><tv date="20221020">
<channel id="Channel2.us"><display-name>Channel 2</display-name><icon src="https://i.imgur.com/qmRnD0M.png"/><url>https://example.com</url><lcn>36</lcn></channel>
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example.com</url></channel>
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example.com</url></channel>
<programme start="20221019043000 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221019043100 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
<programme start="20221020043000 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221020043100 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
</tv>

View File

@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?><tv date="20221020">
<channel id="Channel2.us"><display-name>Channel 2</display-name><icon src="https://i.imgur.com/qmRnD0M.png"/><url>https://example.com</url><lcn>36</lcn></channel>
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example.com</url></channel>
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example.com</url></channel>
<channel id="Channel3.us"><display-name>Channel 3</display-name><icon src="https://upload.wikimedia.org/wikipedia/commons/6/64/6%27eren_2015.png"/><url>https://example2.com</url></channel>
<channel id="Channel4.us@HD"><display-name>Channel 4</display-name><icon src="https://i.imgur.com/BPzH88J.png"/><url>https://example2.com</url></channel>
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example2.com</url></channel>
<programme start="20221019043000 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221019043100 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
<programme start="20221019044000 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example2.com)</title></programme>
<programme start="20221020043000 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221020043100 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
<programme start="20221019043100 +0000" stop="20221019071000 +0000" channel="Channel3.us"><title lang="en">Program1 (example2.com)</title></programme>
<programme start="20221019043100 +0000" stop="20221019071000 +0000" channel="Channel4.us@HD"><title lang="en">Program1 (example2.com)</title></programme>
<?xml version="1.0" encoding="UTF-8" ?><tv date="20221020">
<channel id="Channel2.us"><display-name>Channel 2</display-name><icon src="https://i.imgur.com/qmRnD0M.png"/><url>https://example.com</url><lcn>36</lcn></channel>
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example.com</url></channel>
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example.com</url></channel>
<channel id="Channel3.us"><display-name>Channel 3</display-name><icon src="https://upload.wikimedia.org/wikipedia/commons/6/64/6%27eren_2015.png"/><url>https://example2.com</url></channel>
<channel id="Channel4.us@HD"><display-name>Channel 4</display-name><icon src="https://i.imgur.com/BPzH88J.png"/><url>https://example2.com</url></channel>
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example2.com</url></channel>
<programme start="20221019043000 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221019043100 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
<programme start="20221019044000 +0000" stop="20221019071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example2.com)</title></programme>
<programme start="20221020043000 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="fr">Programme1 (example.com)</title></programme>
<programme start="20221020043100 +0000" stop="20221020071000 +0000" channel="Channel1.us"><title lang="en">Program1 (example.com)</title></programme>
<programme start="20221019043100 +0000" stop="20221019071000 +0000" channel="Channel3.us"><title lang="en">Program1 (example2.com)</title></programme>
<programme start="20221019043100 +0000" stop="20221019071000 +0000" channel="Channel4.us@HD"><title lang="en">Program1 (example2.com)</title></programme>
</tv>

View File

@@ -1,61 +1,61 @@
[
{
"id": "Bravo.us",
"name": "Bravo",
"network": null,
"country": "US",
"subdivision": null,
"city": null,
"categories": [],
"is_nsfw": false,
"closed": "2020-01-01",
"replaced_by": "R6.co"
},
{
"id": "Bravos.us",
"name": "Bravos",
"network": null,
"country": "US",
"subdivision": null,
"city": null,
"categories": [],
"is_nsfw": false
},
{
"id": "CNNInternational.us",
"name": "CNN International",
"alt_names": ["CNN", "CNN Int"],
"network": null,
"country": "US",
"subdivision": null,
"city": null,
"categories": [
"news"
],
"is_nsfw": false
},
{
"id": "MNetMovies2.za",
"name": "M-Net Movies 2",
"network": null,
"country": "ZA",
"subdivision": null,
"city": null,
"categories": [],
"is_nsfw": false
},
{"id":"6eren.dk","name":"6'eren","alt_names":[],"network":null,"owners":["Warner Bros. Discovery EMEA"],"country":"DK","subdivision":null,"city":null,"broadcast_area":["c/DK"],"languages":["dan"],"categories":[],"is_nsfw":false,"launched":"2009-01-01","closed":null,"replaced_by":null,"website":"http://www.6-eren.dk/"},
{"id":"BBCNews.uk","name":"BBC News","alt_names":[],"network":null,"owners":[],"country":"UK","subdivision":null,"city":null,"broadcast_area":["c/UK"],"languages":["eng"],"categories":["news"],"is_nsfw":false,"launched":null,"closed":null,"replaced_by":null,"website":"http://news.bbc.co.uk/"},
{
"id": "CNN.us",
"name": "CNN",
"network": null,
"country": "US",
"subdivision": null,
"city": null,
"categories": [],
"is_nsfw": false
},
{"id":"Channel2.us","name":"Channel 2 [API]","alt_names":[],"network":null,"owners":[],"country":"UK","subdivision":null,"city":null,"broadcast_area":["c/US"],"languages":["eng"],"categories":[],"is_nsfw":false,"launched":null,"closed":null,"replaced_by":null,"website":""},
{"id":"Channel3.us","name":"Channel 3 [API]","alt_names":[],"network":null,"owners":[],"country":"UK","subdivision":null,"city":null,"broadcast_area":["c/US"],"languages":["eng"],"categories":[],"is_nsfw":false,"launched":null,"closed":null,"replaced_by":null,"website":""}
[
{
"id": "Bravo.us",
"name": "Bravo",
"network": null,
"country": "US",
"subdivision": null,
"city": null,
"categories": [],
"is_nsfw": false,
"closed": "2020-01-01",
"replaced_by": "R6.co"
},
{
"id": "Bravos.us",
"name": "Bravos",
"network": null,
"country": "US",
"subdivision": null,
"city": null,
"categories": [],
"is_nsfw": false
},
{
"id": "CNNInternational.us",
"name": "CNN International",
"alt_names": ["CNN", "CNN Int"],
"network": null,
"country": "US",
"subdivision": null,
"city": null,
"categories": [
"news"
],
"is_nsfw": false
},
{
"id": "MNetMovies2.za",
"name": "M-Net Movies 2",
"network": null,
"country": "ZA",
"subdivision": null,
"city": null,
"categories": [],
"is_nsfw": false
},
{"id":"6eren.dk","name":"6'eren","alt_names":[],"network":null,"owners":["Warner Bros. Discovery EMEA"],"country":"DK","subdivision":null,"city":null,"broadcast_area":["c/DK"],"languages":["dan"],"categories":[],"is_nsfw":false,"launched":"2009-01-01","closed":null,"replaced_by":null,"website":"http://www.6-eren.dk/"},
{"id":"BBCNews.uk","name":"BBC News","alt_names":[],"network":null,"owners":[],"country":"UK","subdivision":null,"city":null,"broadcast_area":["c/UK"],"languages":["eng"],"categories":["news"],"is_nsfw":false,"launched":null,"closed":null,"replaced_by":null,"website":"http://news.bbc.co.uk/"},
{
"id": "CNN.us",
"name": "CNN",
"network": null,
"country": "US",
"subdivision": null,
"city": null,
"categories": [],
"is_nsfw": false
},
{"id":"Channel2.us","name":"Channel 2 [API]","alt_names":[],"network":null,"owners":[],"country":"UK","subdivision":null,"city":null,"broadcast_area":["c/US"],"languages":["eng"],"categories":[],"is_nsfw":false,"launched":null,"closed":null,"replaced_by":null,"website":""},
{"id":"Channel3.us","name":"Channel 3 [API]","alt_names":[],"network":null,"owners":[],"country":"UK","subdivision":null,"city":null,"broadcast_area":["c/US"],"languages":["eng"],"categories":[],"is_nsfw":false,"launched":null,"closed":null,"replaced_by":null,"website":""}
]

View File

@@ -1,28 +1,28 @@
module.exports = {
site: 'example.com',
days: 2,
request: {
timeout: 1000
},
url: 'https://example.com',
parser({ channel, date }) {
if (channel.xmltv_id === 'Channel2.us') return []
else if (channel.xmltv_id === 'Channel1.us' && channel.lang === 'fr') {
return [
{
title: 'Programme1 (example.com)',
start: `${date.format('YYYY-MM-DD')}T04:30:00.000Z`,
stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z`
}
]
}
return [
{
title: 'Program1 (example.com)',
start: `${date.format('YYYY-MM-DD')}T04:31:00.000Z`,
stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z`
}
]
}
}
module.exports = {
site: 'example.com',
days: 2,
request: {
timeout: 1000
},
url: 'https://example.com',
parser({ channel, date }) {
if (channel.xmltv_id === 'Channel2.us') return []
else if (channel.xmltv_id === 'Channel1.us' && channel.lang === 'fr') {
return [
{
title: 'Programme1 (example.com)',
start: `${date.format('YYYY-MM-DD')}T04:30:00.000Z`,
stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z`
}
]
}
return [
{
title: 'Program1 (example.com)',
start: `${date.format('YYYY-MM-DD')}T04:31:00.000Z`,
stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z`
}
]
}
}

View File

@@ -1,28 +1,28 @@
module.exports = {
site: 'example.com',
days: 2,
request: {
timeout: 1000
},
url: 'https://example.com',
parser({ channel, date }) {
if (channel.xmltv_id === 'Channel2.us') return []
else if (channel.xmltv_id === 'Channel1.us' && channel.lang === 'fr') {
return [
{
title: 'Programme1 (example.com)',
start: `${date.format('YYYY-MM-DD')}T04:30:00.000Z`,
stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z`
}
]
}
return [
{
title: 'Program1 (example.com)',
start: `${date.format('YYYY-MM-DD')}T04:31:00.000Z`,
stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z`
}
]
}
}
module.exports = {
site: 'example.com',
days: 2,
request: {
timeout: 1000
},
url: 'https://example.com',
parser({ channel, date }) {
if (channel.xmltv_id === 'Channel2.us') return []
else if (channel.xmltv_id === 'Channel1.us' && channel.lang === 'fr') {
return [
{
title: 'Programme1 (example.com)',
start: `${date.format('YYYY-MM-DD')}T04:30:00.000Z`,
stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z`
}
]
}
return [
{
title: 'Program1 (example.com)',
start: `${date.format('YYYY-MM-DD')}T04:31:00.000Z`,
stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z`
}
]
}
}

View File

@@ -1,23 +1,23 @@
module.exports = {
site: 'example2.com',
url: 'https://example2.com',
parser({ channel, date }) {
if (channel.lang === 'fr') {
return [
{
title: 'Programme1 (example2.com)',
start: `${date.format('YYYY-MM-DD')}T04:40:00.000Z`,
stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z`
}
]
}
return [
{
title: 'Program1 (example2.com)',
start: `${date.format('YYYY-MM-DD')}T04:31:00.000Z`,
stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z`
}
]
}
}
module.exports = {
site: 'example2.com',
url: 'https://example2.com',
parser({ channel, date }) {
if (channel.lang === 'fr') {
return [
{
title: 'Programme1 (example2.com)',
start: `${date.format('YYYY-MM-DD')}T04:40:00.000Z`,
stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z`
}
]
}
return [
{
title: 'Program1 (example2.com)',
start: `${date.format('YYYY-MM-DD')}T04:31:00.000Z`,
stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z`
}
]
}
}

View File

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

View File

@@ -1,36 +1,36 @@
import { execSync } from 'child_process'
import fs from 'fs-extra'
const ENV_VAR = 'cross-env DATA_DIR=tests/__data__/input/__data__'
beforeEach(() => {
fs.emptyDirSync('tests/__data__/output')
fs.copySync(
'tests/__data__/input/channels_edit/example.com.channels.xml',
'tests/__data__/output/channels.xml'
)
})
describe('channels:edit', () => {
it('shows list of options for a channel', () => {
const cmd = `${ENV_VAR} npm run channels:edit --- tests/__data__/output/channels.xml`
try {
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
checkStdout(stdout)
} catch (error: unknown) {
// NOTE: for Windows only
if (process.env.DEBUG === 'true') console.log(cmd, error)
if (error && typeof error === 'object' && 'stdout' in error) {
checkStdout(error.stdout as string)
}
}
})
})
function checkStdout(stdout: string) {
expect(stdout).toContain('CNNInternational.us (CNN International, CNN, CNN Int)')
expect(stdout).toContain('Type...')
expect(stdout).toContain('Skip')
expect(stdout).toContain("File 'tests/__data__/output/channels.xml' successfully saved")
}
import { execSync } from 'child_process'
import fs from 'fs-extra'
const ENV_VAR = 'cross-env DATA_DIR=tests/__data__/input/__data__'
beforeEach(() => {
fs.emptyDirSync('tests/__data__/output')
fs.copySync(
'tests/__data__/input/channels_edit/example.com.channels.xml',
'tests/__data__/output/channels.xml'
)
})
describe('channels:edit', () => {
it('shows list of options for a channel', () => {
const cmd = `${ENV_VAR} npm run channels:edit --- tests/__data__/output/channels.xml`
try {
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
checkStdout(stdout)
} catch (error: unknown) {
// NOTE: for Windows only
if (process.env.DEBUG === 'true') console.log(cmd, error)
if (error && typeof error === 'object' && 'stdout' in error) {
checkStdout(error.stdout as string)
}
}
})
})
function checkStdout(stdout: string) {
expect(stdout).toContain('CNNInternational.us (CNN International, CNN, CNN Int)')
expect(stdout).toContain('Type...')
expect(stdout).toContain('Skip')
expect(stdout).toContain("File 'tests/__data__/output/channels.xml' successfully saved")
}

View File

@@ -1,162 +1,162 @@
import { pathToFileURL } from 'node:url'
import { execSync } from 'child_process'
import { Zip } from '@freearhey/core'
import fs from 'fs-extra'
import path from 'path'
const ENV_VAR =
'cross-env SITES_DIR=tests/__data__/input/epg_grab/sites CURR_DATE=2022-10-20 DATA_DIR=tests/__data__/input/__data__'
beforeEach(() => {
fs.emptyDirSync('tests/__data__/output')
})
describe('epg:grab', () => {
it('can grab epg by site name', () => {
const cmd = `${ENV_VAR} npm run grab --- --site=example.com --output="${path.resolve(
'tests/__data__/output/guide.xml'
)}" --timeout=100`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/guide.xml')).toEqual(
content('tests/__data__/expected/epg_grab/base.guide.xml')
)
})
it('it will raise an error if the timeout is exceeded', () => {
const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/custom.channels.xml --output=tests/__data__/output/guide.xml --timeout=0`
let errorThrown = false
try {
execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] })
// If no error is thrown, explicitly fail the test
fail('Expected command to throw an error due to timeout, but it did not.')
} catch (error) {
errorThrown = true
if (process.env.DEBUG === 'true') {
const stderr = error.stderr?.toString() || ''
const stdout = error.stdout?.toString() || ''
const combined = stderr + stdout
console.log('stdout:', stdout)
console.log('stderr:', stderr)
console.log('combined:', combined)
console.log('exit code:', error.exitCode)
console.log('Error output:', combined)
}
}
expect(errorThrown).toBe(true)
})
it('can grab epg with wildcard as output', () => {
const cmd = `${ENV_VAR} npm run grab --- --channels="tests/__data__/input/epg_grab/sites/example.com/example.com.channels.xml" --output="tests/__data__/output/guides/{lang}/{site}.xml" --timeout=100`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/guides/en/example.com.xml')).toEqual(
content('tests/__data__/expected/epg_grab/guides/en/example.com.xml')
)
expect(content('tests/__data__/output/guides/fr/example.com.xml')).toEqual(
content('tests/__data__/expected/epg_grab/guides/fr/example.com.xml')
)
})
it('can grab epg then language filter enabled', () => {
const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/sites/example.com/example.com.channels.xml --output=tests/__data__/output/guides/{lang}/{site}.xml --lang=fr --timeout=100`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/guides/fr/example.com.xml')).toEqual(
content('tests/__data__/expected/epg_grab/guides/fr/example.com.xml')
)
})
it('can grab epg then using a multi-language filter', () => {
const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/example.com/example.com.channels.xml --output=tests/__data__/output/guides/{site}.xml --lang=fr,it --timeout=100`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/guides/example.com.xml')).toEqual(
content('tests/__data__/expected/epg_grab/lang.guide.xml')
)
})
it('can grab epg via https proxy', () => {
const cmd = `${ENV_VAR} npm run grab --- --site=example.com --proxy=https://bob:123456@proxy.com:1234 --output="${path.resolve(
'tests/__data__/output/guide.xml'
)}" --timeout=100`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/guide.xml')).toEqual(
content('tests/__data__/expected/epg_grab/proxy.guide.xml')
)
})
it('can grab epg via socks5 proxy', () => {
const cmd = `${ENV_VAR} npm run grab --- --site=example.com --proxy=socks5://bob:123456@proxy.com:1234 --output="${path.resolve(
'tests/__data__/output/guide.xml'
)}" --timeout=100`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/guide.xml')).toEqual(
content('tests/__data__/expected/epg_grab/proxy.guide.xml')
)
})
it('can grab epg with curl option', () => {
const cmd = `${ENV_VAR} npm run grab --- --site=example.com --curl --output="${path.resolve(
'tests/__data__/output/guide.xml'
)}" --timeout=100`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(stdout).toContain('curl https://example.com')
})
it('can grab epg with multiple channels.xml files', () => {
const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/sites/**/*.channels.xml --output=tests/__data__/output/guide.xml --timeout=100`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/guide.xml')).toEqual(
content('tests/__data__/expected/epg_grab/template.guide.xml')
)
})
it('can grab epg using custom channels list', () => {
const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/custom.channels.xml --output=tests/__data__/output/guide.xml --timeout=100`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/guide.xml')).toEqual(
content('tests/__data__/expected/epg_grab/custom_channels.guide.xml')
)
})
it('can grab epg with gzip option enabled', () => {
const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/sites/**/*.channels.xml --output="${path.resolve(
'tests/__data__/output/guide.xml'
)}" --gzip --timeout=100`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/guide.xml')).toEqual(
content('tests/__data__/expected/epg_grab/template.guide.xml')
)
const zip = new Zip()
const expected = zip.decompress(fs.readFileSync('tests/__data__/output/guide.xml.gz'))
const result = zip.decompress(
fs.readFileSync('tests/__data__/expected/epg_grab/template.guide.xml.gz')
)
expect(expected).toEqual(result)
})
})
function content(filepath: string) {
return fs.readFileSync(pathToFileURL(filepath), {
encoding: 'utf8'
})
}
import { pathToFileURL } from 'node:url'
import { execSync } from 'child_process'
import { Zip } from '@freearhey/core'
import fs from 'fs-extra'
import path from 'path'
const ENV_VAR =
'cross-env SITES_DIR=tests/__data__/input/epg_grab/sites CURR_DATE=2022-10-20 DATA_DIR=tests/__data__/input/__data__'
beforeEach(() => {
fs.emptyDirSync('tests/__data__/output')
})
describe('epg:grab', () => {
it('can grab epg by site name', () => {
const cmd = `${ENV_VAR} npm run grab --- --site=example.com --output="${path.resolve(
'tests/__data__/output/guide.xml'
)}" --timeout=100`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/guide.xml')).toEqual(
content('tests/__data__/expected/epg_grab/base.guide.xml')
)
})
it('it will raise an error if the timeout is exceeded', () => {
const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/custom.channels.xml --output=tests/__data__/output/guide.xml --timeout=0`
let errorThrown = false
try {
execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] })
// If no error is thrown, explicitly fail the test
fail('Expected command to throw an error due to timeout, but it did not.')
} catch (error) {
errorThrown = true
if (process.env.DEBUG === 'true') {
const stderr = error.stderr?.toString() || ''
const stdout = error.stdout?.toString() || ''
const combined = stderr + stdout
console.log('stdout:', stdout)
console.log('stderr:', stderr)
console.log('combined:', combined)
console.log('exit code:', error.exitCode)
console.log('Error output:', combined)
}
}
expect(errorThrown).toBe(true)
})
it('can grab epg with wildcard as output', () => {
const cmd = `${ENV_VAR} npm run grab --- --channels="tests/__data__/input/epg_grab/sites/example.com/example.com.channels.xml" --output="tests/__data__/output/guides/{lang}/{site}.xml" --timeout=100`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/guides/en/example.com.xml')).toEqual(
content('tests/__data__/expected/epg_grab/guides/en/example.com.xml')
)
expect(content('tests/__data__/output/guides/fr/example.com.xml')).toEqual(
content('tests/__data__/expected/epg_grab/guides/fr/example.com.xml')
)
})
it('can grab epg then language filter enabled', () => {
const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/sites/example.com/example.com.channels.xml --output=tests/__data__/output/guides/{lang}/{site}.xml --lang=fr --timeout=100`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/guides/fr/example.com.xml')).toEqual(
content('tests/__data__/expected/epg_grab/guides/fr/example.com.xml')
)
})
it('can grab epg then using a multi-language filter', () => {
const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/example.com/example.com.channels.xml --output=tests/__data__/output/guides/{site}.xml --lang=fr,it --timeout=100`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/guides/example.com.xml')).toEqual(
content('tests/__data__/expected/epg_grab/lang.guide.xml')
)
})
it('can grab epg via https proxy', () => {
const cmd = `${ENV_VAR} npm run grab --- --site=example.com --proxy=https://bob:123456@proxy.com:1234 --output="${path.resolve(
'tests/__data__/output/guide.xml'
)}" --timeout=100`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/guide.xml')).toEqual(
content('tests/__data__/expected/epg_grab/proxy.guide.xml')
)
})
it('can grab epg via socks5 proxy', () => {
const cmd = `${ENV_VAR} npm run grab --- --site=example.com --proxy=socks5://bob:123456@proxy.com:1234 --output="${path.resolve(
'tests/__data__/output/guide.xml'
)}" --timeout=100`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/guide.xml')).toEqual(
content('tests/__data__/expected/epg_grab/proxy.guide.xml')
)
})
it('can grab epg with curl option', () => {
const cmd = `${ENV_VAR} npm run grab --- --site=example.com --curl --output="${path.resolve(
'tests/__data__/output/guide.xml'
)}" --timeout=100`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(stdout).toContain('curl https://example.com')
})
it('can grab epg with multiple channels.xml files', () => {
const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/sites/**/*.channels.xml --output=tests/__data__/output/guide.xml --timeout=100`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/guide.xml')).toEqual(
content('tests/__data__/expected/epg_grab/template.guide.xml')
)
})
it('can grab epg using custom channels list', () => {
const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/custom.channels.xml --output=tests/__data__/output/guide.xml --timeout=100`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/guide.xml')).toEqual(
content('tests/__data__/expected/epg_grab/custom_channels.guide.xml')
)
})
it('can grab epg with gzip option enabled', () => {
const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/sites/**/*.channels.xml --output="${path.resolve(
'tests/__data__/output/guide.xml'
)}" --gzip --timeout=100`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(content('tests/__data__/output/guide.xml')).toEqual(
content('tests/__data__/expected/epg_grab/template.guide.xml')
)
const zip = new Zip()
const expected = zip.decompress(fs.readFileSync('tests/__data__/output/guide.xml.gz'))
const result = zip.decompress(
fs.readFileSync('tests/__data__/expected/epg_grab/template.guide.xml.gz')
)
expect(expected).toEqual(result)
})
})
function content(filepath: string) {
return fs.readFileSync(pathToFileURL(filepath), {
encoding: 'utf8'
})
}

View File

@@ -1,41 +1,41 @@
import { execSync } from 'child_process'
import fs from 'fs-extra'
import { pathToFileURL } from 'node:url'
const ENV_VAR = 'cross-env SITES_DIR=tests/__data__/output/sites'
beforeEach(() => {
fs.emptyDirSync('tests/__data__/output')
fs.mkdirSync('tests/__data__/output/sites')
})
it('can create new site config from template', () => {
const cmd = `${ENV_VAR} npm run sites:init --- example.com`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(exists('tests/__data__/output/sites/example.com')).toBe(true)
expect(exists('tests/__data__/output/sites/example.com/example.com.test.js')).toBe(true)
expect(exists('tests/__data__/output/sites/example.com/example.com.config.js')).toBe(true)
expect(exists('tests/__data__/output/sites/example.com/readme.md')).toBe(true)
expect(content('tests/__data__/output/sites/example.com/example.com.test.js')).toEqual(
content('tests/__data__/expected/sites_init/example.com.test.js')
)
expect(content('tests/__data__/output/sites/example.com/example.com.config.js')).toEqual(
content('tests/__data__/expected/sites_init/example.com.config.js')
)
expect(content('tests/__data__/output/sites/example.com/readme.md')).toEqual(
content('tests/__data__/expected/sites_init/readme.md')
)
})
function content(filepath: string) {
return fs.readFileSync(pathToFileURL(filepath), {
encoding: 'utf8'
})
}
function exists(filepath: string) {
return fs.existsSync(pathToFileURL(filepath))
}
import { execSync } from 'child_process'
import fs from 'fs-extra'
import { pathToFileURL } from 'node:url'
const ENV_VAR = 'cross-env SITES_DIR=tests/__data__/output/sites'
beforeEach(() => {
fs.emptyDirSync('tests/__data__/output')
fs.mkdirSync('tests/__data__/output/sites')
})
it('can create new site config from template', () => {
const cmd = `${ENV_VAR} npm run sites:init --- example.com`
const stdout = execSync(cmd, { encoding: 'utf8' })
if (process.env.DEBUG === 'true') console.log(cmd, stdout)
expect(exists('tests/__data__/output/sites/example.com')).toBe(true)
expect(exists('tests/__data__/output/sites/example.com/example.com.test.js')).toBe(true)
expect(exists('tests/__data__/output/sites/example.com/example.com.config.js')).toBe(true)
expect(exists('tests/__data__/output/sites/example.com/readme.md')).toBe(true)
expect(content('tests/__data__/output/sites/example.com/example.com.test.js')).toEqual(
content('tests/__data__/expected/sites_init/example.com.test.js')
)
expect(content('tests/__data__/output/sites/example.com/example.com.config.js')).toEqual(
content('tests/__data__/expected/sites_init/example.com.config.js')
)
expect(content('tests/__data__/output/sites/example.com/readme.md')).toEqual(
content('tests/__data__/expected/sites_init/readme.md')
)
})
function content(filepath: string) {
return fs.readFileSync(pathToFileURL(filepath), {
encoding: 'utf8'
})
}
function exists(filepath: string) {
return fs.existsSync(pathToFileURL(filepath))
}

View File

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