Merge branch 'master' into pr/2180

This commit is contained in:
freearhey 2023-10-09 12:26:57 +03:00
commit b4135fefe1
642 changed files with 36943 additions and 33981 deletions

View file

@ -1,11 +0,0 @@
module.exports = {
env: {
browser: false,
node: true,
es6: true
},
extends: 'eslint:recommended',
parserOptions: {
ecmaVersion: 12
}
}

39
.eslintrc.json Normal file
View file

@ -0,0 +1,39 @@
{
"env": {
"node": true,
"es2021": true,
"jest": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/no-var-requires": "off",
"no-case-declarations": "off",
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single",
{
"avoidEscape": true
}
],
"semi": [
"error",
"never"
]
}
}

13
.github/CODE_OF_CONDUCT.md vendored Normal file
View file

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

View file

@ -1,33 +0,0 @@
name: 🗓 EPG Request
description: Request to add a channel to existing guide
labels: ['🗓 epg request']
body:
- type: markdown
attributes:
value: |
Please fill out the issue template as much as you can so we could efficiently process your request.
- type: input
attributes:
label: Guide
description: Link to the file in which you want to add these channels
placeholder: 'https://iptv-org.github.io/epg/guides/en/example.co.uk.xml'
validations:
required: true
- type: textarea
attributes:
label: Channels
description: List all channels that need to be added
placeholder: |
- Channel1
- Channel2
...
validations:
required: true
- type: textarea
attributes:
label: Notes
description: Anything else we should know?

View file

@ -1,6 +1,6 @@
name: Source Request
name: 📝 Source Request
description: Request to add a new source
labels: [' source request']
labels: ['source request']
body:
- type: markdown

View file

@ -11,7 +11,7 @@ body:
- type: input
attributes:
label: Site
description: The name of the site listed in the [sites](https://github.com/iptv-org/epg/tree/master/sites) folder
description: The name of the site
placeholder: 'guidatv.sky.it'
validations:
required: true

View file

@ -1,15 +0,0 @@
name: 💡 Feature Request
description: For any ideas or feature requests
labels: ['💡 feature request']
body:
- type: markdown
attributes:
value: |
Please describe your idea in as much detail as possible so that we can efficiently process your request.
- type: textarea
attributes:
label: Description
validations:
required: true

View file

@ -1,10 +0,0 @@
name: ❓ Ask a Question
description: Any questions about this repository
labels: ['❓ question']
body:
- type: textarea
attributes:
label: Description
validations:
required: true

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: 💡 Feature request
url: https://github.com/orgs/iptv-org/discussions/categories/ideas
about: For any ideas or feature requests
- name: ❓ Ask a question
url: https://github.com/orgs/iptv-org/discussions/categories/q-a
about: Ask questions about this project

7
.gitignore vendored
View file

@ -1,6 +1,5 @@
/node_modules/
/logs/
/temp/
/channels.xml
/guide.xml
/guide.xml.gz
/.jenkins/
/guides/
/guide.xml.gz

View file

@ -1,3 +0,0 @@
*
!*.js
!*/

View file

@ -6,7 +6,5 @@ module.exports = {
singleQuote: true,
printWidth: 100,
trailingComma: 'none',
bracketSpacing: true,
singleAttributePerLine: false,
arrowParens: 'avoid'
}

View file

@ -1,21 +1,28 @@
# Contributing Guide
### How do I add a program guide for the channel?
- [How to?](#how-to)
- [Project Structure](#project-structure)
- [Scripts](#scripts)
First, open the [/sites](/sites) folder and select the source that you know has the guide for the channel you want.
## How to?
### How to add a channel to the guide?
Open the [/sites](/sites) folder and select the source that you know has the guide for the channel you want.
Then in the selected folder open the file `*.channels.xml` and add to it:
```xml
<channel lang="LANGUAGE_CODE" xmltv_id="CHANNEL_ID" site_id="SITE_ID">CHANNEL_NAME</channel>
<channel site="SITE" lang="LANGUAGE_CODE" xmltv_id="CHANNEL_ID" site_id="SITE_ID">CHANNEL_NAME</channel>
```
| Attribute | Description | Example |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------- |
| LANGUAGE_CODE | Language of the guide ([ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) code). | `en` |
| CHANNEL_ID | Channel ID from [iptv-org/database](https://github.com/iptv-org/database). A complete list of supported channels can also be found at https://iptv-org.github.io/. | `BBCOne.uk` |
| SITE_ID | Unique ID of the channel used in the source. | `bbc1` |
| CHANNEL_NAME | Name of the channel used in the source. | `BBC 1` |
| Attribute | Description | Example |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------- |
| SITE | Site domain name. | `example.com` |
| LANGUAGE_CODE | Language of the guide ([ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) code). | `en` |
| CHANNEL_ID | Channel ID from [iptv-org/database](https://github.com/iptv-org/database). A complete list of supported channels can also be found at https://iptv-org.github.io/. | `BBCOne.uk` |
| SITE_ID | Unique ID of the channel used in the source. | `bbc1` |
| CHANNEL_NAME | Name of the channel used in the source. | `BBC 1` |
After that just commit all changes and send a pull request.
@ -31,13 +38,13 @@ This file describes what kind of request we need to send to get the guide for a
```js
module.exports = {
site: 'example.com',
url: function ({ channel, date }) {
return `https://example.com/api/${channel.site_id}/${date.format('YYYY-MM-DD')}`
},
parser: function ({ content }) {
return JSON.parse(content)
}
site: 'example.com',
url: function ({ channel, date }) {
return `https://example.com/api/${channel.site_id}/${date.format('YYYY-MM-DD')}`
},
parser: function ({ content }) {
return JSON.parse(content)
}
}
```
@ -61,26 +68,26 @@ const date = dayjs.utc('2022-11-18', 'YYYY-MM-DD').startOf('d')
const channel = { site_id: 'bbc1', xmltv_id: 'BBCOne.uk', lang: 'en' }
it('can generate valid url', () => {
expect(url({ channel, date })).toBe('https://example.com/api/bbc1/2022-11-18')
expect(url({ channel, date })).toBe('https://example.com/api/bbc1/2022-11-18')
})
it('can parse response', () => {
const content = `[{"start":"2022-11-18T01:30:00.000Z","stop":"2022-11-18T02:00:00.000Z","title":"Program 1"}]`
const results = parser({ content })
const content = `[{"start":"2022-11-18T01:30:00.000Z","stop":"2022-11-18T02:00:00.000Z","title":"Program 1"}]`
const results = parser({ content })
expect(results).toMatchObject([
{
start: '2022-11-18T01:30:00.000Z',
stop: '2022-11-18T02:00:00.000Z',
title: 'Program 1'
}
])
expect(results).toMatchObject([
{
start: '2022-11-18T01:30:00.000Z',
stop: '2022-11-18T02:00:00.000Z',
title: 'Program 1'
}
])
})
it('can handle empty guide', () => {
const results = parser({ content: '' })
const results = parser({ content: '' })
expect(results).toMatchObject([])
expect(results).toMatchObject([])
})
```
@ -102,11 +109,9 @@ This file contains a list of channels available at the source.
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<site site="example.com">
<channels>
<channel lang="en" xmltv_id="BBCOne.uk" site_id="bbc1">BBC 1</channel>
</channels>
</site>
<channels>
<channel site="example.com" lang="en" xmltv_id="BBCOne.uk" site_id="bbc1">BBC 1</channel>
</channels>
```
</details>
@ -114,9 +119,39 @@ This file contains a list of channels available at the source.
After creating all the files we can make sure that the guide loads correctly and has no errors using the command:
```sh
npx epg-grabber --config=sites/example.com/example.com.config.js --channels=sites/example.com/example.com.channels.xml --output=guide.xml --days=2
npm run grab -- --site=example.com
```
If the download is successful, the `guide.xml` file with the ready to use program should appear in the root directory.
After that, all that remains is to commit all the changes and send a pull request.
## Project Structure
- `.github/`
- `ISSUE_TEMPLATE/`: issue templates for the repository.
- `CODE_OF_CONDUCT.md`: rules you shouldn't break if you don't want to get banned.
- `scripts/`: contains all scripts used in the repository.
- `sites/`: contains configurations, channel lists and tests for all sites.
- `tests/`: contains tests to check the scripts.
- `CONTRIBUTING.md`: file you are currently reading.
- `README.md`: project description displayed on the home page.
- `SITES.md`: list of all supported sites and their current status.
## Scripts
These scripts are created to automate routine processes in the repository and make it a bit easier to maintain.
For scripts to work, you must have [Node.js](https://nodejs.org/en) installed on your computer.
To run scripts use the `npm run <script-name>` command.
- `api:load`: downloads the latest channels data from the [iptv-org/api](https://github.com/iptv-org/api).
- `channels:lint`: сhecks the channel lists for syntax errors.
- `channels:parse`: generates a list of channels based on the site configuration.
- `channels:editor`: utility for quick channels markup.
- `channels:validate`: checks the description of channels for errors.
- `grab`: downloads a program from a specified source.
- `serve`: starts the [web server](https://github.com/vercel/serve).
- `lint`: сhecks the scripts for syntax errors.
- `test`: runs a test of all the scripts described above.

108
README.md
View file

@ -1,99 +1,137 @@
# EPG
Utilities for downloading the EPG (Electronic Program Guide) for thousands of TV channels from hundreds of sources.
__IMPORTANT:__ We are no longer able to provide pre-made guides due to the disabling of GitHub Actions (Read more: https://github.com/orgs/iptv-org/discussions/12#discussioncomment-5219050). This repository now contains only utilities and configurations for downloading guides yourself.
Tools for downloading the EPG (Electronic Program Guide) for thousands of TV channels from hundreds of sources.
## Table of contents
- 🚀 [How to use?](#how-to-use)
- ✨ [Installation](#installation)
- 🚀 [Usage](#usage)
- 💫 [Update](#update)
- 📺 [Playlists](#playlists)
- 🗄 [Database](#database)
- 👨‍💻 [API](#api)
- 📚 [Resources](#resources)
- 💬 [Discussions](#discussions)
- 🛠 [Contribution](#contribution)
- © [License](#license)
- 📄 [License](#license)
## How to use?
## Installation
To download the guide from one of the supported sites you must have [Node.js](https://nodejs.org/en) installed on your computer first.
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.
You will also need to install [Git](https://git-scm.com/downloads) to follow these instructions.
After installing them, you need to 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:
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 also through the Console navigate to the just downloaded `epg` folder:
Then navigate to the downloaded `epg` folder:
```sh
cd epg
```
And install all the necessary dependencies:
And install all the dependencies:
```sh
npm install
```
Now choose one of the sources (their complete list can be found in the [/sites](https://github.com/iptv-org/epg/tree/master/sites) folder) and start downloading the guide using the command:
## Usage
To start the download of the guide, select one of the [supported sites](SITES.md) and paste its name into the command below:
```sh
npm run grab -- --site=example.com
```
To download a guide in a specific language pass its [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) code to the `--lang` argument:
And once the download is complete, the guide will be saved to the `guide.xml` file.
```sh
npm run grab -- --site=example.com --lang=fr
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 <code> Filter channels by language (ISO 639-2 code)
-t, --timeout <milliseconds> Override the default timeout for each request
--days <days> Override the number of days for which the program will be loaded
(defaults to the value from the site config)
--maxConnections <number> Limit on the number of concurrent requests (default: 1)
--cron <expression> Schedule a script run (example: "0 0 * * *")
--gzip Create a compressed version of the guide as well (default: false)
```
To override the number of days for which the program will be loaded use the `--days` argument (the default is the value specified in the site config):
### Access the guide by URL
You can make the guide available via URL by running your own server:
```sh
npm run grab -- --site=example.com --days=3
npm run serve
```
To also create a compressed version of the guide, add the `--gzip` flag:
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
```
### 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 --gzip
npm run grab -- --site=example.com --maxConnections=10
```
After the download is completed in the current directory will appear a new folder `guides`, which will store all XML files:
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
guides
└── fr
└── example.com.xml
└── example.com.xml.gz
npm run grab -- --channels=path/to/custom.channels.xml
```
### Run on schedule
If you want to download the guide automatically on a schedule, you need to pass a valid [cron expression](https://crontab.guru/) to the script using the `--cron` attribute:
```sh
npm run grab -- --site=example.com --cron="0 0 * * *"
```
Also you can make these guides available via URL by running your own server:
## 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
npm run serve
git pull
```
After that all the downloaded guides will be available at a link like this:
And then update all the dependencies:
```
http://localhost:3000/guides/fr/example.com.xml
```
In addition, they will be available on your local network at:
```
http://<your_local_ip_address>:3000/guides/fr/example.com.xml
```sh
npm install
```
## Playlists

188
SITES.md Normal file
View file

@ -0,0 +1,188 @@
# Sites
| Site | Status | Notes |
| -------------------------- | ------ | ---------------------------------------------------------------------------------------- |
| 9tv.co.il | 🟢 | |
| abc.net.au | 🟢 | |
| allente.se | 🟢 | |
| andorradifusio.ad | 🟢 | |
| arianaafgtv.com | 🟢 | |
| arianatelevision.com | 🟢 | |
| arirang.com | 🟢 | |
| artonline.tv | 🟢 | |
| astro.com.my | 🟢 | |
| bein.com | 🟢 | |
| beinsports.com | 🟡 | https://github.com/iptv-org/epg/issues/2160, https://github.com/iptv-org/epg/issues/2168 |
| berrymedia.co.kr | 🟢 | |
| bt.com | 🟢 | |
| cablego.com.pe | 🟢 | |
| cableplus.com.uy | 🟢 | |
| canalplus-caraibes.com | 🟢 | |
| canalplus-haiti.com | 🟢 | |
| canalplus-reunion.com | 🟢 | |
| canalplus.com | 🟢 | |
| cgates.lt | 🟢 | |
| chaines-tv.orange.fr | 🟢 | |
| clickthecity.com | 🟢 | |
| compulms.com | 🟢 | |
| comteco.com.bo | 🟢 | |
| cosmote.gr | 🟢 | |
| delta.nl | 🟢 | |
| digiturk.com.tr | 🟢 | |
| directv.com | 🟢 | |
| directv.com.ar | 🟢 | |
| directv.com.uy | 🟢 | |
| dishtv.in | 🟢 | |
| dsmart.com.tr | 🟢 | |
| dstv.com | 🟢 | |
| elcinema.com | 🟢 | |
| ena.skylifetv.co.kr | 🟢 | |
| entertainment.ie | 🟢 | |
| epg.i-cable.com | 🟢 | |
| firstmedia.com | 🟢 | |
| flixed.io | 🟢 | |
| foxsports.com.au | 🟢 | |
| foxtel.com.au | 🟢 | |
| frikanalen.no | 🟢 | |
| gatotv.com | 🟢 | |
| getafteritmedia.com | 🟢 | |
| guidatv.sky.it | 🟢 | |
| guide.dstv.com | 🟢 | |
| hd-plus.de | 🟡 | https://github.com/iptv-org/epg/issues/2173 |
| horizon.tv | 🟢 | |
| i.mjh.nz | 🟢 | |
| i24news.tv | 🟢 | |
| indihometv.com | 🟢 | |
| ionplustv.com | 🟢 | |
| ipko.com | 🟢 | |
| kan.org.il | 🟢 | |
| knr.gl | 🟢 | |
| kplus.vn | 🟢 | |
| kvf.fo | 🟢 | |
| m.tv.sms.cz | 🟢 | |
| magentatv.at | 🟡 | https://github.com/iptv-org/epg/issues/2133#issuecomment-1640598226 |
| magentatv.de | 🟡 | https://github.com/iptv-org/epg/issues/2133 |
| magticom.ge | 🟢 | |
| mako.co.il | 🟢 | |
| maxtv.hrvatskitelekom.hr | 🟢 | |
| maxtvgo.mk | 🟢 | |
| mbc.net | 🟢 | |
| mediagenie.co.kr | 🟢 | |
| mediaklikk.hu | 🟢 | |
| mediaset.it | 🟢 | |
| melita.com | 🟢 | |
| meo.pt | 🟢 | |
| mewatch.sg | 🟢 | |
| mi.tv | 🟢 | |
| mncvision.id | 🟢 | |
| moji.id | 🟢 | |
| mon-programme-tv.be | 🟢 | |
| movistarplus.es | 🟢 | |
| mtel.ba | 🟢 | |
| mts.rs | 🟢 | |
| mujtvprogram.cz | 🟢 | |
| musor.tv | 🟢 | |
| myafn.dodmedia.osd.mil | 🟢 | |
| mysky.com.ph | 🟢 | |
| mytvsuper.com | 🟢 | |
| nhk.or.jp | 🟢 | |
| nhkworldpremium.com | 🟢 | |
| nos.pt | 🟢 | |
| novacyprus.com | 🟢 | |
| novasports.gr | 🟢 | |
| nowplayer.now.com | 🟢 | |
| nuevosiglo.com.uy | 🟢 | |
| ontvtonight.com | 🟢 | |
| osn.com | 🟡 | https://github.com/iptv-org/epg/issues/2157 |
| pbsguam.org | 🟢 | |
| plex.tv | 🟢 | |
| programacion-tv.elpais.com | 🟢 | |
| programacion.tcc.com.uy | 🟢 | |
| programetv.ro | 🟢 | |
| programme-tv.net | 🟢 | |
| programme-tv.vini.pf | 🟢 | |
| programme.tvb.com | 🟢 | |
| programtv.onet.pl | 🟢 | |
| proximusmwc.be | 🟢 | |
| raiplay.it | 🟢 | |
| reportv.com.ar | 🟢 | |
| rev.bs | 🟢 | |
| rotana.net | 🟢 | |
| rtb.gov.bn | 🟢 | |
| rthk.hk | 🟢 | |
| rtmklik.rtm.gov.my | 🟢 | |
| rtp.pt | 🟢 | |
| ruv.is | 🟢 | |
| sat.tv | 🟢 | |
| siba.com.co | 🟢 | |
| singtel.com | 🟢 | |
| sjonvarp.is | 🟢 | |
| sky.co.nz | 🟢 | |
| sky.com | 🟢 | |
| sky.de | 🟢 | |
| sportsnet.ca | 🟢 | |
| starhubtvplus.com | 🟢 | |
| startimestv.com | 🟢 | |
| startv.com | 🟢 | |
| streamingtvguides.com | 🟢 | |
| superguidatv.it | 🟢 | |
| taiwanplus.com | 🟢 | |
| tapdmv.com | 🟢 | |
| telecablesat.fr | 🟢 | |
| telenet.tv | 🟢 | |
| teliatv.ee | 🟢 | |
| telkku.com | 🟢 | |
| telkussa.fi | 🟢 | |
| telsu.fi | 🟢 | |
| tivu.tv | 🟢 | |
| toonamiaftermath.com | 🟢 | |
| transvision.co.id | 🟢 | |
| turksatkablo.com.tr | 🟢 | |
| tv.blue.ch | 🟢 | |
| tv.cctv.com | 🟢 | |
| tv.dir.bg | 🟢 | |
| tv.lv | 🟢 | |
| tv.mail.ru | 🟢 | |
| tv.movistar.com.pe | 🟢 | |
| tv.nu | 🟢 | |
| tv.post.lu | 🟢 | |
| tv.trueid.net | 🟡 | https://github.com/iptv-org/epg/issues/2164 |
| tv.vera.com.uy | 🟢 | |
| tv.yandex.ru | 🟡 | https://github.com/iptv-org/epg/issues/2170 |
| tv.yettel.hu | 🟢 | |
| tv2go.t-2.net | 🟢 | |
| tv24.co.uk | 🟢 | |
| tv24.se | 🟢 | |
| tva.tv | 🟢 | |
| tvarenasport.com | 🟢 | |
| tvarenasport.hr | 🟢 | |
| tvcubana.icrt.cu | 🟢 | |
| tvgids.nl | 🟢 | |
| tvguide.com | 🟢 | |
| tvguide.myjcom.jp | 🟢 | |
| tvhebdo.com | 🟢 | |
| tvheute.at | 🟢 | |
| tvim.tv | 🟢 | |
| tving.com | 🟢 | |
| tvmi.mt | 🟢 | |
| tvmusor.hu | 🟢 | |
| tvpassport.com | 🟡 | https://github.com/iptv-org/epg/issues/2175 |
| tvplus.com.tr | 🟢 | |
| tvprofil.com | 🟢 | |
| tvtv.us | 🟡 | https://github.com/iptv-org/epg/issues/2176 |
| unifi.com.my | 🟢 | |
| vidio.com | 🟢 | |
| virginmedia.com | 🟢 | |
| virginmediatelevision.ie | 🟢 | |
| visionplus.id | 🟢 | |
| vivacom.bg | 🟢 | |
| vtm.be | 🟢 | |
| walesi.com.fj | 🟢 | |
| watchyour.tv | 🟢 | |
| wavve.com | 🟢 | |
| worldfishingnetwork.com | 🟢 | |
| xumo.tv | 🟢 | |
| zap.co.ao | 🟢 | |
| ziggogo.tv | 🟢 | |
| znbc.co.zm | 🟢 | |
| zuragt.mn | 🟢 | |

4137
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,51 +1,59 @@
{
"name": "epg",
"scripts": {
"channels:validate": "node scripts/commands/channels/validate.js",
"channels:lint": "node scripts/commands/channels/lint.js",
"channels:parse": "node scripts/commands/channels/parse.js",
"channels:editor": "node scripts/commands/channels/editor.js",
"api:load": "./scripts/commands/api/load.sh",
"lint": "npx eslint ./scripts/**/*.js",
"test": "TZ=Pacific/Nauru npx jest --runInBand",
"test:commands": "npx jest --runInBand -- commands",
"test:sites": "TZ=Pacific/Nauru npx jest --runInBand -- sites",
"check": "npm run api:load && npm run channels:lint sites/**/*.js && npm run channels:validate sites/**/*.xml",
"grab": "node scripts/commands/epg/grab.js",
"api:load": "ts-node scripts/commands/api/load.ts",
"channels:lint": "ts-node scripts/commands/channels/lint.ts",
"channels:parse": "ts-node scripts/commands/channels/parse.ts",
"channels:editor": "ts-node scripts/commands/channels/editor.ts",
"channels:validate": "ts-node scripts/commands/channels/validate.ts",
"grab": "ts-node scripts/commands/epg/grab.ts",
"serve": "npx serve",
"lint": "npx eslint ./scripts/**/*.ts ./sites/**/*.js ./tests/**/*.ts ./tests/**/*.js",
"test": "TZ=Pacific/Nauru npx jest --runInBand",
"postinstall": "npm run api:load"
},
"private": true,
"author": "Arhey",
"license": "MIT",
"jest": {
"modulePathIgnorePatterns": [
"<rootDir>/.jenkins/"
],
"testRegex": "(sites|tests)/(.*?/)?.*test.js$",
"setupFilesAfterEnv": [
"@alex_neo/jest-expect-message"
]
"transform": {
"^.+\\.(ts|js)$": "ts-jest"
},
"testRegex": "(tests|sites)/(.*?/)?.*test.(js|ts)$"
},
"dependencies": {
"@alex_neo/jest-expect-message": "^1.0.5",
"@freearhey/core": "^0.2.1",
"@octokit/core": "^4.1.0",
"axios": "^0.21.1",
"@types/cli-progress": "^3.11.3",
"@types/fs-extra": "^11.0.2",
"@types/inquirer": "^9.0.3",
"@types/jest": "^29.5.5",
"@types/lodash": "^4.14.199",
"@types/node-cleanup": "^2.1.2",
"@types/numeral": "^2.0.3",
"@typescript-eslint/eslint-plugin": "^6.7.3",
"axios": "^1.5.1",
"axios-cookiejar-support": "^4.0.7",
"chalk": "^4.1.2",
"cheerio": "^1.0.0-rc.10",
"cli-progress": "^3.12.0",
"commander": "^8.2.0",
"consola": "^3.2.3",
"cron": "^2.3.1",
"cron": "^2.4.3",
"csv-parser": "^3.0.0",
"cwait": "^1.1.2",
"dayjs": "^1.11.7",
"epg-grabber": "^0.32.0",
"epg-grabber": "^0.34.0",
"epg-parser": "^0.2.0",
"eslint": "^8.17.0",
"eslint-config-prettier": "^9.0.0",
"form-data": "^4.0.0",
"fs-extra": "^10.0.1",
"glob": "^7.2.0",
"iconv-lite": "^0.4.24",
"inquirer": "^8.2.0",
"jest": "^29.5.0",
"inquirer": "^8.2.6",
"jest": "^29.7.0",
"langs": "^2.0.0",
"libxmljs2": "^0.32.0",
"lodash": "^4.17.21",
@ -55,6 +63,7 @@
"nedb-promises": "^6.0.3",
"node-cleanup": "^2.1.2",
"node-gzip": "^1.1.2",
"numeral": "^2.0.6",
"parse-duration": "^1.0.0",
"pdf-parse": "^1.1.1",
"serve": "^14.2.0",
@ -62,11 +71,11 @@
"srcset": "^4.0.0",
"table2array": "^0.0.2",
"tabletojson": "^2.0.7",
"tough-cookie": "^4.1.3",
"transliteration": "^2.2.0",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
"unzipit": "^1.4.0",
"wildcard-match": "^5.1.2"
},
"devDependencies": {
"eslint": "^8.17.0"
}
}

View file

@ -1,7 +0,0 @@
#!/bin/bash
mkdir -p scripts/tmp/data
curl -L -o scripts/tmp/data/channels.json https://iptv-org.github.io/api/channels.json
curl -L -o scripts/tmp/data/countries.json https://iptv-org.github.io/api/countries.json
curl -L -o scripts/tmp/data/regions.json https://iptv-org.github.io/api/regions.json
curl -L -o scripts/tmp/data/subdivisions.json https://iptv-org.github.io/api/subdivisions.json

View file

@ -0,0 +1,18 @@
import { Logger } from '@freearhey/core'
import { ApiClient } from '../../core'
async function main() {
const logger = new Logger()
const client = new ApiClient({ logger })
const requests = [
client.download('channels.json'),
client.download('countries.json'),
client.download('regions.json'),
client.download('subdivisions.json')
]
await Promise.all(requests)
}
main()

1
scripts/commands/channels/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/replace.ts

View file

@ -1,160 +0,0 @@
const { api, parser, xml, file, logger } = require('../../core')
const { transliterate } = require('transliteration')
const nodeCleanup = require('node-cleanup')
const { program } = require('commander')
const inquirer = require('inquirer')
program
.argument('<filepath>', 'Path to *.channels.xml file to edit')
.option('-c, --country <name>', 'Source country', 'us')
.parse(process.argv)
const filepath = program.args[0]
const options = program.opts()
const defaultCountry = options.country
const newLabel = ` [new]`
let site
let channels = []
async function main() {
if (!(await file.exists(filepath))) {
throw new Error(`File "${filepath}" does not exists`)
return
}
let result = await parser.parseChannels(filepath)
site = result.site
channels = result.channels
channels = channels.map(c => {
c.xmltv_id = c.xmltv_id
return c
})
await api.channels.load()
const buffer = []
for (const channel of channels) {
if (channel.xmltv_id) {
if (channel.xmltv_id !== '-') {
buffer.push(`${channel.xmltv_id}/${channel.lang}`)
}
continue
}
let choices = await getOptions(channel)
const question = {
name: 'option',
message: `Choose an option:`,
type: 'list',
choices,
pageSize: 10
}
await inquirer.prompt(question).then(async selected => {
switch (selected.option) {
case 'Overwrite':
const input = await getInput(channel)
channel.xmltv_id = input.xmltv_id
break
case 'Skip':
channel.xmltv_id = '-'
break
default:
const [name, xmltv_id] = selected.option
.replace(/ \[.*\]/, '')
.split('|')
.map(i => i.trim().replace(newLabel, ''))
channel.xmltv_id = xmltv_id
break
}
const found = buffer.includes(`${channel.xmltv_id}/${channel.lang}`)
if (found) {
const question = {
name: 'option',
message: `"${channel.xmltv_id}" already on the list. Choose an option:`,
type: 'list',
choices: ['Skip', 'Add', 'Delete'],
pageSize: 5
}
await inquirer.prompt(question).then(async selected => {
switch (selected.option) {
case 'Skip':
channel.xmltv_id = '-'
break
case 'Delete':
channel.delete = true
break
default:
break
}
})
} else {
if (channel.xmltv_id !== '-') {
buffer.push(`${channel.xmltv_id}/${channel.lang}`)
}
}
})
}
}
main()
function save() {
if (!file.existsSync(filepath)) return
channels = channels.filter(c => !c.delete)
const output = xml.create(channels, site)
file.writeSync(filepath, output)
logger.info(`\nFile '${filepath}' successfully saved`)
}
nodeCleanup(() => {
save()
})
async function getInput(channel) {
const name = channel.name.trim()
const input = await inquirer.prompt([
{
name: 'xmltv_id',
message: ' ID:',
type: 'input',
default: generateCode(name, defaultCountry)
}
])
return { name, xmltv_id: input['xmltv_id'] }
}
async function getOptions(channel) {
const channels = await api.channels.all()
const channelId = generateCode(channel.name, defaultCountry)
const similar = await getSimilar(channels, channelId)
let variants = []
variants.push(`${channel.name.trim()} | ${channelId}${newLabel}`)
similar.forEach(i => {
let alt_names = i.alt_names.length ? ` (${i.alt_names.join(',')})` : ''
let closed = i.closed ? `[closed:${i.closed}]` : ``
let replaced_by = i.replaced_by ? `[replaced_by:${i.replaced_by}]` : ''
variants.push(`${i.name}${alt_names} | ${i.id} ${closed}${replaced_by}[api]`)
})
variants.push(`Overwrite`)
variants.push(`Skip`)
return variants
}
async function getSimilar(list, channelId) {
const normChannelId = channelId.split('.')[0].slice(0, 8).toLowerCase()
return list.filter(i => i.id.split('.')[0].toLowerCase().startsWith(normChannelId))
}
function generateCode(name, country) {
const id = transliterate(name)
.replace(/\+/gi, 'Plus')
.replace(/^\&/gi, 'And')
.replace(/[^a-z\d]+/gi, '')
return `${id}.${country}`
}

View file

@ -0,0 +1,181 @@
import { DATA_DIR } from '../../constants'
import { Storage, Collection, Dictionary, Logger } from '@freearhey/core'
import { ChannelsParser, XML, ApiChannel } from '../../core'
import { Channel } from 'epg-grabber'
import { transliterate } from 'transliteration'
import nodeCleanup from 'node-cleanup'
import { program } from 'commander'
import inquirer, { QuestionCollection } from 'inquirer'
program
.argument('<filepath>', 'Path to *.channels.xml file to edit')
.option('-c, --country <name>', 'Default country (ISO 3166 code)', 'US')
.parse(process.argv)
const filepath = program.args[0]
const programOptions = program.opts()
const defaultCountry = programOptions.country.toLowerCase()
const newLabel = ` [new]`
let site: string
let options = new Collection()
async function main() {
const storage = new Storage()
if (!(await storage.exists(filepath))) {
throw new Error(`File "${filepath}" does not exists`)
}
const parser = new ChannelsParser({ storage })
const parsedChannels = await parser.parse(filepath)
options = parsedChannels.map((channel: Channel) => {
return {
channel,
delete: false
}
})
const dataStorage = new Storage(DATA_DIR)
const channelsContent = await dataStorage.json('channels.json')
const channels = new Collection(channelsContent).map(data => new ApiChannel(data))
const buffer = new Dictionary()
options.forEach(async (option: { channel: Channel; delete: boolean }) => {
const channel = option.channel
if (channel.xmltv_id) {
if (channel.xmltv_id !== '-') {
buffer.set(`${channel.xmltv_id}/${channel.lang}`, true)
}
return
}
let choices = getOptions(channels, channel)
const question: QuestionCollection = {
name: 'option',
message: `Choose an option:`,
type: 'list',
choices,
pageSize: 10
}
await inquirer.prompt(question).then(async selected => {
switch (selected.option) {
case 'Overwrite':
const input = await getInput(channel)
channel.xmltv_id = input.xmltv_id
break
case 'Skip':
channel.xmltv_id = '-'
break
default:
const [, xmltv_id] = selected.option
.replace(/ \[.*\]/, '')
.split('|')
.map((i: string) => i.trim().replace(newLabel, ''))
channel.xmltv_id = xmltv_id
break
}
const found = buffer.has(`${channel.xmltv_id}/${channel.lang}`)
if (found) {
const question: QuestionCollection = {
name: 'option',
message: `"${channel.xmltv_id}" already on the list. Choose an option:`,
type: 'list',
choices: ['Skip', 'Add', 'Delete'],
pageSize: 5
}
await inquirer.prompt(question).then(async selected => {
switch (selected.option) {
case 'Skip':
channel.xmltv_id = '-'
break
case 'Delete':
option.delete = true
break
default:
break
}
})
} else {
if (channel.xmltv_id !== '-') {
buffer.set(`${channel.xmltv_id}/${channel.lang}`, true)
}
}
})
})
}
main()
function save() {
const logger = new Logger()
const storage = new Storage()
if (!storage.existsSync(filepath)) return
const channels = options
.filter((option: { channel: Channel; delete: boolean }) => !option.delete)
.map((option: { channel: Channel; delete: boolean }) => option.channel)
const xml = new XML(channels, site)
storage.saveSync(filepath, xml.toString())
logger.info(`\nFile '${filepath}' successfully saved`)
}
nodeCleanup(() => {
save()
})
async function getInput(channel: Channel) {
const name = channel.name.trim()
const input = await inquirer.prompt([
{
name: 'xmltv_id',
message: ' ID:',
type: 'input',
default: generateCode(name, defaultCountry)
}
])
return { name, xmltv_id: input['xmltv_id'] }
}
function getOptions(channels: Collection, channel: Channel) {
const channelId = generateCode(channel.name, defaultCountry)
const similar = getSimilar(channels, channelId)
const variants = new Collection()
variants.add(`${channel.name.trim()} | ${channelId}${newLabel}`)
similar.forEach((_channel: ApiChannel) => {
const altNames = _channel.altNames.notEmpty() ? ` (${_channel.altNames.join(',')})` : ''
const closed = _channel.closed ? `[closed:${_channel.closed}]` : ``
const replacedBy = _channel.replacedBy ? `[replaced_by:${_channel.replacedBy}]` : ''
variants.add(`${_channel.name}${altNames} | ${_channel.id} ${closed}${replacedBy}[api]`)
})
variants.add(`Overwrite`)
variants.add(`Skip`)
return variants.all()
}
function getSimilar(channels: Collection, channelId: string) {
const normChannelId = channelId.split('.')[0].slice(0, 8).toLowerCase()
return channels.filter((channel: ApiChannel) =>
channel.id.split('.')[0].toLowerCase().startsWith(normChannelId)
)
}
function generateCode(name: string, country: string) {
const channelId: string = transliterate(name)
.replace(/\+/gi, 'Plus')
.replace(/^\&/gi, 'And')
.replace(/[^a-z\d]+/gi, '')
return `${channelId}.${country}`
}

View file

@ -1,18 +1,10 @@
const chalk = require('chalk')
const libxml = require('libxmljs2')
const { program } = require('commander')
const { logger, file } = require('../../core')
import chalk from 'chalk'
import libxml, { ValidationError } from 'libxmljs2'
import { program } from 'commander'
import { Logger, Storage, File } from '@freearhey/core'
const xsd = `<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="site">
<xs:complexType>
<xs:sequence>
<xs:element ref="channels"/>
</xs:sequence>
<xs:attribute name="site" use="required" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="channels">
<xs:complexType>
<xs:sequence>
@ -22,43 +14,53 @@ const xsd = `<?xml version="1.0" encoding="UTF-8"?>
</xs:element>
<xs:element name="channel">
<xs:complexType mixed="true">
<xs:attribute name="site" use="required" type="xs:string"/>
<xs:attribute name="lang" use="required" type="xs:string"/>
<xs:attribute name="site_id" use="required" type="xs:string"/>
<xs:attribute name="xmltv_id" use="required" type="xs:string"/>
<xs:attribute name="logo" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:schema>`
program.argument('<filepath>', 'Path to file to validate').parse(process.argv)
program
.option(
'-c, --channels <path>',
'Path to channels.xml file to validate',
'sites/**/*.channels.xml'
)
.parse(process.argv)
const options = program.opts()
async function main() {
if (!program.args.length) {
logger.error('required argument "filepath" not specified')
}
const logger = new Logger()
const storage = new Storage()
let errors = []
logger.info('options:')
logger.tree(options)
for (const filepath of program.args) {
if (!filepath.endsWith('.xml')) continue
let errors: ValidationError[] = []
const xml = await file.read(filepath)
let files: string[] = await storage.list(options.channels)
for (const filepath of files) {
const file = new File(filepath)
if (file.extension() !== 'xml') continue
let localErrors = []
const xml = await storage.load(filepath)
try {
const xsdDoc = libxml.parseXml(xsd)
const doc = libxml.parseXml(xml)
let localErrors: ValidationError[] = []
if (!doc.validate(xsdDoc)) {
localErrors = doc.validationErrors
}
} catch (error) {
localErrors.push(error)
const xsdDoc = libxml.parseXml(xsd)
const doc = libxml.parseXml(xml)
if (!doc.validate(xsdDoc)) {
localErrors = doc.validationErrors
}
if (localErrors.length) {
console.log(`\n${chalk.underline(filepath)}`)
localErrors.forEach(error => {
localErrors.forEach((error: ValidationError) => {
const position = `${error.line}:${error.column}`
console.log(` ${chalk.gray(position.padEnd(4, ' '))} ${error.message.trim()}`)
})

View file

@ -1,65 +0,0 @@
const { logger, file, xml, parser } = require('../../core')
const { Command } = require('commander')
const path = require('path')
const _ = require('lodash')
const program = new Command()
program
.requiredOption('-c, --config <config>', 'Config file')
.option('-s, --set [args...]', 'Set custom arguments', [])
.option('-o, --output <output>', 'Output file')
.option('--clean', 'Delete the previous *.channels.xml if exists')
.parse(process.argv)
const options = program.opts()
async function main() {
const config = require(path.resolve(options.config))
const dir = file.dirname(options.config)
const outputFilepath = options.output || `${dir}/${config.site}.channels.xml`
let channels = []
if (!options.clean && (await file.exists(outputFilepath))) {
let result = await parser.parseChannels(outputFilepath)
channels = result.channels
}
const args = {}
options.set.forEach(arg => {
const [key, value] = arg.split(':')
args[key] = value
})
let parsedChannels = config.channels(args)
if (isPromise(parsedChannels)) {
parsedChannels = await parsedChannels
}
parsedChannels = parsedChannels.map(c => {
c.lang = c.lang || 'en'
return c
})
channels = channels.concat(parsedChannels)
channels = _.uniqBy(channels, c => c.site_id + c.lang)
channels = _.sortBy(channels, [
'lang',
c => (c.xmltv_id ? c.xmltv_id.toLowerCase() : '_'),
'site_id'
])
const output = xml.create(channels, config.site)
await file.write(outputFilepath, output)
logger.info(`File '${outputFilepath}' successfully saved`)
}
main()
function isPromise(promise) {
return !!promise && typeof promise.then === 'function'
}

View file

@ -0,0 +1,76 @@
import { Logger, File, Collection, Storage } from '@freearhey/core'
import { ChannelsParser, XML } from '../../core'
import { Channel } from 'epg-grabber'
import { Command, OptionValues } from 'commander'
import path from 'path'
const program = new Command()
program
.requiredOption('-c, --config <config>', 'Config file')
.option('-s, --set [args...]', 'Set custom arguments')
.option('-o, --output <output>', 'Output file')
.option('--clean', 'Delete the previous *.channels.xml if exists')
.parse(process.argv)
type ParseOptions = {
config: string
set?: string
output?: string
clean?: boolean
}
const options: ParseOptions = program.opts()
async function main() {
const storage = new Storage()
const parser = new ChannelsParser({ storage })
const logger = new Logger()
const file = new File(options.config)
const dir = file.dirname()
const config = require(path.resolve(options.config))
const outputFilepath = options.output || `${dir}/${config.site}.channels.xml`
let channels = new Collection()
if (!options.clean && (await storage.exists(outputFilepath))) {
channels = await parser.parse(outputFilepath)
}
const args: {
[key: string]: any
} = {}
if (Array.isArray(options.set)) {
options.set.forEach((arg: string) => {
const [key, value] = arg.split(':')
args[key] = value
})
}
let parsedChannels = config.channels(args)
if (isPromise(parsedChannels)) {
parsedChannels = await parsedChannels
}
channels = channels
.mergeBy(
new Collection(parsedChannels),
(channel: Channel) => channel.site_id.toString() + channel.lang
)
.orderBy([
(channel: Channel) => channel.lang,
(channel: Channel) => (channel.xmltv_id ? channel.xmltv_id.toLowerCase() : '_'),
(channel: Channel) => channel.site_id
])
const xml = new XML(channels, config.site)
await storage.save(outputFilepath, xml.toString())
logger.info(`File '${outputFilepath}' successfully saved`)
}
main()
function isPromise(promise: any) {
return !!promise && typeof promise.then === 'function'
}

View file

@ -1,68 +0,0 @@
const { parser, logger, api } = require('../../core')
const { program } = require('commander')
const chalk = require('chalk')
const langs = require('langs')
program.argument('<filepath>', 'Path to file to validate').parse(process.argv)
async function main() {
await api.channels.load()
const stats = {
files: 0,
errors: 0
}
if (!program.args.length) {
logger.error('required argument "filepath" not specified')
}
for (const filepath of program.args) {
if (!filepath.endsWith('.xml')) continue
const { site, channels } = await parser.parseChannels(filepath)
const bufferById = {}
const bufferBySiteId = {}
const errors = []
for (const channel of channels) {
if (!bufferById[channel.xmltv_id + channel.lang]) {
bufferById[channel.xmltv_id + channel.lang] = channel
} else {
errors.push({ type: 'duplicate', ...channel })
stats.errors++
}
if (!bufferBySiteId[channel.site_id + channel.lang]) {
bufferBySiteId[channel.site_id + channel.lang] = channel
} else {
errors.push({ type: 'duplicate', ...channel })
stats.errors++
}
if (!api.channels.find({ id: channel.xmltv_id })) {
errors.push({ type: 'wrong_xmltv_id', ...channel })
stats.errors++
}
if (!langs.where('1', channel.lang)) {
errors.push({ type: 'wrong_lang', ...channel })
stats.errors++
}
}
if (errors.length) {
console.log(chalk.underline(filepath))
console.table(errors, ['type', 'lang', 'xmltv_id', 'site_id', 'name'])
console.log()
stats.files++
}
}
if (stats.errors > 0) {
console.log(chalk.red(`${stats.errors} error(s) in ${stats.files} file(s)`))
process.exit(1)
}
}
main()

View file

@ -0,0 +1,95 @@
import { Storage, Collection, Dictionary, File, Logger } from '@freearhey/core'
import { ChannelsParser, ApiChannel } from '../../core'
import { program } from 'commander'
import chalk from 'chalk'
import langs from 'langs'
import { DATA_DIR } from '../../constants'
import { Channel } from 'epg-grabber'
program
.option(
'-c, --channels <path>',
'Path to channels.xml file to validate',
'sites/**/*.channels.xml'
)
.parse(process.argv)
const options = program.opts()
type ValidationError = {
type: 'duplicate' | 'wrong_xmltv_id' | 'wrong_lang'
name: string
lang?: string
xmltv_id?: string
site_id?: string
logo?: string
}
async function main() {
const logger = new Logger()
logger.info('options:')
logger.tree(options)
const parser = new ChannelsParser({ storage: new Storage() })
const dataStorage = new Storage(DATA_DIR)
const channelsContent = await dataStorage.json('channels.json')
const channels = new Collection(channelsContent).map(data => new ApiChannel(data))
let totalFiles = 0
let totalErrors = 0
const storage = new Storage()
let files: string[] = await storage.list(options.channels)
for (const filepath of files) {
const file = new File(filepath)
if (file.extension() !== 'xml') continue
const parsedChannels = await parser.parse(filepath)
const bufferById = new Dictionary()
const bufferBySiteId = new Dictionary()
const errors: ValidationError[] = []
parsedChannels.forEach((channel: Channel) => {
const bufferId: string = `${channel.xmltv_id}:${channel.lang}`
if (bufferById.missing(bufferId)) {
bufferById.set(bufferId, true)
} else {
errors.push({ type: 'duplicate', ...channel })
totalErrors++
}
const bufferSiteId: string = `${channel.site_id}:${channel.lang}`
if (bufferBySiteId.missing(bufferSiteId)) {
bufferBySiteId.set(bufferSiteId, true)
} else {
errors.push({ type: 'duplicate', ...channel })
totalErrors++
}
if (channels.missing((_channel: ApiChannel) => _channel.id === channel.xmltv_id)) {
errors.push({ type: 'wrong_xmltv_id', ...channel })
totalErrors++
}
if (!langs.where('1', channel.lang)) {
errors.push({ type: 'wrong_lang', ...channel })
totalErrors++
}
})
if (errors.length) {
console.log(chalk.underline(filepath))
console.table(errors, ['type', 'lang', 'xmltv_id', 'site_id', 'name'])
console.log()
totalFiles++
}
}
if (totalErrors > 0) {
console.log(chalk.red(`${totalErrors} error(s) in ${totalFiles} file(s)`))
process.exit(1)
}
}
main()

View file

@ -1,220 +0,0 @@
const { program } = require('commander')
const _ = require('lodash')
const { EPGGrabber, generateXMLTV, Channel, Program } = require('epg-grabber')
const { db, logger, date, timer, file, parser, api, zip } = require('../../core')
const path = require('path')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const CronJob = require('cron').CronJob
dayjs.extend(utc)
const BASE_DIR = process.env.BASE_DIR || '.'
const CURR_DATE = process.env.CURR_DATE || new Date()
program
.requiredOption('-s, --site <name>', 'Name of the site to parse')
.option('-l, --lang <code>', 'Filter channels by language (ISO 639-2 code)')
.option('-o, --output <path>', 'Path to output file')
.option('--days <days>', 'Override the number of days for which the program will be loaded')
.option('--cron <expression>', 'Schedule a script run')
.option('--gzip', 'Create a compressed version of the guide as well', false)
.parse(process.argv)
const options = program.opts()
options.output = options.output || file.resolve(`${BASE_DIR}/guides/{lang}/{site}.xml`)
options.config = file.resolve(`${BASE_DIR}/sites/${options.site}/${options.site}.config.js`)
options.channels = file.resolve(`${BASE_DIR}/sites/${options.site}/${options.site}*.channels.xml`)
let channels = []
let programs = []
let runIndex = 0
async function main() {
logger.start('staring...')
logger.info('settings:')
for (let prop in options) {
logger.info(` ${prop}: ${options[prop]}`)
}
const config = await loadConfig(options.config)
const queue = await createQueue(options.channels, config)
const outputPath = options.output
if (options.cron) {
const job = new CronJob(options.cron, function () {
runJob(config, queue, outputPath)
})
job.start()
} else {
await runJob(config, queue, outputPath)
}
}
async function loadConfig(configPath) {
let config = require(file.resolve(configPath))
config = _.merge(config, {})
config.days = config.days || 1
logger.info('config:')
logConfig(config)
return config
}
function logConfig(config, level = 1) {
let padLeft = ' '.repeat(level)
for (let prop in config) {
if (typeof config[prop] === 'string' || typeof config[prop] === 'number') {
logger.info(`${padLeft}${prop}: ${config[prop]}`)
} else if (typeof config[prop] === 'object') {
level++
logger.info(`${padLeft}${prop}:`)
logConfig(config[prop], level)
}
}
}
async function runJob(config, queue, outputPath) {
runIndex++
logger.info(`run #${runIndex}:`)
timer.start()
await grab(queue, config)
await save(outputPath, channels, programs)
logger.success(` done in ${timer.format('HH[h] mm[m] ss[s]')}`)
}
async function grab(queue, config) {
const grabber = new EPGGrabber(config)
const total = queue.length
let i = 1
for (const item of queue) {
let channel = item.channel
let date = item.date
channels.push(item.channel)
await grabber
.grab(channel, date, (data, err) => {
logger.info(
` [${i}/${total}] ${channel.site} (${channel.lang}) - ${channel.xmltv_id} - ${dayjs
.utc(data.date)
.format('MMM D, YYYY')} (${data.programs.length} programs)`
)
if (i < total) i++
if (err) {
logger.info(` ERR: ${err.message}`)
}
})
.then(results => {
programs = programs.concat(results)
})
}
}
async function createQueue(channelsPath, config) {
logger.info('creating queue...')
let queue = {}
await api.channels.load().catch(logger.error)
const files = await file.list(channelsPath).catch(logger.error)
const utcDate = date.getUTC(CURR_DATE)
const days = options.days ? parseInt(options.days) : config.days
for (const filepath of files) {
logger.info(` loading "${filepath}"...`)
try {
const dir = file.dirname(filepath)
const { channels } = await parser.parseChannels(filepath)
const filename = file.basename(filepath)
const dates = Array.from({ length: days }, (_, i) => utcDate.add(i, 'd'))
for (const channel of channels) {
if (!channel.site || !channel.xmltv_id) continue
if (options.lang && channel.lang !== options.lang) continue
const found = api.channels.find({ id: channel.xmltv_id })
if (found) {
channel.logo = found.logo
}
for (const d of dates) {
const dateString = d.toJSON()
const key = `${channel.site}:${channel.lang}:${channel.xmltv_id}:${dateString}`
if (!queue[key]) {
queue[key] = {
channel,
date: dateString,
config,
error: null
}
}
}
}
} catch (err) {
logger.error(err)
continue
}
}
queue = Object.values(queue)
logger.info(` added ${queue.length} items`)
return queue
}
async function save(template, parsedChannels, programs = []) {
const variables = file.templateVariables(template)
const groups = _.groupBy(parsedChannels, channel => {
let groupId = ''
for (let key in channel) {
if (variables.includes(key)) {
groupId += channel[key]
}
}
return groupId
})
for (let groupId in groups) {
const channels = groups[groupId]
let output = {
channels,
programs: [],
date: CURR_DATE
}
for (let program of programs) {
let programLang = program.titles[0].lang
let channel = channels.find(c => c.xmltv_id === program.channel && c.lang === programLang)
if (!channel) continue
output.programs.push(new Program(program, channel))
}
output.channels = _.sortBy(output.channels, 'xmltv_id')
output.channels = _.uniqBy(output.channels, 'xmltv_id')
output.programs = _.sortBy(output.programs, ['channel', 'start'])
output.programs = _.uniqBy(output.programs, p => p.channel + p.start)
const outputPath = file.templateFormat(template, output.channels[0])
const xmlFilepath = outputPath
const xmltv = generateXMLTV(output)
logger.info(` saving to "${xmlFilepath}"...`)
await file.create(xmlFilepath, xmltv)
if (options.gzip) {
const gzFilepath = `${outputPath}.gz`
const compressed = await zip.compress(xmltv)
logger.info(` saving to "${gzFilepath}"...`)
await file.create(gzFilepath, compressed)
}
}
}
main()

View file

@ -0,0 +1,115 @@
import { Logger, Timer, Storage, Collection } from '@freearhey/core'
import { program } from 'commander'
import { CronJob } from 'cron'
import { Queue, Job, ChannelsParser } from '../../core'
import { Channel } from 'epg-grabber'
import path from 'path'
import { SITES_DIR } from '../../constants'
program
.option('-s, --site <name>', 'Name of the site to parse')
.option(
'-c, --channels <path>',
'Path to *.channels.xml file (required if the "--site" attribute is not specified)'
)
.option('-o, --output <path>', 'Path to output file', 'guide.xml')
.option('-l, --lang <code>', 'Filter channels by language (ISO 639-2 code)')
.option('-t, --timeout <milliseconds>', 'Override the default timeout for each request')
.option(
'--days <days>',
'Override the number of days for which the program will be loaded (defaults to the value from the site config)',
value => parseInt(value)
)
.option(
'--maxConnections <number>',
'Limit on the number of concurrent requests',
value => parseInt(value),
1
)
.option('--cron <expression>', 'Schedule a script run (example: "0 0 * * *")')
.option('--gzip', 'Create a compressed version of the guide as well', false)
.parse(process.argv)
export type GrabOptions = {
site?: string
channels?: string
output: string
gzip: boolean
maxConnections: number
timeout?: string
lang?: string
days?: number
cron?: string
}
const options: GrabOptions = program.opts()
async function main() {
if (!options.site && !options.channels)
throw new Error('One of the arguments must be presented: `--site` or `--channels`')
const logger = new Logger()
logger.start('staring...')
logger.info('config:')
logger.tree(options)
logger.info(`loading channels...`)
const storage = new Storage()
const parser = new ChannelsParser({ storage })
let files: string[] = []
if (options.site) {
files = await storage.list(path.join(SITES_DIR, `${options.site}/*.channels.xml`))
} else if (options.channels) {
files = await storage.list(options.channels)
}
let parsedChannels = new Collection()
for (let filepath of files) {
parsedChannels = parsedChannels.concat(await parser.parse(filepath))
}
if (options.lang) {
parsedChannels = parsedChannels.filter((channel: Channel) => channel.lang === options.lang)
}
logger.info(` found ${parsedChannels.count()} channels`)
logger.info('creating queue...')
const queue = new Queue({
parsedChannels,
logger,
options
})
await queue.create()
logger.info(` added ${queue.size()} items`)
const job = new Job({
queue,
logger,
options
})
let runIndex = 1
if (options.cron) {
const cronJob = new CronJob(options.cron, async () => {
logger.info(`run #${runIndex}:`)
const timer = new Timer()
timer.start()
await job.run()
runIndex++
logger.success(` done in ${timer.format('HH[h] mm[m] ss[s]')}`)
})
cronJob.start()
} else {
logger.info(`run #${runIndex}:`)
const timer = new Timer()
timer.start()
await job.run()
logger.success(` done in ${timer.format('HH[h] mm[m] ss[s]')}`)
}
logger.info('finished')
}
main()

4
scripts/constants.ts Normal file
View file

@ -0,0 +1,4 @@
export const SITES_DIR = process.env.SITES_DIR || './sites'
export const GUIDES_DIR = process.env.GUIDES_DIR || './guides'
export const DATA_DIR = process.env.DATA_DIR || './temp/data'
export const CURR_DATE = process.env.CURR_DATE || new Date().toISOString()

View file

@ -1,32 +0,0 @@
const _ = require('lodash')
const file = require('./file')
const DATA_DIR = process.env.DATA_DIR || './scripts/tmp/data'
class API {
constructor(filepath) {
this.filepath = file.resolve(filepath)
}
async load() {
const data = await file.read(this.filepath)
this.collection = JSON.parse(data)
}
find(query) {
return _.find(this.collection, query)
}
all() {
return this.collection
}
}
const api = {}
api.channels = new API(`${DATA_DIR}/channels.json`)
api.regions = new API(`${DATA_DIR}/regions.json`)
api.countries = new API(`${DATA_DIR}/countries.json`)
api.subdivisions = new API(`${DATA_DIR}/subdivisions.json`)
module.exports = api

View file

@ -0,0 +1,79 @@
import { Collection } from '@freearhey/core'
type ApiChannelProps = {
id: string
name: string
alt_names: string[]
network: string
owners: string[]
country: string
subdivision: string
city: string
broadcast_area: string[]
languages: string[]
categories: string[]
is_nsfw: boolean
launched: string
closed: string
replaced_by: string
website: string
logo: string
}
export class ApiChannel {
id: string
name: string
altNames: Collection
network: string
owners: Collection
country: string
subdivision: string
city: string
broadcastArea: Collection
languages: Collection
categories: Collection
isNSFW: boolean
launched: string
closed: string
replacedBy: string
website: string
logo: string
constructor({
id,
name,
alt_names,
network,
owners,
country,
subdivision,
city,
broadcast_area,
languages,
categories,
is_nsfw,
launched,
closed,
replaced_by,
website,
logo
}: ApiChannelProps) {
this.id = id
this.name = name
this.altNames = new Collection(alt_names)
this.network = network
this.owners = new Collection(owners)
this.country = country
this.subdivision = subdivision
this.city = city
this.broadcastArea = new Collection(broadcast_area)
this.languages = new Collection(languages)
this.categories = new Collection(categories)
this.isNSFW = is_nsfw
this.launched = launched
this.closed = closed
this.replacedBy = replaced_by
this.website = website
this.logo = logo
}
}

59
scripts/core/apiClient.ts Normal file
View file

@ -0,0 +1,59 @@
import { Logger, Storage } from '@freearhey/core'
import axios, { AxiosInstance, AxiosResponse, AxiosProgressEvent } from 'axios'
import cliProgress, { MultiBar } from 'cli-progress'
import numeral from 'numeral'
export class ApiClient {
progressBar: MultiBar
client: AxiosInstance
storage: Storage
logger: Logger
constructor({ logger }: { logger: Logger }) {
this.logger = logger
this.client = axios.create({
responseType: 'stream'
})
this.storage = new 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 download(filename: string) {
const stream = await this.storage.createStream(`/temp/data/${filename}`)
const bar = this.progressBar.create(0, 0, { filename })
this.client
.get(`https://iptv-org.github.io/api/${filename}`, {
onDownloadProgress({ total, loaded, rate }: AxiosProgressEvent) {
if (total) bar.setTotal(total)
bar.update(loaded, { speed: rate })
}
})
.then((response: AxiosResponse) => {
response.data.pipe(stream)
})
}
}

View file

@ -0,0 +1,24 @@
import { parseChannels } from 'epg-grabber'
import { Storage, Collection } from '@freearhey/core'
type ChannelsParserProps = {
storage: Storage
}
export class ChannelsParser {
storage: Storage
constructor({ storage }: ChannelsParserProps) {
this.storage = storage
}
async parse(filepath: string) {
let parsedChannels = new Collection()
const content = await this.storage.load(filepath)
const channels = parseChannels(content)
parsedChannels = parsedChannels.concat(new Collection(channels))
return parsedChannels
}
}

View file

@ -0,0 +1,19 @@
import { SiteConfig } from 'epg-grabber'
import _ from 'lodash'
export class ConfigLoader {
async load(filepath: string): Promise<SiteConfig> {
const config = (await import(filepath)).default
return _.merge(
{
delay: 0,
maxConnections: 1,
request: {
timeout: 30000
}
},
config
)
}
}

View file

@ -1,76 +0,0 @@
const nedb = require('nedb-promises')
const file = require('./file')
const DB_DIR = process.env.DB_DIR || './scripts/tmp/database'
class Database {
constructor(filepath) {
this.filepath = filepath
}
load() {
this.db = nedb.create({
filename: file.resolve(this.filepath),
autoload: true,
onload: err => {
if (err) console.error(err)
},
compareStrings: (a, b) => {
a = a.replace(/\s/g, '_')
b = b.replace(/\s/g, '_')
return a.localeCompare(b, undefined, {
sensitivity: 'accent',
numeric: true
})
}
})
}
removeIndex(field) {
return this.db.removeIndex(field)
}
addIndex(options) {
return this.db.ensureIndex(options)
}
compact() {
return this.db.persistence.compactDatafile()
}
stopAutocompact() {
return this.db.persistence.stopAutocompaction()
}
reset() {
return file.clear(this.filepath)
}
count(query) {
return this.db.count(query)
}
insert(doc) {
return this.db.insert(doc)
}
update(query, update) {
return this.db.update(query, update)
}
find(query) {
return this.db.find(query)
}
remove(query, options) {
return this.db.remove(query, options)
}
}
const db = {}
db.queue = new Database(`${DB_DIR}/queue.db`)
db.programs = new Database(`${DB_DIR}/programs.db`)
module.exports = db

View file

@ -1,93 +0,0 @@
const path = require('path')
const glob = require('glob')
const fs = require('fs-extra')
const file = {}
file.templateVariables = function (template) {
const match = template.match(/{[^}]+}/g)
return Array.isArray(match) ? match.map(s => s.substring(1, s.length - 1)) : []
}
file.templateFormat = function (template, obj) {
let output = template
for (let key in obj) {
const regex = new RegExp(`{${key}}`, 'g')
const value = obj[key] || undefined
output = output.replace(regex, value)
}
return output
}
file.list = function (pattern) {
return new Promise(resolve => {
glob(pattern, function (err, files) {
resolve(files)
})
})
}
file.getFilename = function (filepath) {
return path.parse(filepath).name
}
file.createDir = async function (dir) {
if (await file.exists(dir)) return
return fs.mkdir(dir, { recursive: true }).catch(console.error)
}
file.exists = function (filepath) {
return fs.exists(path.resolve(filepath))
}
file.existsSync = function (filepath) {
return fs.existsSync(path.resolve(filepath))
}
file.read = function (filepath) {
return fs.readFile(path.resolve(filepath), { encoding: 'utf8' }).catch(console.error)
}
file.append = function (filepath, data) {
return fs.appendFile(path.resolve(filepath), data).catch(console.error)
}
file.create = function (filepath, data = '') {
filepath = path.resolve(filepath)
const dir = path.dirname(filepath)
return file
.createDir(dir)
.then(() => file.write(filepath, data))
.catch(console.error)
}
file.write = function (filepath, data = '') {
return fs.writeFile(path.resolve(filepath), data, { encoding: 'utf8' }).catch(console.error)
}
file.writeSync = function (filepath, data = '') {
return fs.writeFileSync(path.resolve(filepath), data, { encoding: 'utf8' })
}
file.clear = async function (filepath) {
if (await file.exists(filepath)) return file.write(filepath, '')
return true
}
file.resolve = function (filepath) {
return path.resolve(filepath)
}
file.dirname = function (filepath) {
return path.dirname(filepath)
}
file.basename = function (filepath) {
return path.basename(filepath)
}
module.exports = file

75
scripts/core/grabber.ts Normal file
View file

@ -0,0 +1,75 @@
import { EPGGrabber, GrabCallbackData, EPGGrabberMock, SiteConfig, Channel } from 'epg-grabber'
import { Logger, Collection } from '@freearhey/core'
import { Queue } from './'
import { GrabOptions } from '../commands/epg/grab'
import { TaskQueue, PromisyClass } from 'cwait'
type GrabberProps = {
logger: Logger
queue: Queue
options: GrabOptions
}
export class Grabber {
logger: Logger
queue: Queue
options: GrabOptions
constructor({ logger, queue, options }: GrabberProps) {
this.logger = logger
this.queue = queue
this.options = options
}
async grab(): Promise<{ channels: Collection; programs: Collection }> {
const taskQueue = new TaskQueue(Promise as PromisyClass, this.options.maxConnections)
const total = this.queue.size()
const channels = new Collection()
let programs = new Collection()
let i = 1
await Promise.all(
this.queue.items().map(
taskQueue.wrap(
async (queueItem: { channel: Channel; config: SiteConfig; date: string }) => {
const { channel, config, date } = queueItem
channels.add(channel)
if (this.options.timeout !== undefined) {
const timeout = parseInt(this.options.timeout)
config.request = { ...config.request, ...{ timeout } }
}
const grabber =
process.env.NODE_ENV === 'test' ? new EPGGrabberMock(config) : new EPGGrabber(config)
const _programs = await grabber.grab(
channel,
date,
(data: GrabCallbackData, error: Error | null) => {
const { programs, date } = data
this.logger.info(
` [${i}/${total}] ${channel.site} (${channel.lang}) - ${
channel.xmltv_id
} - ${date.format('MMM D, YYYY')} (${programs.length} programs)`
)
if (i < total) i++
if (error) {
this.logger.info(` ERR: ${error.message}`)
}
}
)
programs = programs.concat(new Collection(_programs))
}
)
)
)
return { channels, programs }
}
}

55
scripts/core/guide.ts Normal file
View file

@ -0,0 +1,55 @@
import { Collection, Logger, DateTime, Storage, Zip } from '@freearhey/core'
import { Channel } from 'epg-grabber'
import { XMLTV } from '../core'
import { CURR_DATE } from '../constants'
type GuideProps = {
channels: Collection
programs: Collection
logger: Logger
filepath: string
gzip: boolean
}
export class Guide {
channels: Collection
programs: Collection
logger: Logger
storage: Storage
filepath: string
gzip: boolean
constructor({ channels, programs, logger, filepath, gzip }: GuideProps) {
this.channels = channels
this.programs = programs
this.logger = logger
this.storage = new Storage()
this.filepath = filepath
this.gzip = gzip || false
}
async save() {
const channels = this.channels.uniqBy(
(channel: Channel) => `${channel.xmltv_id}:${channel.site}`
)
const programs = this.programs
const xmltv = new XMLTV({
channels,
programs,
date: new DateTime(CURR_DATE, { zone: 'UTC' })
})
const xmlFilepath = this.filepath
this.logger.info(` saving to "${xmlFilepath}"...`)
await this.storage.save(xmlFilepath, xmltv.toString())
if (this.gzip) {
const zip = new Zip()
const compressed = await zip.compress(xmltv.toString())
const gzFilepath = `${this.filepath}.gz`
this.logger.info(` saving to "${gzFilepath}"...`)
await this.storage.save(gzFilepath, compressed)
}
}
}

View file

@ -0,0 +1,61 @@
import { Collection, Logger, Storage, StringTemplate } from '@freearhey/core'
import { OptionValues } from 'commander'
import { Channel, Program } from 'epg-grabber'
import { Guide } from '.'
type GuideManagerProps = {
options: OptionValues
logger: Logger
channels: Collection
programs: Collection
}
export class GuideManager {
options: OptionValues
storage: Storage
logger: Logger
channels: Collection
programs: Collection
constructor({ channels, programs, logger, options }: GuideManagerProps) {
this.options = options
this.logger = logger
this.channels = channels
this.programs = programs
this.storage = new Storage()
}
async createGuides() {
const pathTemplate = new StringTemplate(this.options.output)
const groupedChannels = this.channels
.orderBy([(channel: Channel) => channel.xmltv_id])
.uniqBy((channel: Channel) => `${channel.xmltv_id}:${channel.site}:${channel.lang}`)
.groupBy((channel: Channel) => {
return pathTemplate.format({ lang: channel.lang || 'en', site: channel.site || '' })
})
const groupedPrograms = this.programs
.orderBy([(program: Program) => program.channel, (program: Program) => program.start])
.groupBy((program: Program) => {
const lang =
program.titles && program.titles.length && program.titles[0].lang
? program.titles[0].lang
: 'en'
return pathTemplate.format({ lang, site: program.site || '' })
})
for (const groupKey of groupedPrograms.keys()) {
const guide = new Guide({
filepath: groupKey,
gzip: this.options.gzip,
channels: new Collection(groupedChannels.get(groupKey)),
programs: new Collection(groupedPrograms.get(groupKey)),
logger: this.logger
})
await guide.save()
}
}
}

View file

@ -1,11 +0,0 @@
exports.db = require('./db')
exports.logger = require('./logger')
exports.file = require('./file')
exports.parser = require('./parser')
exports.timer = require('./timer')
exports.markdown = require('./markdown')
exports.api = require('./api')
exports.date = require('./date')
exports.table = require('./table')
exports.xml = require('./xml')
exports.zip = require('./zip')

11
scripts/core/index.ts Normal file
View file

@ -0,0 +1,11 @@
export * from './xml'
export * from './channelsParser'
export * from './xmltv'
export * from './configLoader'
export * from './grabber'
export * from './job'
export * from './queue'
export * from './guideManager'
export * from './guide'
export * from './apiChannel'
export * from './apiClient'

34
scripts/core/job.ts Normal file
View file

@ -0,0 +1,34 @@
import { Logger } from '@freearhey/core'
import { Queue, Grabber, GuideManager } from '.'
import { GrabOptions } from '../commands/epg/grab'
type JobProps = {
options: GrabOptions
logger: Logger
queue: Queue
}
export class Job {
options: GrabOptions
logger: Logger
grabber: Grabber
constructor({ queue, logger, options }: JobProps) {
this.options = options
this.logger = logger
this.grabber = new Grabber({ logger, queue, options })
}
async run() {
const { channels, programs } = await this.grabber.grab()
const manager = new GuideManager({
channels,
programs,
options: this.options,
logger: this.logger
})
await manager.createGuides()
}
}

View file

@ -1,3 +0,0 @@
const { consola } = require('consola')
module.exports = consola

View file

@ -1,10 +0,0 @@
const markdownInclude = require('markdown-include')
const file = require('./file')
const markdown = {}
markdown.compile = function (filepath) {
markdownInclude.compileFiles(file.resolve(filepath))
}
module.exports = markdown

View file

@ -1,29 +0,0 @@
const file = require('./file')
const grabber = require('epg-grabber')
const parser = {}
parser.parseChannels = async function (filepath) {
const content = await file.read(filepath)
return grabber.parseChannels(content)
}
parser.parseLogs = async function (filepath) {
const content = await file.read(filepath)
if (!content) return []
const lines = content.split('\n')
return lines.map(line => (line ? JSON.parse(line) : null)).filter(l => l)
}
parser.parseNumber = function (string) {
const parsed = parseInt(string)
if (isNaN(parsed)) {
throw new Error('scripts/core/parser.js:parseNumber() Input value is not a number')
}
return parsed
}
module.exports = parser

94
scripts/core/queue.ts Normal file
View file

@ -0,0 +1,94 @@
import { Storage, Collection, DateTime, Logger, Dictionary } from '@freearhey/core'
import { ChannelsParser, ConfigLoader, ApiChannel } from './'
import { SITES_DIR, DATA_DIR, CURR_DATE } from '../constants'
import { Channel, SiteConfig } from 'epg-grabber'
import path from 'path'
import { GrabOptions } from '../commands/epg/grab'
export type QueueItem = {
channel: Channel
date: string
config: SiteConfig
error: string | null
}
type QueueProps = {
logger: Logger
options: GrabOptions
parsedChannels: Collection
}
export class Queue {
configLoader: ConfigLoader
logger: Logger
sitesStorage: Storage
dataStorage: Storage
parser: ChannelsParser
parsedChannels: Collection
options: GrabOptions
date: DateTime
_items: QueueItem[] = []
constructor({ parsedChannels, logger, options }: QueueProps) {
this.parsedChannels = parsedChannels
this.logger = logger
this.sitesStorage = new Storage()
this.dataStorage = new Storage(DATA_DIR)
this.parser = new ChannelsParser({ storage: new Storage() })
this.date = new DateTime(CURR_DATE)
this.options = options
this.configLoader = new ConfigLoader()
}
async create() {
const channelsContent = await this.dataStorage.json('channels.json')
const channels = new Collection(channelsContent).map(data => new ApiChannel(data))
const queue = new Dictionary()
for (const channel of this.parsedChannels.all()) {
if (!channel.site || !channel.xmltv_id) continue
if (this.options.lang && channel.lang !== this.options.lang) continue
const configPath = path.resolve(SITES_DIR, `${channel.site}/${channel.site}.config.js`)
const config: SiteConfig = await this.configLoader.load(configPath)
const found: ApiChannel = channels.first(
(_channel: ApiChannel) => _channel.id === channel.xmltv_id
)
if (found) {
channel.logo = found.logo
}
const days = this.options.days || config.days || 1
const dates = Array.from({ length: days }, (_, day) => this.date.add(day, 'd'))
dates.forEach((date: DateTime) => {
const dateString = date.toJSON()
const key = `${channel.site}:${channel.lang}:${channel.xmltv_id}:${dateString}`
if (queue.missing(key)) {
queue.set(key, {
channel,
date: dateString,
config,
error: null
})
}
})
}
this._items = Object.values(queue.data())
}
size(): number {
return this._items.length
}
items(): QueueItem[] {
return this._items
}
isEmpty(): boolean {
return this._items.length === 0
}
}

View file

@ -1,47 +0,0 @@
const table = {}
table.create = function (data, cols) {
let output = '<table>\r\n'
output += ' <thead>\r\n <tr>'
for (let column of cols) {
output += `<th align="left">${column}</th>`
}
output += '</tr>\r\n </thead>\r\n'
output += ' <tbody>\r\n'
output += getHTMLRows(data)
output += ' </tbody>\r\n'
output += '</table>'
return output
}
function getHTMLRows(data) {
let output = ''
for (let group of data) {
let rowspan = group.length
for (let [j, row] of group.entries()) {
output += ' <tr>'
for (let [i, value] of row.entries()) {
if (i === 0 && j === 0) {
output += `<td valign="top" rowspan="${rowspan}">${value}</td>`
} else if (i > 0) {
if (typeof value === 'number') {
output += `<td align="right" nowrap>${value}</td>`
} else {
output += `<td nowrap>${value}</td>`
}
}
}
output += '</tr>\r\n'
}
}
return output
}
function getSpan() {}
module.exports = table

View file

@ -1,29 +0,0 @@
const { performance } = require('perf_hooks')
const dayjs = require('dayjs')
const duration = require('dayjs/plugin/duration')
const relativeTime = require('dayjs/plugin/relativeTime')
dayjs.extend(relativeTime)
dayjs.extend(duration)
const timer = {}
let t0 = 0
timer.start = function () {
t0 = performance.now()
}
timer.format = function (f) {
let t1 = performance.now()
return dayjs.duration(t1 - t0).format(f)
}
timer.humanize = function (suffix = true) {
let t1 = performance.now()
return dayjs.duration(t1 - t0).humanize(suffix)
}
module.exports = timer

View file

@ -1,25 +1,36 @@
const xml = {}
import { Collection } from '@freearhey/core'
import { Channel } from 'epg-grabber'
xml.create = function (items, site) {
let output = `<?xml version="1.0" encoding="UTF-8"?>\r\n<site site="${site}">\r\n <channels>\r\n`
export class XML {
items: Collection
site: string
items.forEach(channel => {
const logo = channel.logo ? ` logo="${channel.logo}"` : ''
const xmltv_id = channel.xmltv_id || ''
const lang = channel.lang || ''
const site_id = channel.site_id || ''
output += ` <channel lang="${lang}" xmltv_id="${escapeString(
xmltv_id
)}" site_id="${site_id}"${logo}>${escapeString(channel.name)}</channel>\r\n`
})
constructor(items: Collection, site: string) {
this.items = items
this.site = site
}
output += ` </channels>\r\n</site>\r\n`
toString() {
let output = '<?xml version="1.0" encoding="UTF-8"?>\r\n<channels>\r\n'
return output
this.items.forEach((channel: Channel) => {
const logo = channel.logo ? ` logo="${channel.logo}"` : ''
const xmltv_id = channel.xmltv_id || ''
const lang = channel.lang || ''
const site_id = channel.site_id || ''
output += ` <channel site="${this.site}" lang="${lang}" xmltv_id="${escapeString(
xmltv_id
)}" site_id="${site_id}"${logo}>${escapeString(channel.name)}</channel>\r\n`
})
output += '</channels>\r\n'
return output
}
}
function escapeString(string, defaultValue = '') {
if (!string) return defaultValue
function escapeString(value: string, defaultValue: string = '') {
if (!value) return defaultValue
const regex = new RegExp(
'((?:[\0-\x08\x0B\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))|([\\x7F-\\x84]|[\\x86-\\x9F]|[\\uFDD0-\\uFDEF]|(?:\\uD83F[\\uDFFE\\uDFFF])|(?:\\uD87F[\\uDF' +
@ -33,9 +44,9 @@ function escapeString(string, defaultValue = '') {
'g'
)
string = String(string || '').replace(regex, '')
value = String(value || '').replace(regex, '')
return string
return value
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
@ -45,5 +56,3 @@ function escapeString(string, defaultValue = '') {
.replace(/ +/g, ' ')
.trim()
}
module.exports = xml

28
scripts/core/xmltv.ts Normal file
View file

@ -0,0 +1,28 @@
import { DateTime, Collection } from '@freearhey/core'
import { generateXMLTV } from 'epg-grabber'
type XMLTVProps = {
channels: Collection
programs: Collection
date: DateTime
}
export class XMLTV {
channels: Collection
programs: Collection
date: DateTime
constructor({ channels, programs, date }: XMLTVProps) {
this.channels = channels
this.programs = programs
this.date = date
}
toString() {
return generateXMLTV({
channels: this.channels.all(),
programs: this.programs.all(),
date: this.date.toJSON()
})
}
}

View file

@ -1,13 +0,0 @@
const { gzip, ungzip } = require('node-gzip')
const zip = {}
zip.compress = async function (string) {
return gzip(string)
}
zip.decompress = async function (string) {
return ungzip(string)
}
module.exports = zip

View file

@ -1,2 +0,0 @@
*
!.gitignore

1
scripts/types/langs.d.ts vendored Normal file
View file

@ -0,0 +1 @@
declare module 'langs'

View file

@ -1,6 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="9tv.co.il">
<channels>
<channel lang="ru" xmltv_id="Channel9.il" site_id="#">9 канал</channel>
</channels>
</site>
<channels>
<channel site="9tv.co.il" lang="ru" xmltv_id="Channel9.il" site_id="#">9 канал</channel>
</channels>

View file

@ -49,7 +49,7 @@ function parseIcon($item) {
'background-image'
)
if (!backgroundImage) return null
const [_, relativePath] = backgroundImage.match(/url\((.*)\)/) || [null, null]
const [, relativePath] = backgroundImage.match(/url\((.*)\)/) || [null, null]
return relativePath ? `https://www.9tv.co.il${relativePath}` : null
}

View file

@ -1,4 +1,4 @@
// npx epg-grabber --config=sites/9tv.co.il/9tv.co.il.config.js --channels=sites/9tv.co.il/9tv.co.il.channels.xml --output=guide.xml --days=2
// npm run grab -- --site=9tv.co.il
const { parser, url } = require('./9tv.co.il.config.js')
const dayjs = require('dayjs')
@ -20,7 +20,8 @@ it('can generate valid url', () => {
})
it('can parse response', () => {
const content = `<li> <a href="#" class="guide_list_link w-inline-block"> <div class="guide_list_time">06:30</div><div class="guide_info_group"> <div class="guide_info_pict" style="background-image: url(/download/pictures/img_id=8484.jpg);"></div><div class="guide_txt_group"> <h3 class="guide_info_title">Слепая</h3> <div>Она не очень любит говорить о себе или о том, кто и зачем к ней обращается. Живет уединенно, в глуши. Но тех, кто приходит -принимает. Она видит судьбы.&#160;</div></div></div></a></li><li> <a href="#" class="guide_list_link even w-inline-block"> <div class="guide_list_time">09:10</div><div class="guide_info_group"> <div class="guide_info_pict" style="background-image: url(/download/pictures/img_id=23694.jpg);"></div><div class="guide_txt_group"> <h3 class="guide_info_title">Орел и решка. Морской сезон</h3> <div>Орел и решка. Морской сезон. Ведущие -Алина Астровская и Коля Серга.</div></div></div></a></li>`
const content =
'<li> <a href="#" class="guide_list_link w-inline-block"> <div class="guide_list_time">06:30</div><div class="guide_info_group"> <div class="guide_info_pict" style="background-image: url(/download/pictures/img_id=8484.jpg);"></div><div class="guide_txt_group"> <h3 class="guide_info_title">Слепая</h3> <div>Она не очень любит говорить о себе или о том, кто и зачем к ней обращается. Живет уединенно, в глуши. Но тех, кто приходит -принимает. Она видит судьбы.&#160;</div></div></div></a></li><li> <a href="#" class="guide_list_link even w-inline-block"> <div class="guide_list_time">09:10</div><div class="guide_info_group"> <div class="guide_info_pict" style="background-image: url(/download/pictures/img_id=23694.jpg);"></div><div class="guide_txt_group"> <h3 class="guide_info_title">Орел и решка. Морской сезон</h3> <div>Орел и решка. Морской сезон. Ведущие -Алина Астровская и Коля Серга.</div></div></div></a></li>'
const result = parser({ content, date }).map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
@ -31,7 +32,7 @@ it('can parse response', () => {
{
start: '2022-03-06T04:30:00.000Z',
stop: '2022-03-06T07:10:00.000Z',
title: `Слепая`,
title: 'Слепая',
icon: 'https://www.9tv.co.il/download/pictures/img_id=8484.jpg',
description:
'Она не очень любит говорить о себе или о том, кто и зачем к ней обращается. Живет уединенно, в глуши. Но тех, кто приходит -принимает. Она видит судьбы.'
@ -40,7 +41,7 @@ it('can parse response', () => {
start: '2022-03-06T07:10:00.000Z',
stop: '2022-03-06T08:10:00.000Z',
icon: 'https://www.9tv.co.il/download/pictures/img_id=23694.jpg',
title: `Орел и решка. Морской сезон`,
title: 'Орел и решка. Морской сезон',
description: 'Орел и решка. Морской сезон. Ведущие -Алина Астровская и Коля Серга.'
}
])
@ -50,7 +51,7 @@ it('can handle empty guide', () => {
const result = parser({
date,
channel,
content: `<!DOCTYPE html><html><head></head><body></body></html>`
content: '<!DOCTYPE html><html><head></head><body></body></html>'
})
expect(result).toMatchObject([])
})

View file

@ -1,37 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="abc.net.au">
<channels>
<channel lang="en" xmltv_id="10Bold.au" site_id="ONE">10 Bold</channel>
<channel lang="en" xmltv_id="10Peach.au" site_id="11">10 Peach</channel>
<channel lang="en" xmltv_id="10Shake.au" site_id="SHAKE">10 Shake</channel>
<channel lang="en" xmltv_id="7flix.au" site_id="7flix">7flix</channel>
<channel lang="en" xmltv_id="7mate.au" site_id="7MATE">7mate</channel>
<channel lang="en" xmltv_id="7two.au" site_id="7TWO">7two</channel>
<channel lang="en" xmltv_id="9Gem.au" site_id="GEM">9 Gem</channel>
<channel lang="en" xmltv_id="9Go.au" site_id="GO">9 Go!</channel>
<channel lang="en" xmltv_id="9Life.au" site_id="9Life">9 Life</channel>
<channel lang="en" xmltv_id="9Rush.au" site_id="9Rush">9 Rush</channel>
<channel lang="en" xmltv_id="ABCKids.au" site_id="ABC4KIDS">ABC Kids</channel>
<channel lang="en" xmltv_id="ABCMe.au" site_id="ABC3">ABC ME</channel>
<channel lang="en" xmltv_id="ABCNewsAustralia.au" site_id="ABCN">ABC News</channel>
<channel lang="en" xmltv_id="ABCTV.au" site_id="ABC1">ABC TV</channel>
<channel lang="en" xmltv_id="ABCTVPlus.au" site_id="ABC2">ABC TV Plus</channel>
<channel lang="en" xmltv_id="Channel10.au" site_id="10">Channel 10</channel>
<channel lang="en" xmltv_id="Channel7.au" site_id="7">Channel 7</channel>
<channel lang="en" xmltv_id="Channel9.au" site_id="9">Channel 9</channel>
<channel lang="en" xmltv_id="NITV.au" site_id="NITV">NITV</channel>
<channel lang="en" xmltv_id="Racingcom.au" site_id="RTV">Racing.com</channel>
<channel lang="en" xmltv_id="SBS.au" site_id="SBS">SBS One</channel>
<channel lang="en" xmltv_id="SBSFood.au" site_id="SBS3">SBS Food</channel>
<channel lang="en" xmltv_id="SBSViceland.au" site_id="VICHD">SBS Viceland</channel>
<channel lang="en" xmltv_id="SBSWorldMovies.au" site_id="SBS2">SBS World Movies</channel>
<channel lang="en" xmltv_id="SBSWorldWatch.au" site_id="SBSWW">SBS World Watch</channel>
<channel lang="en" xmltv_id="SpreeTV.au" site_id="SPREE">Spree TV</channel>
<channel lang="en" xmltv_id="TVSN.au" site_id="TVSN">TSVN</channel>
<!-- <channel lang="en" xmltv_id="ABCTV.au" site_id="ABCHD">ABC TV HD</channel> -->
<!-- <channel lang="en" xmltv_id="Channel10.au" site_id="TENHD">Channel 10 HD</channel> -->
<!-- <channel lang="en" xmltv_id="Channel7.au" site_id="7HD">Channel 7 HD</channel> -->
<!-- <channel lang="en" xmltv_id="Channel9.au" site_id="9HD">Channel 9 HD</channel> -->
<!-- <channel lang="en" xmltv_id="SBS.au" site_id="SBSHD">SBS HD</channel> -->
</channels>
</site>
<channels>
<!-- <channel site="abc.net.au" lang="en" xmltv_id="ABCTV.au" site_id="ABCHD">ABC TV HD</channel> -->
<!-- <channel site="abc.net.au" lang="en" xmltv_id="Channel10.au" site_id="TENHD">Channel 10 HD</channel> -->
<!-- <channel site="abc.net.au" lang="en" xmltv_id="Channel7.au" site_id="7HD">Channel 7 HD</channel> -->
<!-- <channel site="abc.net.au" lang="en" xmltv_id="Channel9.au" site_id="9HD">Channel 9 HD</channel> -->
<!-- <channel site="abc.net.au" lang="en" xmltv_id="SBS.au" site_id="SBSHD">SBS HD</channel> -->
<channel site="abc.net.au" lang="en" xmltv_id="10Bold.au" site_id="ONE">10 Bold</channel>
<channel site="abc.net.au" lang="en" xmltv_id="10Peach.au" site_id="11">10 Peach</channel>
<channel site="abc.net.au" lang="en" xmltv_id="10Shake.au" site_id="SHAKE">10 Shake</channel>
<channel site="abc.net.au" lang="en" xmltv_id="7flix.au" site_id="7flix">7flix</channel>
<channel site="abc.net.au" lang="en" xmltv_id="7mate.au" site_id="7MATE">7mate</channel>
<channel site="abc.net.au" lang="en" xmltv_id="7two.au" site_id="7TWO">7two</channel>
<channel site="abc.net.au" lang="en" xmltv_id="9Gem.au" site_id="GEM">9 Gem</channel>
<channel site="abc.net.au" lang="en" xmltv_id="9Go.au" site_id="GO">9 Go!</channel>
<channel site="abc.net.au" lang="en" xmltv_id="9Life.au" site_id="9Life">9 Life</channel>
<channel site="abc.net.au" lang="en" xmltv_id="9Rush.au" site_id="9Rush">9 Rush</channel>
<channel site="abc.net.au" lang="en" xmltv_id="ABCKids.au" site_id="ABC4KIDS">ABC Kids</channel>
<channel site="abc.net.au" lang="en" xmltv_id="ABCMe.au" site_id="ABC3">ABC ME</channel>
<channel site="abc.net.au" lang="en" xmltv_id="ABCNewsAustralia.au" site_id="ABCN">ABC News</channel>
<channel site="abc.net.au" lang="en" xmltv_id="ABCTV.au" site_id="ABC1">ABC TV</channel>
<channel site="abc.net.au" lang="en" xmltv_id="ABCTVPlus.au" site_id="ABC2">ABC TV Plus</channel>
<channel site="abc.net.au" lang="en" xmltv_id="Channel10.au" site_id="10">Channel 10</channel>
<channel site="abc.net.au" lang="en" xmltv_id="Channel7.au" site_id="7">Channel 7</channel>
<channel site="abc.net.au" lang="en" xmltv_id="Channel9.au" site_id="9">Channel 9</channel>
<channel site="abc.net.au" lang="en" xmltv_id="NITV.au" site_id="NITV">NITV</channel>
<channel site="abc.net.au" lang="en" xmltv_id="Racingcom.au" site_id="RTV">Racing.com</channel>
<channel site="abc.net.au" lang="en" xmltv_id="SBS.au" site_id="SBS">SBS One</channel>
<channel site="abc.net.au" lang="en" xmltv_id="SBSFood.au" site_id="SBS3">SBS Food</channel>
<channel site="abc.net.au" lang="en" xmltv_id="SBSViceland.au" site_id="VICHD">SBS Viceland</channel>
<channel site="abc.net.au" lang="en" xmltv_id="SBSWorldMovies.au" site_id="SBS2">SBS World Movies</channel>
<channel site="abc.net.au" lang="en" xmltv_id="SBSWorldWatch.au" site_id="SBSWW">SBS World Watch</channel>
<channel site="abc.net.au" lang="en" xmltv_id="SpreeTV.au" site_id="SPREE">Spree TV</channel>
<channel site="abc.net.au" lang="en" xmltv_id="TVSN.au" site_id="TVSN">TSVN</channel>
</channels>

View file

@ -1,4 +1,4 @@
// npx epg-grabber --config=sites/abc.net.au/abc.net.au.config.js --channels=sites/abc.net.au/abc.net.au.channels.xml --output=guide.xml --days=2
// npm run grab -- --site=abc.net.au
const { parser, url } = require('./abc.net.au.config.js')
const dayjs = require('dayjs')
@ -15,7 +15,8 @@ it('can generate valid url', () => {
})
it('can parse response', () => {
const content = `{"date":"2022-12-22","region":"Sydney","schedule":[{"channel":"ABC1","listing":[{"consumer_advice":"Adult Themes, Drug Use, Violence","rating":"M","show_id":912747,"repeat":true,"description":"When tragedy strikes close to home, it puts head teacher Noah Taylor on a collision course with the criminals responsible. Can the Lyell team help him stop the cycle of violence?","title":"Silent Witness","crid":"ZW2178A004S00","start_time":"2022-12-22T00:46:00","series-crid":"ZW2178A","live":false,"captioning":true,"show_type":"Episode","series_num":22,"episode_title":"Lift Up Your Hearts (part Two)","length":58,"onair_title":"Silent Witness","end_time":"2022-12-22T01:44:00","genres":["Entertainment"],"image_file":"ZW2178A004S00_460.jpg","prog_slug":"silent-witness","episode_num":4}]}]}`
const content =
'{"date":"2022-12-22","region":"Sydney","schedule":[{"channel":"ABC1","listing":[{"consumer_advice":"Adult Themes, Drug Use, Violence","rating":"M","show_id":912747,"repeat":true,"description":"When tragedy strikes close to home, it puts head teacher Noah Taylor on a collision course with the criminals responsible. Can the Lyell team help him stop the cycle of violence?","title":"Silent Witness","crid":"ZW2178A004S00","start_time":"2022-12-22T00:46:00","series-crid":"ZW2178A","live":false,"captioning":true,"show_type":"Episode","series_num":22,"episode_title":"Lift Up Your Hearts (part Two)","length":58,"onair_title":"Silent Witness","end_time":"2022-12-22T01:44:00","genres":["Entertainment"],"image_file":"ZW2178A004S00_460.jpg","prog_slug":"silent-witness","episode_num":4}]}]}'
const result = parser({ content, channel }).map(p => {
p.start = p.start.toJSON()
@ -27,7 +28,8 @@ it('can parse response', () => {
{
title: 'Silent Witness',
sub_title: 'Lift Up Your Hearts (part Two)',
description: `When tragedy strikes close to home, it puts head teacher Noah Taylor on a collision course with the criminals responsible. Can the Lyell team help him stop the cycle of violence?`,
description:
'When tragedy strikes close to home, it puts head teacher Noah Taylor on a collision course with the criminals responsible. Can the Lyell team help him stop the cycle of violence?',
category: ['Entertainment'],
rating: {
system: 'ACB',
@ -45,7 +47,8 @@ it('can parse response', () => {
it('can handle empty guide', () => {
const result = parser(
{
content: `<Error><Code>NoSuchKey</Code><Message>The specified key does not exist.</Message><Key>processed/Sydney_2023-01-17.json</Key><RequestId>6MRHX5TJ12X39B3Y</RequestId><HostId>59rH6XRMrmkFywg8Kv58iqpI6O1fuOCuEbKa1HRRYa4buByXMBTvAhz8zuAK7X5D+ZN9ZuWxyGs=</HostId></Error>`
content:
'<Error><Code>NoSuchKey</Code><Message>The specified key does not exist.</Message><Key>processed/Sydney_2023-01-17.json</Key><RequestId>6MRHX5TJ12X39B3Y</RequestId><HostId>59rH6XRMrmkFywg8Kv58iqpI6O1fuOCuEbKa1HRRYa4buByXMBTvAhz8zuAK7X5D+ZN9ZuWxyGs=</HostId></Error>'
},
channel
)

View file

@ -47,7 +47,7 @@ module.exports = {
}
function parseItems(content, channel) {
const [_, channelId] = channel.site_id.split('#')
const [, channelId] = channel.site_id.split('#')
const data = JSON.parse(content)
if (!data || !Array.isArray(data.channels)) return []
const channelData = data.channels.find(i => i.id === channelId)

View file

@ -1,8 +1,8 @@
// node ./scripts/channels.js --config=./sites/allente.se/allente.se.config.js --output=./sites/allente.se/allente.se_se.channels.xml --set=country:se --set=lang:sv
// node ./scripts/channels.js --config=./sites/allente.se/allente.se.config.js --output=./sites/allente.se/allente.se_fi.channels.xml --set=country:fi --set=lang:fi
// node ./scripts/channels.js --config=./sites/allente.se/allente.se.config.js --output=./sites/allente.se/allente.se_no.channels.xml --set=country:no --set=lang:no
// node ./scripts/channels.js --config=./sites/allente.se/allente.se.config.js --output=./sites/allente.se/allente.se_dk.channels.xml --set=country:dk --set=lang:da
// npx epg-grabber --config=sites/allente.se/allente.se.config.js --channels=sites/allente.se/allente.se_se.channels.xml --output=guide.xml --days=2
// npm run channels:parse -- --config=./sites/allente.se/allente.se.config.js --output=./sites/allente.se/allente.se_se.channels.xml --set=country:se --set=lang:sv
// npm run channels:parse -- --config=./sites/allente.se/allente.se.config.js --output=./sites/allente.se/allente.se_fi.channels.xml --set=country:fi --set=lang:fi
// npm run channels:parse -- --config=./sites/allente.se/allente.se.config.js --output=./sites/allente.se/allente.se_no.channels.xml --set=country:no --set=lang:no
// npm run channels:parse -- --config=./sites/allente.se/allente.se.config.js --output=./sites/allente.se/allente.se_dk.channels.xml --set=country:dk --set=lang:da
// npm run grab -- --site=allente.se
const { parser, url } = require('./allente.se.config.js')
const dayjs = require('dayjs')
@ -29,7 +29,8 @@ it('can generate valid url for different country', () => {
})
it('can parse response', () => {
const content = `{"channels":[{"id":"0148","icon":"//images.ctfassets.net/989y85n5kcxs/5uT9g9pdQWRZeDPQXVI9g6/9cc44da567f591822ed645c99ecdcb64/SVT_1_black_new__2_.png","name":"SVT1 HD (T)","events":[{"id":"0086202208220710","live":false,"time":"2022-08-22T07:10:00Z","title":"Hemmagympa med Sofia","details":{"title":"Hemmagympa med Sofia","image":"https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440","description":"Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.","season":4,"episode":1,"categories":["other"],"duration":"20"}}]}]}`
const content =
'{"channels":[{"id":"0148","icon":"//images.ctfassets.net/989y85n5kcxs/5uT9g9pdQWRZeDPQXVI9g6/9cc44da567f591822ed645c99ecdcb64/SVT_1_black_new__2_.png","name":"SVT1 HD (T)","events":[{"id":"0086202208220710","live":false,"time":"2022-08-22T07:10:00Z","title":"Hemmagympa med Sofia","details":{"title":"Hemmagympa med Sofia","image":"https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440","description":"Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.","season":4,"episode":1,"categories":["other"],"duration":"20"}}]}]}'
const result = parser({ content, channel }).map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
@ -40,9 +41,10 @@ it('can parse response', () => {
{
start: '2022-08-22T07:10:00.000Z',
stop: '2022-08-22T07:30:00.000Z',
title: `Hemmagympa med Sofia`,
title: 'Hemmagympa med Sofia',
category: ['other'],
description: `Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.`,
description:
'Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.',
icon: 'https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440',
season: 4,
episode: 1
@ -54,7 +56,7 @@ it('can handle empty guide', () => {
const result = parser({
date,
channel,
content: `{"date":"2001-11-17","categories":[],"channels":[]}`
content: '{"date":"2001-11-17","categories":[],"channels":[]}'
})
expect(result).toMatchObject([])
})

View file

@ -1,66 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="allente.se">
<channels>
<channel lang="da" xmltv_id="6eren.dk" site_id="dk#568">6&apos;eren</channel>
<channel lang="da" xmltv_id="BoomerangNordic.uk" site_id="dk#0017">Boomerang Nordic</channel>
<channel lang="da" xmltv_id="Canal9.dk" site_id="dk#0368">Canal 9</channel>
<channel lang="da" xmltv_id="CartoonNetworkScandinavia.uk" site_id="dk#0028">Cartoon Network Nordic</channel>
<channel lang="da" xmltv_id="CMoreFirst.se" site_id="dk#968">C More First</channel>
<channel lang="da" xmltv_id="CMoreHits.se" site_id="dk#969">C More Hits</channel>
<channel lang="da" xmltv_id="CMoreSeries.se" site_id="dk#971">C More Series</channel>
<channel lang="da" xmltv_id="CMoreStars.se" site_id="dk#970">C More Stars</channel>
<channel lang="da" xmltv_id="DisneyChannelScandinavia.uk" site_id="dk#0037">Disney Channel Scandinavia</channel>
<channel lang="da" xmltv_id="DisneyJuniorScandinavia.uk" site_id="dk#0307">Disney Junior Scandinavia</channel>
<channel lang="da" xmltv_id="dk4.dk" site_id="dk#0376">DK 4</channel>
<channel lang="da" xmltv_id="DR1.dk" site_id="dk#452">DR 1</channel>
<channel lang="da" xmltv_id="DR2.dk" site_id="dk#0051">DR 2</channel>
<channel lang="da" xmltv_id="DRRamasjang.dk" site_id="dk#0048">DR Ramasjang</channel>
<channel lang="da" xmltv_id="EEurope.us" site_id="dk#0052">E! Europe</channel>
<channel lang="da" xmltv_id="EuronewsEnglish.fr" site_id="dk#0281">EuroNews English</channel>
<channel lang="da" xmltv_id="Eurosport2Danmark.dk" site_id="dk#0367">Eurosport 2 Danmark</channel>
<channel lang="da" xmltv_id="GodTV.uk" site_id="dk#0058">God TV Scandinavia</channel>
<channel lang="da" xmltv_id="Kanal4.dk" site_id="dk#0064">Kanal 4</channel>
<channel lang="da" xmltv_id="Kanal5.dk" site_id="dk#0065">Kanal 5</channel>
<channel lang="da" xmltv_id="MTV00s.uk" site_id="dk#0246">MTV 00s</channel>
<channel lang="da" xmltv_id="MTV80s.uk" site_id="dk#604">MTV 80s</channel>
<channel lang="da" xmltv_id="MTVGlobal.uk" site_id="dk#0076">MTV Nordic</channel>
<channel lang="da" xmltv_id="MTVHitsEurope.uk" site_id="dk#0077">MTV Hits Europe</channel>
<channel lang="da" xmltv_id="NationalGeographicDenmark.dk" site_id="dk#0317">National Geographic Danmark</channel>
<channel lang="da" xmltv_id="NationalGeographicWildDenmark.dk" site_id="dk#0082">National Geographic Wild Europe</channel>
<channel lang="da" xmltv_id="NickelodeonDenmark.dk" site_id="dk#0087">Nickelodeon Danmark</channel>
<channel lang="da" xmltv_id="NickJrScandinavia.nl" site_id="dk#0088">Nick Jr Scandinavia</channel>
<channel lang="da" xmltv_id="NicktoonsScandinavia.nl" site_id="dk#570">Nicktoons Scandinavia</channel>
<channel lang="da" xmltv_id="NRK1.no" site_id="dk#0090">NRK1</channel>
<channel lang="da" xmltv_id="ParamountNetworkDenmark.dk" site_id="dk#450">Paramount Network Danmark</channel>
<channel lang="da" xmltv_id="SFkanalen.se" site_id="dk#972">SF-kanalen</channel>
<channel lang="da" xmltv_id="SkyNewsInternational.uk" site_id="dk#0008">Sky News International</channel>
<channel lang="da" xmltv_id="SVT1.se" site_id="dk#0121">SVT 1</channel>
<channel lang="da" xmltv_id="TV2.dk" site_id="dk#0297">TV 2</channel>
<channel lang="da" xmltv_id="TV2Charlie.dk" site_id="dk#0180">TV 2 Charlie</channel>
<channel lang="da" xmltv_id="TV2Fri.dk" site_id="dk#0378">TV 2 Fri</channel>
<channel lang="da" xmltv_id="TV2News.dk" site_id="dk#0190">TV 2 News</channel>
<channel lang="da" xmltv_id="TV2Sport.dk" site_id="dk#454">TV 2 Sport</channel>
<channel lang="da" xmltv_id="TV2Zulu.dk" site_id="dk#0209">TV 2 Zulu</channel>
<channel lang="da" xmltv_id="TV3Danmark.dk" site_id="dk#0359">TV 3 Danmark</channel>
<channel lang="da" xmltv_id="TV3Max.dk" site_id="dk#0374">TV 3 Max</channel>
<channel lang="da" xmltv_id="TV3Plus.dk" site_id="dk#0248">TV3+</channel>
<channel lang="da" xmltv_id="TV3Puls.dk" site_id="dk#665">TV 3 Puls</channel>
<channel lang="da" xmltv_id="TV3SportDenmark.dk" site_id="dk#0200">TV 3 Sport</channel>
<channel lang="da" xmltv_id="TV4.se" site_id="dk#0227">TV 4</channel>
<channel lang="da" xmltv_id="VFilmAction.se" site_id="dk#0299">V Film Action</channel>
<channel lang="da" xmltv_id="VFilmFamily.se" site_id="dk#0308">V Film Family</channel>
<channel lang="da" xmltv_id="VFilmHits.se" site_id="dk#0322">V Film Hits</channel>
<channel lang="da" xmltv_id="VFilmPremiere.se" site_id="dk#0321">V Film Premiere</channel>
<channel lang="da" xmltv_id="ViasatExplore.se" site_id="dk#0358">Viasat Explore</channel>
<channel lang="da" xmltv_id="ViasatHistory.se" site_id="dk#0357">Viasat History HD</channel>
<channel lang="da" xmltv_id="ViasatNature.se" site_id="dk#0250">Viasat Nature</channel>
<channel lang="da" xmltv_id="VSeries.se" site_id="dk#0320">V Series</channel>
<channel lang="da" xmltv_id="VSportGolf.se" site_id="dk#0364">V Sport Golf</channel>
<channel lang="da" xmltv_id="VSportUltraHD.se" site_id="dk#418">V Sport Ultra HD</channel>
<channel lang="da" xmltv_id="Xee.dk" site_id="dk#707">Xee</channel>
<channel lang="en" xmltv_id="AlJazeeraEnglish.qa" site_id="dk#0344">Aljazeera English</channel>
<channel lang="en" xmltv_id="BBCWorldNewsEurope.uk" site_id="dk#0016">BBC World News Europe</channel>
<channel lang="en" xmltv_id="CNBCEurope.uk" site_id="dk#0032">CNBC Europe</channel>
<channel lang="en" xmltv_id="CNNInternationalEurope.us" site_id="dk#0033">CNN International Europe</channel>
</channels>
</site>
<channels>
<channel site="allente.se" lang="da" xmltv_id="6eren.dk" site_id="dk#568">6&apos;eren</channel>
<channel site="allente.se" lang="da" xmltv_id="BoomerangNordic.uk" site_id="dk#0017">Boomerang Nordic</channel>
<channel site="allente.se" lang="da" xmltv_id="Canal9.dk" site_id="dk#0368">Canal 9</channel>
<channel site="allente.se" lang="da" xmltv_id="CartoonNetworkScandinavia.uk" site_id="dk#0028">Cartoon Network Nordic</channel>
<channel site="allente.se" lang="da" xmltv_id="CMoreFirst.se" site_id="dk#968">C More First</channel>
<channel site="allente.se" lang="da" xmltv_id="CMoreHits.se" site_id="dk#969">C More Hits</channel>
<channel site="allente.se" lang="da" xmltv_id="CMoreSeries.se" site_id="dk#971">C More Series</channel>
<channel site="allente.se" lang="da" xmltv_id="CMoreStars.se" site_id="dk#970">C More Stars</channel>
<channel site="allente.se" lang="da" xmltv_id="DisneyChannelScandinavia.uk" site_id="dk#0037">Disney Channel Scandinavia</channel>
<channel site="allente.se" lang="da" xmltv_id="DisneyJuniorScandinavia.uk" site_id="dk#0307">Disney Junior Scandinavia</channel>
<channel site="allente.se" lang="da" xmltv_id="dk4.dk" site_id="dk#0376">DK 4</channel>
<channel site="allente.se" lang="da" xmltv_id="DR1.dk" site_id="dk#452">DR 1</channel>
<channel site="allente.se" lang="da" xmltv_id="DR2.dk" site_id="dk#0051">DR 2</channel>
<channel site="allente.se" lang="da" xmltv_id="DRRamasjang.dk" site_id="dk#0048">DR Ramasjang</channel>
<channel site="allente.se" lang="da" xmltv_id="EEurope.us" site_id="dk#0052">E! Europe</channel>
<channel site="allente.se" lang="da" xmltv_id="EuronewsEnglish.fr" site_id="dk#0281">EuroNews English</channel>
<channel site="allente.se" lang="da" xmltv_id="Eurosport2Danmark.dk" site_id="dk#0367">Eurosport 2 Danmark</channel>
<channel site="allente.se" lang="da" xmltv_id="GodTV.uk" site_id="dk#0058">God TV Scandinavia</channel>
<channel site="allente.se" lang="da" xmltv_id="Kanal4.dk" site_id="dk#0064">Kanal 4</channel>
<channel site="allente.se" lang="da" xmltv_id="Kanal5.dk" site_id="dk#0065">Kanal 5</channel>
<channel site="allente.se" lang="da" xmltv_id="MTV00s.uk" site_id="dk#0246">MTV 00s</channel>
<channel site="allente.se" lang="da" xmltv_id="MTV80s.uk" site_id="dk#604">MTV 80s</channel>
<channel site="allente.se" lang="da" xmltv_id="MTVGlobal.uk" site_id="dk#0076">MTV Nordic</channel>
<channel site="allente.se" lang="da" xmltv_id="MTVHitsEurope.uk" site_id="dk#0077">MTV Hits Europe</channel>
<channel site="allente.se" lang="da" xmltv_id="NationalGeographicDenmark.dk" site_id="dk#0317">National Geographic Danmark</channel>
<channel site="allente.se" lang="da" xmltv_id="NationalGeographicWildDenmark.dk" site_id="dk#0082">National Geographic Wild Europe</channel>
<channel site="allente.se" lang="da" xmltv_id="NickelodeonDenmark.dk" site_id="dk#0087">Nickelodeon Danmark</channel>
<channel site="allente.se" lang="da" xmltv_id="NickJrScandinavia.nl" site_id="dk#0088">Nick Jr Scandinavia</channel>
<channel site="allente.se" lang="da" xmltv_id="NicktoonsScandinavia.nl" site_id="dk#570">Nicktoons Scandinavia</channel>
<channel site="allente.se" lang="da" xmltv_id="NRK1.no" site_id="dk#0090">NRK1</channel>
<channel site="allente.se" lang="da" xmltv_id="ParamountNetworkDenmark.dk" site_id="dk#450">Paramount Network Danmark</channel>
<channel site="allente.se" lang="da" xmltv_id="SFkanalen.se" site_id="dk#972">SF-kanalen</channel>
<channel site="allente.se" lang="da" xmltv_id="SkyNewsInternational.uk" site_id="dk#0008">Sky News International</channel>
<channel site="allente.se" lang="da" xmltv_id="SVT1.se" site_id="dk#0121">SVT 1</channel>
<channel site="allente.se" lang="da" xmltv_id="TV2.dk" site_id="dk#0297">TV 2</channel>
<channel site="allente.se" lang="da" xmltv_id="TV2Charlie.dk" site_id="dk#0180">TV 2 Charlie</channel>
<channel site="allente.se" lang="da" xmltv_id="TV2Fri.dk" site_id="dk#0378">TV 2 Fri</channel>
<channel site="allente.se" lang="da" xmltv_id="TV2News.dk" site_id="dk#0190">TV 2 News</channel>
<channel site="allente.se" lang="da" xmltv_id="TV2Sport.dk" site_id="dk#454">TV 2 Sport</channel>
<channel site="allente.se" lang="da" xmltv_id="TV2Zulu.dk" site_id="dk#0209">TV 2 Zulu</channel>
<channel site="allente.se" lang="da" xmltv_id="TV3Danmark.dk" site_id="dk#0359">TV 3 Danmark</channel>
<channel site="allente.se" lang="da" xmltv_id="TV3Max.dk" site_id="dk#0374">TV 3 Max</channel>
<channel site="allente.se" lang="da" xmltv_id="TV3Plus.dk" site_id="dk#0248">TV3+</channel>
<channel site="allente.se" lang="da" xmltv_id="TV3Puls.dk" site_id="dk#665">TV 3 Puls</channel>
<channel site="allente.se" lang="da" xmltv_id="TV3SportDenmark.dk" site_id="dk#0200">TV 3 Sport</channel>
<channel site="allente.se" lang="da" xmltv_id="TV4.se" site_id="dk#0227">TV 4</channel>
<channel site="allente.se" lang="da" xmltv_id="VFilmAction.se" site_id="dk#0299">V Film Action</channel>
<channel site="allente.se" lang="da" xmltv_id="VFilmFamily.se" site_id="dk#0308">V Film Family</channel>
<channel site="allente.se" lang="da" xmltv_id="VFilmHits.se" site_id="dk#0322">V Film Hits</channel>
<channel site="allente.se" lang="da" xmltv_id="VFilmPremiere.se" site_id="dk#0321">V Film Premiere</channel>
<channel site="allente.se" lang="da" xmltv_id="ViasatExplore.se" site_id="dk#0358">Viasat Explore</channel>
<channel site="allente.se" lang="da" xmltv_id="ViasatHistory.se" site_id="dk#0357">Viasat History HD</channel>
<channel site="allente.se" lang="da" xmltv_id="ViasatNature.se" site_id="dk#0250">Viasat Nature</channel>
<channel site="allente.se" lang="da" xmltv_id="VSeries.se" site_id="dk#0320">V Series</channel>
<channel site="allente.se" lang="da" xmltv_id="VSportGolf.se" site_id="dk#0364">V Sport Golf</channel>
<channel site="allente.se" lang="da" xmltv_id="VSportUltraHD.se" site_id="dk#418">V Sport Ultra HD</channel>
<channel site="allente.se" lang="da" xmltv_id="Xee.dk" site_id="dk#707">Xee</channel>
<channel site="allente.se" lang="en" xmltv_id="AlJazeeraEnglish.qa" site_id="dk#0344">Aljazeera English</channel>
<channel site="allente.se" lang="en" xmltv_id="BBCWorldNewsEurope.uk" site_id="dk#0016">BBC World News Europe</channel>
<channel site="allente.se" lang="en" xmltv_id="CNBCEurope.uk" site_id="dk#0032">CNBC Europe</channel>
<channel site="allente.se" lang="en" xmltv_id="CNNInternationalEurope.us" site_id="dk#0033">CNN International Europe</channel>
</channels>

View file

@ -1,40 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="allente.se">
<channels>
<channel lang="fi" xmltv_id="BoomerangNordic.uk" site_id="fi#0017">Boomerang</channel>
<channel lang="fi" xmltv_id="CartoonNetworkScandinavia.uk" site_id="fi#0028">Cartoon Network</channel>
<channel lang="fi" xmltv_id="CNBCEurope.uk" site_id="fi#0032">CNBC</channel>
<channel lang="fi" xmltv_id="CNNInternationalEurope.us" site_id="fi#0033">CNN</channel>
<channel lang="fi" xmltv_id="DisneyChannelScandinavia.uk" site_id="fi#0037">Disney Channel</channel>
<channel lang="fi" xmltv_id="DisneyJuniorScandinavia.uk" site_id="fi#0307">Disney Junior</channel>
<channel lang="fi" xmltv_id="EEurope.us" site_id="fi#0052">E!</channel>
<channel lang="fi" xmltv_id="MTV00s.uk" site_id="fi#0246">MTV 00s</channel>
<channel lang="fi" xmltv_id="MTVGlobal.uk" site_id="fi#0080">MTV</channel>
<channel lang="fi" xmltv_id="NationalGeographicFinland.fi" site_id="fi#0084">National Geographic</channel>
<channel lang="fi" xmltv_id="NationalGeographicWildFinland.fi" site_id="fi#558">National Geographic Wild</channel>
<channel lang="fi" xmltv_id="NickJrScandinavia.nl" site_id="fi#0088">Nick Jr</channel>
<channel lang="fi" xmltv_id="TV3.se" site_id="fi#0290">TV 3 Sverige</channel>
<channel lang="fi" xmltv_id="TV6Sweden.se" site_id="fi#0360">TV 6 Sverige</channel>
<channel lang="fi" xmltv_id="VFilmAction.se" site_id="fi#0299">V Film Action</channel>
<channel lang="fi" xmltv_id="VFilmFamily.se" site_id="fi#0308">V Film Family</channel>
<channel lang="fi" xmltv_id="VFilmHits.se" site_id="fi#0322">V Film Hits</channel>
<channel lang="fi" xmltv_id="VFilmPremiere.se" site_id="fi#0321">V Film Premiere</channel>
<channel lang="fi" xmltv_id="ViasatExplore.se" site_id="fi#0252">Viasat Explore</channel>
<channel lang="fi" xmltv_id="ViasatHistory.se" site_id="fi#0263">Viasat History HD</channel>
<channel lang="fi" xmltv_id="ViasatNature.se" site_id="fi#0250">Viasat Nature</channel>
<channel lang="fi" xmltv_id="VSport1Finland.fi" site_id="fi#0159">V Sport 1 Suomi</channel>
<channel lang="fi" xmltv_id="VSport1Sweden.se" site_id="fi#0362">V Sport 1 Sverige</channel>
<channel lang="fi" xmltv_id="VSport2Finland.fi" site_id="fi#488">V Sport 2 Suomi</channel>
<channel lang="fi" xmltv_id="VSportFootball.se" site_id="fi#0269">V Sport Football</channel>
<channel lang="fi" xmltv_id="VSportGolf.se" site_id="fi#0364">V Sport Golf</channel>
<channel lang="fi" xmltv_id="VSportLive1.se" site_id="fi#0255">V Sport Live 1</channel>
<channel lang="fi" xmltv_id="VSportLive2.se" site_id="fi#0256">V Sport Live 2</channel>
<channel lang="fi" xmltv_id="VSportLive3.se" site_id="fi#0257">V Sport Live 3</channel>
<channel lang="fi" xmltv_id="VSportLive4.se" site_id="fi#0258">V Sport Live 4</channel>
<channel lang="fi" xmltv_id="VSportLive5.se" site_id="fi#0259">V Sport Live 5</channel>
<channel lang="fi" xmltv_id="VSportPlusFinland.fi" site_id="fi#0369">V Sport + Suomi</channel>
<channel lang="fi" xmltv_id="VSportPremium.se" site_id="fi#527">V Sport Premium</channel>
<channel lang="fi" xmltv_id="VSportUltraHD.se" site_id="fi#418">V Sport Ultra HD</channel>
<channel lang="fi" xmltv_id="VSportVinter.se" site_id="fi#0363">V Sport Vinter</channel>
</channels>
</site>
<channels>
<channel site="allente.se" lang="fi" xmltv_id="BoomerangNordic.uk" site_id="fi#0017">Boomerang</channel>
<channel site="allente.se" lang="fi" xmltv_id="CartoonNetworkScandinavia.uk" site_id="fi#0028">Cartoon Network</channel>
<channel site="allente.se" lang="fi" xmltv_id="CNBCEurope.uk" site_id="fi#0032">CNBC</channel>
<channel site="allente.se" lang="fi" xmltv_id="CNNInternationalEurope.us" site_id="fi#0033">CNN</channel>
<channel site="allente.se" lang="fi" xmltv_id="DisneyChannelScandinavia.uk" site_id="fi#0037">Disney Channel</channel>
<channel site="allente.se" lang="fi" xmltv_id="DisneyJuniorScandinavia.uk" site_id="fi#0307">Disney Junior</channel>
<channel site="allente.se" lang="fi" xmltv_id="EEurope.us" site_id="fi#0052">E!</channel>
<channel site="allente.se" lang="fi" xmltv_id="MTV00s.uk" site_id="fi#0246">MTV 00s</channel>
<channel site="allente.se" lang="fi" xmltv_id="MTVGlobal.uk" site_id="fi#0080">MTV</channel>
<channel site="allente.se" lang="fi" xmltv_id="NationalGeographicFinland.fi" site_id="fi#0084">National Geographic</channel>
<channel site="allente.se" lang="fi" xmltv_id="NationalGeographicWildFinland.fi" site_id="fi#558">National Geographic Wild</channel>
<channel site="allente.se" lang="fi" xmltv_id="NickJrScandinavia.nl" site_id="fi#0088">Nick Jr</channel>
<channel site="allente.se" lang="fi" xmltv_id="TV3.se" site_id="fi#0290">TV 3 Sverige</channel>
<channel site="allente.se" lang="fi" xmltv_id="TV6Sweden.se" site_id="fi#0360">TV 6 Sverige</channel>
<channel site="allente.se" lang="fi" xmltv_id="VFilmAction.se" site_id="fi#0299">V Film Action</channel>
<channel site="allente.se" lang="fi" xmltv_id="VFilmFamily.se" site_id="fi#0308">V Film Family</channel>
<channel site="allente.se" lang="fi" xmltv_id="VFilmHits.se" site_id="fi#0322">V Film Hits</channel>
<channel site="allente.se" lang="fi" xmltv_id="VFilmPremiere.se" site_id="fi#0321">V Film Premiere</channel>
<channel site="allente.se" lang="fi" xmltv_id="ViasatExplore.se" site_id="fi#0252">Viasat Explore</channel>
<channel site="allente.se" lang="fi" xmltv_id="ViasatHistory.se" site_id="fi#0263">Viasat History HD</channel>
<channel site="allente.se" lang="fi" xmltv_id="ViasatNature.se" site_id="fi#0250">Viasat Nature</channel>
<channel site="allente.se" lang="fi" xmltv_id="VSport1Finland.fi" site_id="fi#0159">V Sport 1 Suomi</channel>
<channel site="allente.se" lang="fi" xmltv_id="VSport1Sweden.se" site_id="fi#0362">V Sport 1 Sverige</channel>
<channel site="allente.se" lang="fi" xmltv_id="VSport2Finland.fi" site_id="fi#488">V Sport 2 Suomi</channel>
<channel site="allente.se" lang="fi" xmltv_id="VSportFootball.se" site_id="fi#0269">V Sport Football</channel>
<channel site="allente.se" lang="fi" xmltv_id="VSportGolf.se" site_id="fi#0364">V Sport Golf</channel>
<channel site="allente.se" lang="fi" xmltv_id="VSportLive1.se" site_id="fi#0255">V Sport Live 1</channel>
<channel site="allente.se" lang="fi" xmltv_id="VSportLive2.se" site_id="fi#0256">V Sport Live 2</channel>
<channel site="allente.se" lang="fi" xmltv_id="VSportLive3.se" site_id="fi#0257">V Sport Live 3</channel>
<channel site="allente.se" lang="fi" xmltv_id="VSportLive4.se" site_id="fi#0258">V Sport Live 4</channel>
<channel site="allente.se" lang="fi" xmltv_id="VSportLive5.se" site_id="fi#0259">V Sport Live 5</channel>
<channel site="allente.se" lang="fi" xmltv_id="VSportPlusFinland.fi" site_id="fi#0369">V Sport + Suomi</channel>
<channel site="allente.se" lang="fi" xmltv_id="VSportPremium.se" site_id="fi#527">V Sport Premium</channel>
<channel site="allente.se" lang="fi" xmltv_id="VSportUltraHD.se" site_id="fi#418">V Sport Ultra HD</channel>
<channel site="allente.se" lang="fi" xmltv_id="VSportVinter.se" site_id="fi#0363">V Sport Vinter</channel>
</channels>

View file

@ -1,75 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="allente.se">
<channels>
<channel lang="no" xmltv_id="AlJazeeraEnglish.qa" site_id="no#0344">Aljazeera</channel>
<channel lang="no" xmltv_id="BBCWorldNewsEurope.uk" site_id="no#0016">BBC World News</channel>
<channel lang="no" xmltv_id="CartoonNetworkScandinavia.uk" site_id="no#0028">Cartoon Network</channel>
<channel lang="no" xmltv_id="CNBCEurope.uk" site_id="no#0032">CNBC</channel>
<channel lang="no" xmltv_id="CNNInternationalEurope.us" site_id="no#0033">CNN</channel>
<channel lang="no" xmltv_id="DiscoveryChannelNorway.no" site_id="no#532">Discovery Channel</channel>
<channel lang="no" xmltv_id="DisneyChannelScandinavia.uk" site_id="no#0037">Disney Channel</channel>
<channel lang="no" xmltv_id="DisneyJuniorScandinavia.uk" site_id="no#0307">Disney Junior</channel>
<channel lang="no" xmltv_id="DR2.dk" site_id="no#0051">DR 2</channel>
<channel lang="no" xmltv_id="EEurope.us" site_id="no#0052">E!</channel>
<channel lang="no" xmltv_id="EuronewsEnglish.fr" site_id="no#0281">EuroNews</channel>
<channel lang="no" xmltv_id="Eurosport1Norway.no" site_id="no#531">Eurosport 1</channel>
<channel lang="no" xmltv_id="EurosportNorway.no" site_id="no#530">Eurosport</channel>
<channel lang="no" xmltv_id="FEM.no" site_id="no#0056">FEM</channel>
<channel lang="no" xmltv_id="Kunskapskanalen.se" site_id="no#0149">Kunskapskanalen</channel>
<channel lang="no" xmltv_id="Matkanalen.no" site_id="no#565">Matkanalen</channel>
<channel lang="no" xmltv_id="MAX.no" site_id="no#533">Max</channel>
<channel lang="no" xmltv_id="MTV00s.uk" site_id="no#0246">MTV 00s</channel>
<channel lang="no" xmltv_id="MTV80s.uk" site_id="no#604">MTV 80s</channel>
<channel lang="no" xmltv_id="MTVHitsEurope.uk" site_id="no#0077">MTV Hits</channel>
<channel lang="no" xmltv_id="MTVGlobal.uk" site_id="no#0080">MTV Nordic</channel>
<channel lang="no" xmltv_id="NationalGeographicNorway.no" site_id="no#0316">National Geographic</channel>
<channel lang="no" xmltv_id="NationalGeographicWildNorway.no" site_id="no#558">National Geographic Wild</channel>
<channel lang="no" xmltv_id="NFLNetwork.us" site_id="no#556">NFL Network</channel>
<channel lang="no" xmltv_id="NickelodeonNorway.no" site_id="no#0087">Nickelodeon</channel>
<channel lang="no" xmltv_id="NickJrScandinavia.nl" site_id="no#0088">Nick Jr</channel>
<channel lang="no" xmltv_id="NicktoonsScandinavia.nl" site_id="no#570">Nicktoons</channel>
<channel lang="no" xmltv_id="NRK1.no" site_id="no#0090">NRK1</channel>
<channel lang="no" xmltv_id="NRK2.no" site_id="no#0288">NRK2</channel>
<channel lang="no" xmltv_id="NRK3.no" site_id="no#0289">NRK3</channel>
<channel lang="no" xmltv_id="SkyNewsInternational.uk" site_id="no#0008">Sky News International</channel>
<channel lang="no" xmltv_id="SVT1.se" site_id="no#0121">SVT 1</channel>
<channel lang="no" xmltv_id="SVT2.se" site_id="no#0141">SVT 2</channel>
<channel lang="no" xmltv_id="SVT24.se" site_id="no#598">SVT 24</channel>
<channel lang="no" xmltv_id="SVTBarn.se" site_id="no#0147">SVT Barn</channel>
<channel lang="no" xmltv_id="TV2.dk" site_id="no#0188">TV 2</channel>
<channel lang="no" xmltv_id="TV2.no" site_id="no#0187">TV 2</channel>
<channel lang="no" xmltv_id="TV2Livsstil.no" site_id="no#0277">TV 2 Livsstil</channel>
<channel lang="no" xmltv_id="TV2Nyhetskanalen.no" site_id="no#457">TV 2 Nyhetskanalen</channel>
<channel lang="no" xmltv_id="TV2Sport1.no" site_id="no#0199">TV 2 Sport 1</channel>
<channel lang="no" xmltv_id="TV2Sport2.no" site_id="no#0406">TV 2 Sport 2</channel>
<channel lang="no" xmltv_id="TV2SportPremium.no" site_id="no#0197">TV 2 Sport Premium</channel>
<channel lang="no" xmltv_id="TV2Zebra.no" site_id="no#0405">TV 2 Zebra</channel>
<channel lang="no" xmltv_id="TV3Danmark.dk" site_id="no#0359">TV 3 Danmark</channel>
<channel lang="no" xmltv_id="TV3Norway.no" site_id="no#0298">TV 3 Norge</channel>
<channel lang="no" xmltv_id="TV3.se" site_id="no#0222">TV 3 Sverige</channel>
<channel lang="no" xmltv_id="TV6Norway.no" site_id="no#0206">TV 6 Norge</channel>
<channel lang="no" xmltv_id="TV6Sweden.se" site_id="no#0360">TV 6 Sverige</channel>
<channel lang="no" xmltv_id="TVNorge.no" site_id="no#534">TV Norge</channel>
<channel lang="no" xmltv_id="V4.no" site_id="no#0361">V 4</channel>
<channel lang="no" xmltv_id="VFilmAction.se" site_id="no#0299">V Film Action</channel>
<channel lang="no" xmltv_id="VFilmFamily.se" site_id="no#0308">V Film Family</channel>
<channel lang="no" xmltv_id="VFilmHits.se" site_id="no#0322">V Film Hits</channel>
<channel lang="no" xmltv_id="VFilmPremiere.se" site_id="no#0321">V Film Premiere</channel>
<channel lang="no" xmltv_id="ViasatExplore.se" site_id="no#0358">Viasat Explore</channel>
<channel lang="no" xmltv_id="ViasatHistory.se" site_id="no#0357">Viasat History HD</channel>
<channel lang="no" xmltv_id="ViasatNature.se" site_id="no#0250">Viasat Nature</channel>
<channel lang="no" xmltv_id="VOX.no" site_id="no#535">Vox</channel>
<channel lang="no" xmltv_id="VSeries.se" site_id="no#0320">V Series</channel>
<channel lang="no" xmltv_id="VSport1Norway.no" site_id="no#0365">V Sport 1</channel>
<channel lang="no" xmltv_id="VSport2.no" site_id="no#608">V Sport 2</channel>
<channel lang="no" xmltv_id="VSport3.no" site_id="no#609">V Sport 3</channel>
<channel lang="no" xmltv_id="VSportGolf.se" site_id="no#0364">V Sport Golf</channel>
<channel lang="no" xmltv_id="VSportLive1.se" site_id="no#0255">V Sport Live 1</channel>
<channel lang="no" xmltv_id="VSportLive2.se" site_id="no#0256">V Sport Live 2</channel>
<channel lang="no" xmltv_id="VSportLive3.se" site_id="no#0257">V Sport Live 3</channel>
<channel lang="no" xmltv_id="VSportLive4.se" site_id="no#0258">V Sport Live 4</channel>
<channel lang="no" xmltv_id="VSportLive5.se" site_id="no#0259">V Sport Live 5</channel>
<channel lang="no" xmltv_id="VSportPlus.no" site_id="no#0271">V Sport +</channel>
<channel lang="no" xmltv_id="VSportUltraHD.se" site_id="no#418">V Sport Ultra HD</channel>
</channels>
</site>
<channels>
<channel site="allente.se" lang="no" xmltv_id="AlJazeeraEnglish.qa" site_id="no#0344">Aljazeera</channel>
<channel site="allente.se" lang="no" xmltv_id="BBCWorldNewsEurope.uk" site_id="no#0016">BBC World News</channel>
<channel site="allente.se" lang="no" xmltv_id="CartoonNetworkScandinavia.uk" site_id="no#0028">Cartoon Network</channel>
<channel site="allente.se" lang="no" xmltv_id="CNBCEurope.uk" site_id="no#0032">CNBC</channel>
<channel site="allente.se" lang="no" xmltv_id="CNNInternationalEurope.us" site_id="no#0033">CNN</channel>
<channel site="allente.se" lang="no" xmltv_id="DiscoveryChannelNorway.no" site_id="no#532">Discovery Channel</channel>
<channel site="allente.se" lang="no" xmltv_id="DisneyChannelScandinavia.uk" site_id="no#0037">Disney Channel</channel>
<channel site="allente.se" lang="no" xmltv_id="DisneyJuniorScandinavia.uk" site_id="no#0307">Disney Junior</channel>
<channel site="allente.se" lang="no" xmltv_id="DR2.dk" site_id="no#0051">DR 2</channel>
<channel site="allente.se" lang="no" xmltv_id="EEurope.us" site_id="no#0052">E!</channel>
<channel site="allente.se" lang="no" xmltv_id="EuronewsEnglish.fr" site_id="no#0281">EuroNews</channel>
<channel site="allente.se" lang="no" xmltv_id="Eurosport1Norway.no" site_id="no#531">Eurosport 1</channel>
<channel site="allente.se" lang="no" xmltv_id="EurosportNorway.no" site_id="no#530">Eurosport</channel>
<channel site="allente.se" lang="no" xmltv_id="FEM.no" site_id="no#0056">FEM</channel>
<channel site="allente.se" lang="no" xmltv_id="Kunskapskanalen.se" site_id="no#0149">Kunskapskanalen</channel>
<channel site="allente.se" lang="no" xmltv_id="Matkanalen.no" site_id="no#565">Matkanalen</channel>
<channel site="allente.se" lang="no" xmltv_id="MAX.no" site_id="no#533">Max</channel>
<channel site="allente.se" lang="no" xmltv_id="MTV00s.uk" site_id="no#0246">MTV 00s</channel>
<channel site="allente.se" lang="no" xmltv_id="MTV80s.uk" site_id="no#604">MTV 80s</channel>
<channel site="allente.se" lang="no" xmltv_id="MTVHitsEurope.uk" site_id="no#0077">MTV Hits</channel>
<channel site="allente.se" lang="no" xmltv_id="MTVGlobal.uk" site_id="no#0080">MTV Nordic</channel>
<channel site="allente.se" lang="no" xmltv_id="NationalGeographicNorway.no" site_id="no#0316">National Geographic</channel>
<channel site="allente.se" lang="no" xmltv_id="NationalGeographicWildNorway.no" site_id="no#558">National Geographic Wild</channel>
<channel site="allente.se" lang="no" xmltv_id="NFLNetwork.us" site_id="no#556">NFL Network</channel>
<channel site="allente.se" lang="no" xmltv_id="NickelodeonNorway.no" site_id="no#0087">Nickelodeon</channel>
<channel site="allente.se" lang="no" xmltv_id="NickJrScandinavia.nl" site_id="no#0088">Nick Jr</channel>
<channel site="allente.se" lang="no" xmltv_id="NicktoonsScandinavia.nl" site_id="no#570">Nicktoons</channel>
<channel site="allente.se" lang="no" xmltv_id="NRK1.no" site_id="no#0090">NRK1</channel>
<channel site="allente.se" lang="no" xmltv_id="NRK2.no" site_id="no#0288">NRK2</channel>
<channel site="allente.se" lang="no" xmltv_id="NRK3.no" site_id="no#0289">NRK3</channel>
<channel site="allente.se" lang="no" xmltv_id="SkyNewsInternational.uk" site_id="no#0008">Sky News International</channel>
<channel site="allente.se" lang="no" xmltv_id="SVT1.se" site_id="no#0121">SVT 1</channel>
<channel site="allente.se" lang="no" xmltv_id="SVT2.se" site_id="no#0141">SVT 2</channel>
<channel site="allente.se" lang="no" xmltv_id="SVT24.se" site_id="no#598">SVT 24</channel>
<channel site="allente.se" lang="no" xmltv_id="SVTBarn.se" site_id="no#0147">SVT Barn</channel>
<channel site="allente.se" lang="no" xmltv_id="TV2.dk" site_id="no#0188">TV 2</channel>
<channel site="allente.se" lang="no" xmltv_id="TV2.no" site_id="no#0187">TV 2</channel>
<channel site="allente.se" lang="no" xmltv_id="TV2Livsstil.no" site_id="no#0277">TV 2 Livsstil</channel>
<channel site="allente.se" lang="no" xmltv_id="TV2Nyhetskanalen.no" site_id="no#457">TV 2 Nyhetskanalen</channel>
<channel site="allente.se" lang="no" xmltv_id="TV2Sport1.no" site_id="no#0199">TV 2 Sport 1</channel>
<channel site="allente.se" lang="no" xmltv_id="TV2Sport2.no" site_id="no#0406">TV 2 Sport 2</channel>
<channel site="allente.se" lang="no" xmltv_id="TV2SportPremium.no" site_id="no#0197">TV 2 Sport Premium</channel>
<channel site="allente.se" lang="no" xmltv_id="TV2Zebra.no" site_id="no#0405">TV 2 Zebra</channel>
<channel site="allente.se" lang="no" xmltv_id="TV3Danmark.dk" site_id="no#0359">TV 3 Danmark</channel>
<channel site="allente.se" lang="no" xmltv_id="TV3Norway.no" site_id="no#0298">TV 3 Norge</channel>
<channel site="allente.se" lang="no" xmltv_id="TV3.se" site_id="no#0222">TV 3 Sverige</channel>
<channel site="allente.se" lang="no" xmltv_id="TV6Norway.no" site_id="no#0206">TV 6 Norge</channel>
<channel site="allente.se" lang="no" xmltv_id="TV6Sweden.se" site_id="no#0360">TV 6 Sverige</channel>
<channel site="allente.se" lang="no" xmltv_id="TVNorge.no" site_id="no#534">TV Norge</channel>
<channel site="allente.se" lang="no" xmltv_id="V4.no" site_id="no#0361">V 4</channel>
<channel site="allente.se" lang="no" xmltv_id="VFilmAction.se" site_id="no#0299">V Film Action</channel>
<channel site="allente.se" lang="no" xmltv_id="VFilmFamily.se" site_id="no#0308">V Film Family</channel>
<channel site="allente.se" lang="no" xmltv_id="VFilmHits.se" site_id="no#0322">V Film Hits</channel>
<channel site="allente.se" lang="no" xmltv_id="VFilmPremiere.se" site_id="no#0321">V Film Premiere</channel>
<channel site="allente.se" lang="no" xmltv_id="ViasatExplore.se" site_id="no#0358">Viasat Explore</channel>
<channel site="allente.se" lang="no" xmltv_id="ViasatHistory.se" site_id="no#0357">Viasat History HD</channel>
<channel site="allente.se" lang="no" xmltv_id="ViasatNature.se" site_id="no#0250">Viasat Nature</channel>
<channel site="allente.se" lang="no" xmltv_id="VOX.no" site_id="no#535">Vox</channel>
<channel site="allente.se" lang="no" xmltv_id="VSeries.se" site_id="no#0320">V Series</channel>
<channel site="allente.se" lang="no" xmltv_id="VSport1Norway.no" site_id="no#0365">V Sport 1</channel>
<channel site="allente.se" lang="no" xmltv_id="VSport2.no" site_id="no#608">V Sport 2</channel>
<channel site="allente.se" lang="no" xmltv_id="VSport3.no" site_id="no#609">V Sport 3</channel>
<channel site="allente.se" lang="no" xmltv_id="VSportGolf.se" site_id="no#0364">V Sport Golf</channel>
<channel site="allente.se" lang="no" xmltv_id="VSportLive1.se" site_id="no#0255">V Sport Live 1</channel>
<channel site="allente.se" lang="no" xmltv_id="VSportLive2.se" site_id="no#0256">V Sport Live 2</channel>
<channel site="allente.se" lang="no" xmltv_id="VSportLive3.se" site_id="no#0257">V Sport Live 3</channel>
<channel site="allente.se" lang="no" xmltv_id="VSportLive4.se" site_id="no#0258">V Sport Live 4</channel>
<channel site="allente.se" lang="no" xmltv_id="VSportLive5.se" site_id="no#0259">V Sport Live 5</channel>
<channel site="allente.se" lang="no" xmltv_id="VSportPlus.no" site_id="no#0271">V Sport +</channel>
<channel site="allente.se" lang="no" xmltv_id="VSportUltraHD.se" site_id="no#418">V Sport Ultra HD</channel>
</channels>

View file

@ -1,114 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="allente.se">
<channels>
<channel lang="sv" xmltv_id="AlJazeeraEnglish.qa" site_id="se#0344">Aljazeera</channel>
<channel lang="sv" xmltv_id="AnimalPlanetSweden.se" site_id="se#1005">Animal Planet Sverige</channel>
<channel lang="sv" xmltv_id="ATGLive.se" site_id="se#1000">ATG Live</channel>
<channel lang="sv" xmltv_id="BBCEarthNordic.uk" site_id="se#1018">BBC Earth HD</channel>
<channel lang="sv" xmltv_id="BBCBritNordic.uk" site_id="se#1016">BBC Brit HD</channel>
<channel lang="sv" xmltv_id="BBCWorldNewsEurope.uk" site_id="se#0016">BBC World News</channel>
<channel lang="sv" xmltv_id="BloombergTVEurope.uk" site_id="se#1008">Bloomberg TV</channel>
<channel lang="sv" xmltv_id="BoomerangNordic.uk" site_id="se#0017">Boomerang</channel>
<channel lang="sv" xmltv_id="CartoonNetworkScandinavia.uk" site_id="se#0028">Cartoon Network</channel>
<channel lang="sv" xmltv_id="CMoreFirst.se" site_id="se#968">C More First</channel>
<channel lang="sv" xmltv_id="CMoreFotboll.se" site_id="se#657">C More Fotboll</channel>
<channel lang="sv" xmltv_id="CMoreHits.se" site_id="se#969">C More Hits</channel>
<channel lang="sv" xmltv_id="CMoreHockey.se" site_id="se#656">C More Hockey</channel>
<channel lang="sv" xmltv_id="CMoreLive.se" site_id="se#659">C More Live</channel>
<channel lang="sv" xmltv_id="CMoreLive2.se" site_id="se#660">C More Live 2</channel>
<channel lang="sv" xmltv_id="CMoreLive3.se" site_id="se#661">C More Live 3</channel>
<channel lang="sv" xmltv_id="CMoreLive4.se" site_id="se#662">C More Live 4</channel>
<channel lang="sv" xmltv_id="CMoreLive5.se" site_id="se#663">C More Live 5</channel>
<channel lang="sv" xmltv_id="CMoreMix.se" site_id="se#658">C More Mix</channel>
<channel lang="sv" xmltv_id="CMoreSeries.se" site_id="se#971">C More Series</channel>
<channel lang="sv" xmltv_id="CMoreStars.se" site_id="se#970">C More Stars</channel>
<channel lang="sv" xmltv_id="CNBCEurope.uk" site_id="se#0032">CNBC</channel>
<channel lang="sv" xmltv_id="CNNInternationalEurope.us" site_id="se#0033">CNN</channel>
<channel lang="sv" xmltv_id="DiscoveryChannelSweden.se" site_id="se#493">Discovery Channel Sverige</channel>
<channel lang="sv" xmltv_id="DiscoveryScienceSweden.se" site_id="se#1006">Discovery Science Sverige</channel>
<channel lang="sv" xmltv_id="DisneyChannelScandinavia.uk" site_id="se#0037">Disney Channel</channel>
<channel lang="sv" xmltv_id="DisneyJuniorScandinavia.uk" site_id="se#0307">Disney Junior</channel>
<channel lang="sv" xmltv_id="DiscoveryPlusExtra1.se" site_id="se#637">Discovery+ Extra 1</channel>
<channel lang="sv" xmltv_id="DiscoveryPlusExtra2.se" site_id="se#638">Discovery+ Extra 2</channel>
<channel lang="sv" xmltv_id="DiscoveryPlusExtra3.se" site_id="se#639">Discovery+ Extra 3</channel>
<channel lang="sv" xmltv_id="DiscoveryPlusExtra4.se" site_id="se#640">Discovery+ Extra 4</channel>
<channel lang="sv" xmltv_id="DiscoveryPlusExtra5.se" site_id="se#641">Discovery+ Extra 5</channel>
<channel lang="sv" xmltv_id="DiscoveryPlusExtra6.se" site_id="se#642">Discovery+ Extra 6</channel>
<channel lang="sv" xmltv_id="DiscoveryPlusExtra7.se" site_id="se#643">Discovery+ Extra 7</channel>
<channel lang="sv" xmltv_id="DiscoveryPlusExtra8.se" site_id="se#644">Discovery+ Extra 8</channel>
<channel lang="sv" xmltv_id="DR1.dk" site_id="se#452">DR 1</channel>
<channel lang="sv" xmltv_id="DR2.dk" site_id="se#0051">DR 2</channel>
<channel lang="sv" xmltv_id="DRRamasjang.dk" site_id="se#0048">DR Ramasjang</channel>
<channel lang="sv" xmltv_id="EEurope.us" site_id="se#0052">E!</channel>
<channel lang="sv" xmltv_id="EuronewsEnglish.fr" site_id="se#0281">EuroNews</channel>
<channel lang="sv" xmltv_id="Eurosport1.fr" site_id="se#1023">Eurosport 1</channel>
<channel lang="sv" xmltv_id="Eurosport2.fr" site_id="se#1024">Eurosport 2</channel>
<channel lang="sv" xmltv_id="Godare.se" site_id="se#722">Godare</channel>
<channel lang="sv" xmltv_id="GodTV.uk" site_id="se#0058">God TV</channel>
<channel lang="sv" xmltv_id="HistorySweden.se" site_id="se#652">History</channel>
<channel lang="sv" xmltv_id="History2Nordic.us" site_id="se#1004">H2</channel>
<channel lang="sv" xmltv_id="HorseCountryTV.uk" site_id="se#668">Horse &amp; Country TV</channel>
<channel lang="sv" xmltv_id="InvestigationDiscoverySweden.se" site_id="se#1039">Investigation Discovery Sverige</channel>
<channel lang="sv" xmltv_id="Kanal5.se" site_id="se#0279">Kanal 5</channel>
<channel lang="sv" xmltv_id="Kanal9.se" site_id="se#474">Kanal 9</channel>
<channel lang="sv" xmltv_id="Kanal11.se" site_id="se#0235">Kanal 11</channel>
<channel lang="sv" xmltv_id="Kunskapskanalen.se" site_id="se#0149">Kunskapskanalen</channel>
<channel lang="sv" xmltv_id="MTV00s.uk" site_id="se#0246">MTV 00s</channel>
<channel lang="sv" xmltv_id="MTV80s.uk" site_id="se#0099">MTV 80s</channel>
<channel lang="sv" xmltv_id="MTVHitsEurope.uk" site_id="se#0077">MTV Hits</channel>
<channel lang="sv" xmltv_id="MTVGlobal.uk" site_id="se#0080">MTV Nordic</channel>
<channel lang="sv" xmltv_id="MotorvisionTV.de" site_id="se#1009">Motorvision</channel>
<channel lang="sv" xmltv_id="NationalGeographicSweden.se" site_id="se#0084">National Geographic</channel>
<channel lang="sv" xmltv_id="NationalGeographicWildSweden.se" site_id="se#0082">National Geographic Wild</channel>
<channel lang="sv" xmltv_id="NFLNetwork.us" site_id="se#569">NFL Network</channel>
<channel lang="sv" xmltv_id="NickelodeonScandinavia.nl" site_id="se#0086">Nickelodeon</channel>
<channel lang="sv" xmltv_id="NickJrScandinavia.nl" site_id="se#0088">Nick Jr</channel>
<channel lang="sv" xmltv_id="NicktoonsScandinavia.nl" site_id="se#570">Nicktoons</channel>
<channel lang="sv" xmltv_id="NRK1.no" site_id="se#0090">NRK1</channel>
<channel lang="sv" xmltv_id="NRK2.no" site_id="se#0288">NRK2</channel>
<channel lang="sv" xmltv_id="NRK3.no" site_id="se#0289">NRK3</channel>
<channel lang="sv" xmltv_id="ParamountNetworkSweden.se" site_id="se#0034">Paramount Network</channel>
<channel lang="sv" xmltv_id="ParamountPlusMovies.se" site_id="se#1001">Paramount+ Movies</channel>
<channel lang="sv" xmltv_id="ParamountPlusSeries.se" site_id="se#1002">Paramount+ Series</channel>
<channel lang="sv" xmltv_id="SFkanalen.se" site_id="se#972">SF-kanalen</channel>
<channel lang="sv" xmltv_id="Sjuan.se" site_id="se#0232">Sjuan</channel>
<channel lang="sv" xmltv_id="SkyNewsInternational.uk" site_id="se#596">Sky News International</channel>
<channel lang="sv" xmltv_id="Sportkanalen.se" site_id="se#0325">Sportkanalen</channel>
<channel lang="sv" xmltv_id="SVT1.se" site_id="se#0148">SVT 1</channel>
<channel lang="sv" xmltv_id="SVT2.se" site_id="se#0282">SVT 2</channel>
<channel lang="sv" xmltv_id="SVT24.se" site_id="se#146">SVT 24</channel>
<channel lang="sv" xmltv_id="SVTBarn.se" site_id="se#0147">SVT Barn</channel>
<channel lang="sv" xmltv_id="TLCSweden.se" site_id="se#1038">TLC Sverige</channel>
<channel lang="sv" xmltv_id="TV2.dk" site_id="se#0297">TV 2</channel>
<channel lang="sv" xmltv_id="TV3.se" site_id="se#0290">TV 3</channel>
<channel lang="sv" xmltv_id="TV4.se" site_id="se#0227">TV 4</channel>
<channel lang="sv" xmltv_id="TV4Fakta.se" site_id="se#0228">TV 4 Fakta</channel>
<channel lang="sv" xmltv_id="TV4Film.se" site_id="se#0229">TV 4 Film</channel>
<channel lang="sv" xmltv_id="TV4Guld.se" site_id="se#0230">TV 4 Guld</channel>
<channel lang="sv" xmltv_id="TV6Sweden.se" site_id="se#0360">TV 6</channel>
<channel lang="sv" xmltv_id="TV8Sweden.se" site_id="se#666">TV 8</channel>
<channel lang="sv" xmltv_id="TV10.se" site_id="se#667">TV 10</channel>
<channel lang="sv" xmltv_id="TV12.se" site_id="se#664">TV 12</channel>
<channel lang="sv" xmltv_id="VFilmAction.se" site_id="se#0299">V Film Action</channel>
<channel lang="sv" xmltv_id="VFilmFamily.se" site_id="se#0308">V Film Family</channel>
<channel lang="sv" xmltv_id="VFilmHits.se" site_id="se#0322">V Film Hits</channel>
<channel lang="sv" xmltv_id="VFilmPremiere.se" site_id="se#0321">V Film Premiere</channel>
<channel lang="sv" xmltv_id="ViasatExplore.se" site_id="se#0358">Viasat Explore</channel>
<channel lang="sv" xmltv_id="ViasatHistory.se" site_id="se#0357">Viasat History HD</channel>
<channel lang="sv" xmltv_id="ViasatNature.se" site_id="se#0356">Viasat Nature</channel>
<channel lang="sv" xmltv_id="VSeries.se" site_id="se#0320">V Series</channel>
<channel lang="sv" xmltv_id="VSport1Sweden.se" site_id="se#0362">V Sport 1</channel>
<channel lang="sv" xmltv_id="VSportExtra.se" site_id="se#715">V Sport Extra</channel>
<channel lang="sv" xmltv_id="VSportFootball.se" site_id="se#0269">V Sport Football</channel>
<channel lang="sv" xmltv_id="VSportGolf.se" site_id="se#0364">V Sport Golf</channel>
<channel lang="sv" xmltv_id="VSportLive1.se" site_id="se#0255">V Sport Live 1</channel>
<channel lang="sv" xmltv_id="VSportLive2.se" site_id="se#0256">V Sport Live 2</channel>
<channel lang="sv" xmltv_id="VSportLive3.se" site_id="se#0257">V Sport Live 3</channel>
<channel lang="sv" xmltv_id="VSportLive4.se" site_id="se#0258">V Sport Live 4</channel>
<channel lang="sv" xmltv_id="VSportLive5.se" site_id="se#0259">V Sport Live 5</channel>
<channel lang="sv" xmltv_id="VSportMotor.se" site_id="se#0292">V Sport Motor</channel>
<channel lang="sv" xmltv_id="VSportPremium.se" site_id="se#527">V Sport Premium</channel>
<channel lang="sv" xmltv_id="VSportUltraHD.se" site_id="se#418">V Sport Ultra HD</channel>
<channel lang="sv" xmltv_id="VSportVinter.se" site_id="se#0363">V Sport Vinter</channel>
</channels>
</site>
<channels>
<channel site="allente.se" lang="sv" xmltv_id="AlJazeeraEnglish.qa" site_id="se#0344">Aljazeera</channel>
<channel site="allente.se" lang="sv" xmltv_id="AnimalPlanetSweden.se" site_id="se#1005">Animal Planet Sverige</channel>
<channel site="allente.se" lang="sv" xmltv_id="ATGLive.se" site_id="se#1000">ATG Live</channel>
<channel site="allente.se" lang="sv" xmltv_id="BBCEarthNordic.uk" site_id="se#1018">BBC Earth HD</channel>
<channel site="allente.se" lang="sv" xmltv_id="BBCBritNordic.uk" site_id="se#1016">BBC Brit HD</channel>
<channel site="allente.se" lang="sv" xmltv_id="BBCWorldNewsEurope.uk" site_id="se#0016">BBC World News</channel>
<channel site="allente.se" lang="sv" xmltv_id="BloombergTVEurope.uk" site_id="se#1008">Bloomberg TV</channel>
<channel site="allente.se" lang="sv" xmltv_id="BoomerangNordic.uk" site_id="se#0017">Boomerang</channel>
<channel site="allente.se" lang="sv" xmltv_id="CartoonNetworkScandinavia.uk" site_id="se#0028">Cartoon Network</channel>
<channel site="allente.se" lang="sv" xmltv_id="CMoreFirst.se" site_id="se#968">C More First</channel>
<channel site="allente.se" lang="sv" xmltv_id="CMoreFotboll.se" site_id="se#657">C More Fotboll</channel>
<channel site="allente.se" lang="sv" xmltv_id="CMoreHits.se" site_id="se#969">C More Hits</channel>
<channel site="allente.se" lang="sv" xmltv_id="CMoreHockey.se" site_id="se#656">C More Hockey</channel>
<channel site="allente.se" lang="sv" xmltv_id="CMoreLive.se" site_id="se#659">C More Live</channel>
<channel site="allente.se" lang="sv" xmltv_id="CMoreLive2.se" site_id="se#660">C More Live 2</channel>
<channel site="allente.se" lang="sv" xmltv_id="CMoreLive3.se" site_id="se#661">C More Live 3</channel>
<channel site="allente.se" lang="sv" xmltv_id="CMoreLive4.se" site_id="se#662">C More Live 4</channel>
<channel site="allente.se" lang="sv" xmltv_id="CMoreLive5.se" site_id="se#663">C More Live 5</channel>
<channel site="allente.se" lang="sv" xmltv_id="CMoreMix.se" site_id="se#658">C More Mix</channel>
<channel site="allente.se" lang="sv" xmltv_id="CMoreSeries.se" site_id="se#971">C More Series</channel>
<channel site="allente.se" lang="sv" xmltv_id="CMoreStars.se" site_id="se#970">C More Stars</channel>
<channel site="allente.se" lang="sv" xmltv_id="CNBCEurope.uk" site_id="se#0032">CNBC</channel>
<channel site="allente.se" lang="sv" xmltv_id="CNNInternationalEurope.us" site_id="se#0033">CNN</channel>
<channel site="allente.se" lang="sv" xmltv_id="DiscoveryChannelSweden.se" site_id="se#493">Discovery Channel Sverige</channel>
<channel site="allente.se" lang="sv" xmltv_id="DiscoveryScienceSweden.se" site_id="se#1006">Discovery Science Sverige</channel>
<channel site="allente.se" lang="sv" xmltv_id="DisneyChannelScandinavia.uk" site_id="se#0037">Disney Channel</channel>
<channel site="allente.se" lang="sv" xmltv_id="DisneyJuniorScandinavia.uk" site_id="se#0307">Disney Junior</channel>
<channel site="allente.se" lang="sv" xmltv_id="DiscoveryPlusExtra1.se" site_id="se#637">Discovery+ Extra 1</channel>
<channel site="allente.se" lang="sv" xmltv_id="DiscoveryPlusExtra2.se" site_id="se#638">Discovery+ Extra 2</channel>
<channel site="allente.se" lang="sv" xmltv_id="DiscoveryPlusExtra3.se" site_id="se#639">Discovery+ Extra 3</channel>
<channel site="allente.se" lang="sv" xmltv_id="DiscoveryPlusExtra4.se" site_id="se#640">Discovery+ Extra 4</channel>
<channel site="allente.se" lang="sv" xmltv_id="DiscoveryPlusExtra5.se" site_id="se#641">Discovery+ Extra 5</channel>
<channel site="allente.se" lang="sv" xmltv_id="DiscoveryPlusExtra6.se" site_id="se#642">Discovery+ Extra 6</channel>
<channel site="allente.se" lang="sv" xmltv_id="DiscoveryPlusExtra7.se" site_id="se#643">Discovery+ Extra 7</channel>
<channel site="allente.se" lang="sv" xmltv_id="DiscoveryPlusExtra8.se" site_id="se#644">Discovery+ Extra 8</channel>
<channel site="allente.se" lang="sv" xmltv_id="DR1.dk" site_id="se#452">DR 1</channel>
<channel site="allente.se" lang="sv" xmltv_id="DR2.dk" site_id="se#0051">DR 2</channel>
<channel site="allente.se" lang="sv" xmltv_id="DRRamasjang.dk" site_id="se#0048">DR Ramasjang</channel>
<channel site="allente.se" lang="sv" xmltv_id="EEurope.us" site_id="se#0052">E!</channel>
<channel site="allente.se" lang="sv" xmltv_id="EuronewsEnglish.fr" site_id="se#0281">EuroNews</channel>
<channel site="allente.se" lang="sv" xmltv_id="Eurosport1.fr" site_id="se#1023">Eurosport 1</channel>
<channel site="allente.se" lang="sv" xmltv_id="Eurosport2.fr" site_id="se#1024">Eurosport 2</channel>
<channel site="allente.se" lang="sv" xmltv_id="Godare.se" site_id="se#722">Godare</channel>
<channel site="allente.se" lang="sv" xmltv_id="GodTV.uk" site_id="se#0058">God TV</channel>
<channel site="allente.se" lang="sv" xmltv_id="HistorySweden.se" site_id="se#652">History</channel>
<channel site="allente.se" lang="sv" xmltv_id="History2Nordic.us" site_id="se#1004">H2</channel>
<channel site="allente.se" lang="sv" xmltv_id="HorseCountryTV.uk" site_id="se#668">Horse &amp; Country TV</channel>
<channel site="allente.se" lang="sv" xmltv_id="InvestigationDiscoverySweden.se" site_id="se#1039">Investigation Discovery Sverige</channel>
<channel site="allente.se" lang="sv" xmltv_id="Kanal5.se" site_id="se#0279">Kanal 5</channel>
<channel site="allente.se" lang="sv" xmltv_id="Kanal9.se" site_id="se#474">Kanal 9</channel>
<channel site="allente.se" lang="sv" xmltv_id="Kanal11.se" site_id="se#0235">Kanal 11</channel>
<channel site="allente.se" lang="sv" xmltv_id="Kunskapskanalen.se" site_id="se#0149">Kunskapskanalen</channel>
<channel site="allente.se" lang="sv" xmltv_id="MTV00s.uk" site_id="se#0246">MTV 00s</channel>
<channel site="allente.se" lang="sv" xmltv_id="MTV80s.uk" site_id="se#0099">MTV 80s</channel>
<channel site="allente.se" lang="sv" xmltv_id="MTVHitsEurope.uk" site_id="se#0077">MTV Hits</channel>
<channel site="allente.se" lang="sv" xmltv_id="MTVGlobal.uk" site_id="se#0080">MTV Nordic</channel>
<channel site="allente.se" lang="sv" xmltv_id="MotorvisionTV.de" site_id="se#1009">Motorvision</channel>
<channel site="allente.se" lang="sv" xmltv_id="NationalGeographicSweden.se" site_id="se#0084">National Geographic</channel>
<channel site="allente.se" lang="sv" xmltv_id="NationalGeographicWildSweden.se" site_id="se#0082">National Geographic Wild</channel>
<channel site="allente.se" lang="sv" xmltv_id="NFLNetwork.us" site_id="se#569">NFL Network</channel>
<channel site="allente.se" lang="sv" xmltv_id="NickelodeonScandinavia.nl" site_id="se#0086">Nickelodeon</channel>
<channel site="allente.se" lang="sv" xmltv_id="NickJrScandinavia.nl" site_id="se#0088">Nick Jr</channel>
<channel site="allente.se" lang="sv" xmltv_id="NicktoonsScandinavia.nl" site_id="se#570">Nicktoons</channel>
<channel site="allente.se" lang="sv" xmltv_id="NRK1.no" site_id="se#0090">NRK1</channel>
<channel site="allente.se" lang="sv" xmltv_id="NRK2.no" site_id="se#0288">NRK2</channel>
<channel site="allente.se" lang="sv" xmltv_id="NRK3.no" site_id="se#0289">NRK3</channel>
<channel site="allente.se" lang="sv" xmltv_id="ParamountNetworkSweden.se" site_id="se#0034">Paramount Network</channel>
<channel site="allente.se" lang="sv" xmltv_id="ParamountPlusMovies.se" site_id="se#1001">Paramount+ Movies</channel>
<channel site="allente.se" lang="sv" xmltv_id="ParamountPlusSeries.se" site_id="se#1002">Paramount+ Series</channel>
<channel site="allente.se" lang="sv" xmltv_id="SFkanalen.se" site_id="se#972">SF-kanalen</channel>
<channel site="allente.se" lang="sv" xmltv_id="Sjuan.se" site_id="se#0232">Sjuan</channel>
<channel site="allente.se" lang="sv" xmltv_id="SkyNewsInternational.uk" site_id="se#596">Sky News International</channel>
<channel site="allente.se" lang="sv" xmltv_id="Sportkanalen.se" site_id="se#0325">Sportkanalen</channel>
<channel site="allente.se" lang="sv" xmltv_id="SVT1.se" site_id="se#0148">SVT 1</channel>
<channel site="allente.se" lang="sv" xmltv_id="SVT2.se" site_id="se#0282">SVT 2</channel>
<channel site="allente.se" lang="sv" xmltv_id="SVT24.se" site_id="se#146">SVT 24</channel>
<channel site="allente.se" lang="sv" xmltv_id="SVTBarn.se" site_id="se#0147">SVT Barn</channel>
<channel site="allente.se" lang="sv" xmltv_id="TLCSweden.se" site_id="se#1038">TLC Sverige</channel>
<channel site="allente.se" lang="sv" xmltv_id="TV2.dk" site_id="se#0297">TV 2</channel>
<channel site="allente.se" lang="sv" xmltv_id="TV3.se" site_id="se#0290">TV 3</channel>
<channel site="allente.se" lang="sv" xmltv_id="TV4.se" site_id="se#0227">TV 4</channel>
<channel site="allente.se" lang="sv" xmltv_id="TV4Fakta.se" site_id="se#0228">TV 4 Fakta</channel>
<channel site="allente.se" lang="sv" xmltv_id="TV4Film.se" site_id="se#0229">TV 4 Film</channel>
<channel site="allente.se" lang="sv" xmltv_id="TV4Guld.se" site_id="se#0230">TV 4 Guld</channel>
<channel site="allente.se" lang="sv" xmltv_id="TV6Sweden.se" site_id="se#0360">TV 6</channel>
<channel site="allente.se" lang="sv" xmltv_id="TV8Sweden.se" site_id="se#666">TV 8</channel>
<channel site="allente.se" lang="sv" xmltv_id="TV10.se" site_id="se#667">TV 10</channel>
<channel site="allente.se" lang="sv" xmltv_id="TV12.se" site_id="se#664">TV 12</channel>
<channel site="allente.se" lang="sv" xmltv_id="VFilmAction.se" site_id="se#0299">V Film Action</channel>
<channel site="allente.se" lang="sv" xmltv_id="VFilmFamily.se" site_id="se#0308">V Film Family</channel>
<channel site="allente.se" lang="sv" xmltv_id="VFilmHits.se" site_id="se#0322">V Film Hits</channel>
<channel site="allente.se" lang="sv" xmltv_id="VFilmPremiere.se" site_id="se#0321">V Film Premiere</channel>
<channel site="allente.se" lang="sv" xmltv_id="ViasatExplore.se" site_id="se#0358">Viasat Explore</channel>
<channel site="allente.se" lang="sv" xmltv_id="ViasatHistory.se" site_id="se#0357">Viasat History HD</channel>
<channel site="allente.se" lang="sv" xmltv_id="ViasatNature.se" site_id="se#0356">Viasat Nature</channel>
<channel site="allente.se" lang="sv" xmltv_id="VSeries.se" site_id="se#0320">V Series</channel>
<channel site="allente.se" lang="sv" xmltv_id="VSport1Sweden.se" site_id="se#0362">V Sport 1</channel>
<channel site="allente.se" lang="sv" xmltv_id="VSportExtra.se" site_id="se#715">V Sport Extra</channel>
<channel site="allente.se" lang="sv" xmltv_id="VSportFootball.se" site_id="se#0269">V Sport Football</channel>
<channel site="allente.se" lang="sv" xmltv_id="VSportGolf.se" site_id="se#0364">V Sport Golf</channel>
<channel site="allente.se" lang="sv" xmltv_id="VSportLive1.se" site_id="se#0255">V Sport Live 1</channel>
<channel site="allente.se" lang="sv" xmltv_id="VSportLive2.se" site_id="se#0256">V Sport Live 2</channel>
<channel site="allente.se" lang="sv" xmltv_id="VSportLive3.se" site_id="se#0257">V Sport Live 3</channel>
<channel site="allente.se" lang="sv" xmltv_id="VSportLive4.se" site_id="se#0258">V Sport Live 4</channel>
<channel site="allente.se" lang="sv" xmltv_id="VSportLive5.se" site_id="se#0259">V Sport Live 5</channel>
<channel site="allente.se" lang="sv" xmltv_id="VSportMotor.se" site_id="se#0292">V Sport Motor</channel>
<channel site="allente.se" lang="sv" xmltv_id="VSportPremium.se" site_id="se#527">V Sport Premium</channel>
<channel site="allente.se" lang="sv" xmltv_id="VSportUltraHD.se" site_id="se#418">V Sport Ultra HD</channel>
<channel site="allente.se" lang="sv" xmltv_id="VSportVinter.se" site_id="se#0363">V Sport Vinter</channel>
</channels>

View file

@ -1,6 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="andorradifusio.ad">
<channels>
<channel lang="ca" xmltv_id="AndorraTV.ad" site_id="atv">Andorra TV</channel>
</channels>
</site>
<channels>
<channel site="andorradifusio.ad" lang="ca" xmltv_id="AndorraTV.ad" site_id="atv">Andorra TV</channel>
</channels>

View file

@ -47,8 +47,8 @@ function parseItems(content, date) {
.parent()
.parent()
const items = []
const titles = column.find(`p`).toArray()
column.find(`h4`).each((i, time) => {
const titles = column.find('p').toArray()
column.find('h4').each((i, time) => {
items.push({
time: $(time).text(),
title: $(titles[i]).text()

View file

@ -1,4 +1,4 @@
// npx epg-grabber --config=sites/andorradifusio.ad/andorradifusio.ad.config.js --channels=sites/andorradifusio.ad/andorradifusio.ad.channels.xml --output=guide.xml
// npm run grab -- --site=andorradifusio.ad
const { parser, url } = require('./andorradifusio.ad.config.js')
const fs = require('fs')
@ -30,20 +30,20 @@ it('can parse response', () => {
expect(results[0]).toMatchObject({
start: '2023-06-07T05:00:00.000Z',
stop: '2023-06-07T06:00:00.000Z',
title: `Club Piolet`
title: 'Club Piolet'
})
expect(results[20]).toMatchObject({
start: '2023-06-07T23:00:00.000Z',
stop: '2023-06-08T00:00:00.000Z',
title: `Àrea Andorra Difusió`
title: 'Àrea Andorra Difusió'
})
})
it('can handle empty guide', () => {
const result = parser({
date,
content: `<!DOCTYPE html><html><head></head><body></body></html>`
content: '<!DOCTYPE html><html><head></head><body></body></html>'
})
expect(result).toMatchObject([])
})

View file

@ -1,6 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="arianaafgtv.com">
<channels>
<channel lang="en" xmltv_id="ArianaAfghanistanInternationalTV.us" site_id="#">Ariana Afghanistan International TV</channel>
</channels>
</site>
<channels>
<channel site="arianaafgtv.com" lang="en" xmltv_id="ArianaAfghanistanInternationalTV.us" site_id="#">Ariana Afghanistan International TV</channel>
</channels>

View file

@ -11,9 +11,7 @@ dayjs.extend(customParseFormat)
module.exports = {
site: 'arianaafgtv.com',
days: 2,
url() {
return `https://www.arianaafgtv.com/index.html`
},
url: 'https://www.arianaafgtv.com/index.html',
parser({ content, date }) {
const programs = []
const items = parseItems(content, date)

View file

@ -1,6 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="arianatelevision.com">
<channels>
<channel lang="en" xmltv_id="ATNNational.af" site_id="#">Ariana TV National</channel>
</channels>
</site>
<channels>
<channel site="arianatelevision.com" lang="en" xmltv_id="ATNNational.af" site_id="#">Ariana TV National</channel>
</channels>

View file

@ -4,7 +4,7 @@ const { DateTime } = require('luxon')
module.exports = {
site: 'arianatelevision.com',
days: 2,
url: `https://www.arianatelevision.com/program-schedule/`,
url: 'https://www.arianatelevision.com/program-schedule/',
parser({ content, date }) {
const programs = []
const items = parseItems(content, date)
@ -37,8 +37,6 @@ function parseStart(item, date) {
}
function parseItems(content, date) {
const items = []
const col = date.day()
const $ = cheerio.load(content)
const settings = $('#jtrt_table_settings_508').text()
if (!settings) return []

View file

@ -1,4 +1,4 @@
// npx epg-grabber --config=sites/arianatelevision.com/arianatelevision.com.config.js --channels=sites/arianatelevision.com/arianatelevision.com.channels.xml --output=guide.xml
// npm run grab -- --site=arianatelevision.com
const { parser, url } = require('./arianatelevision.com.config.js')
const dayjs = require('dayjs')
@ -18,7 +18,8 @@ it('can generate valid url', () => {
})
it('can parse response', () => {
const content = `<!DOCTYPE html><html><head></head><body><textarea data-jtrt-table-id="508" id="jtrt_table_settings_508" cols="30" rows="10">[[["Start","Saturday","Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","",""],["7:00","City Report","ICC T20 Highlights","ICC T20 Highlights","ICC T20 Highlights","ICC T20 Highlights","ICC T20 Highlights","ICC T20 Highlights","",""],["7:30","ICC T20 Highlights","Sport ","Sport ","Sport ","Sport ","Sport ","Sport ","",""],["15:00","ICC T20 World Cup","ICC T20 World Cup","ICC T20 World Cup","ICC T20 World Cup","ICC T20 World Cup","ICC T20 World Cup","ICC T20 World Cup","",""],["6:30","Quran and Hadis ","Falah","Falah","Falah","Falah","Falah","Falah","",""],["","\\n","","","","","","","",""]]]</textarea></body></html>`
const content =
'<!DOCTYPE html><html><head></head><body><textarea data-jtrt-table-id="508" id="jtrt_table_settings_508" cols="30" rows="10">[[["Start","Saturday","Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","",""],["7:00","City Report","ICC T20 Highlights","ICC T20 Highlights","ICC T20 Highlights","ICC T20 Highlights","ICC T20 Highlights","ICC T20 Highlights","",""],["7:30","ICC T20 Highlights","Sport ","Sport ","Sport ","Sport ","Sport ","Sport ","",""],["15:00","ICC T20 World Cup","ICC T20 World Cup","ICC T20 World Cup","ICC T20 World Cup","ICC T20 World Cup","ICC T20 World Cup","ICC T20 World Cup","",""],["6:30","Quran and Hadis ","Falah","Falah","Falah","Falah","Falah","Falah","",""],["","\\n","","","","","","","",""]]]</textarea></body></html>'
const result = parser({ content, date }).map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
@ -29,22 +30,22 @@ it('can parse response', () => {
{
start: '2021-11-27T02:30:00.000Z',
stop: '2021-11-27T03:00:00.000Z',
title: `City Report`
title: 'City Report'
},
{
start: '2021-11-27T03:00:00.000Z',
stop: '2021-11-27T10:30:00.000Z',
title: `ICC T20 Highlights`
title: 'ICC T20 Highlights'
},
{
start: '2021-11-27T10:30:00.000Z',
stop: '2021-11-28T02:00:00.000Z',
title: `ICC T20 World Cup`
title: 'ICC T20 World Cup'
},
{
start: '2021-11-28T02:00:00.000Z',
stop: '2021-11-28T02:30:00.000Z',
title: `Quran and Hadis`
title: 'Quran and Hadis'
}
])
})
@ -53,7 +54,8 @@ it('can handle empty guide', () => {
const result = parser({
date,
channel,
content: `<!DOCTYPE html><html><head></head><body><textarea data-jtrt-table-id="508" id="jtrt_table_settings_508" cols="30" rows="10"></textarea></body></html>`
content:
'<!DOCTYPE html><html><head></head><body><textarea data-jtrt-table-id="508" id="jtrt_table_settings_508" cols="30" rows="10"></textarea></body></html>'
})
expect(result).toMatchObject([])
})

View file

@ -1,8 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="arirang.com">
<channels>
<channel lang="en" xmltv_id="ArirangTV.kr" site_id="CH_K" logo="https://i.imgur.com/Asu5pE9.png">Arirang TV</channel>
<channel lang="en" xmltv_id="ArirangUN.kr" site_id="CH_Z" logo="https://i.imgur.com/Jdy3WNm.png">Arirang UN</channel>
<channel lang="en" xmltv_id="ArirangWorld.kr" site_id="CH_W" logo="https://i.imgur.com/5Aoithj.png">Arirang World</channel>
</channels>
</site>
<channels>
<channel site="arirang.com" lang="en" xmltv_id="ArirangTV.kr" site_id="CH_K">Arirang TV</channel>
<channel site="arirang.com" lang="en" xmltv_id="ArirangUN.kr" site_id="CH_Z">Arirang UN</channel>
<channel site="arirang.com" lang="en" xmltv_id="ArirangWorld.kr" site_id="CH_W">Arirang World</channel>
</channels>

View file

@ -8,132 +8,146 @@ dayjs.extend(timezone)
dayjs.extend(customParseFormat)
module.exports = {
site: 'arirang.com',
output: 'arirang.com.guide.xml',
channels: 'arirang.com.channels.xml',
lang: 'en',
days: 7,
delay: 5000,
url: 'https://www.arirang.com/v1.0/open/external/proxy',
site: 'arirang.com',
output: 'arirang.com.guide.xml',
channels: 'arirang.com.channels.xml',
lang: 'en',
days: 7,
delay: 5000,
url: 'https://www.arirang.com/v1.0/open/external/proxy',
request: {
request: {
method: 'POST',
timeout: 5000,
cache: { ttl: 60 * 60 * 1000 },
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json',
Origin: 'https://www.arirang.com',
Referer: 'https://www.arirang.com/schedule',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'
},
data: function (context) {
const { channel, date } = context
return {
address: 'https://script.arirang.com/api/v1/bis/listScheduleV3.do',
method: 'POST',
timeout: 5000,
cache: { ttl: 60 * 60 * 1000 },
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json',
'Origin': 'https://www.arirang.com',
'Referer': 'https://www.arirang.com/schedule',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'
},
data: function (context) {
const { channel, date } = context
return {
'address': 'https://script.arirang.com/api/v1/bis/listScheduleV3.do',
'method': 'POST',
'headers': {},
'body': {
'data': {
'dmParam': {
'chanId': channel.site_id,
'broadYmd': dayjs.tz(date, 'Asia/Seoul').format('YYYYMMDD'),
'planNo': '1'
}
}
}
headers: {},
body: {
data: {
dmParam: {
chanId: channel.site_id,
broadYmd: dayjs.tz(date, 'Asia/Seoul').format('YYYYMMDD'),
planNo: '1'
}
}
}
},
logo: function (context) {
return context.channel.logo
},
async parser(context) {
const programs = []
const items = parseItems(context.content)
for (let item of items) {
const programDetail = await parseProgramDetail(item)
programs.push({
title: item.displayNm,
start: parseStart(item),
stop: parseStop(item),
icon: parseIcon(programDetail),
category: parseCategory(programDetail),
description: parseDescription(programDetail)
})
}
return programs
}
}
},
logo: function (context) {
return context.channel.logo
},
async parser(context) {
const programs = []
const items = parseItems(context.content)
for (let item of items) {
const programDetail = await parseProgramDetail(item)
programs.push({
title: item.displayNm,
start: parseStart(item),
stop: parseStop(item),
icon: parseIcon(programDetail),
category: parseCategory(programDetail),
description: parseDescription(programDetail)
})
}
return programs
}
}
function parseItems(content) {
if (content != '') {
const data = JSON.parse(content)
return (!data || !data.responseBody || !Array.isArray(data.responseBody.dsSchWeek)) ? [] : data.responseBody.dsSchWeek
} else {
return []
}
if (content != '') {
const data = JSON.parse(content)
return !data || !data.responseBody || !Array.isArray(data.responseBody.dsSchWeek)
? []
: data.responseBody.dsSchWeek
} else {
return []
}
}
function parseStart(item) {
return dayjs.tz(item.broadYmd + ' ' + item.broadHm, 'YYYYMMDD HHmm', 'Asia/Seoul')
return dayjs.tz(item.broadYmd + ' ' + item.broadHm, 'YYYYMMDD HHmm', 'Asia/Seoul')
}
function parseStop(item) {
return dayjs.tz(item.broadYmd + ' ' + item.broadHm, 'YYYYMMDD HHmm', 'Asia/Seoul').add(item.broadRun, 'minute')
return dayjs
.tz(item.broadYmd + ' ' + item.broadHm, 'YYYYMMDD HHmm', 'Asia/Seoul')
.add(item.broadRun, 'minute')
}
async function parseProgramDetail(item) {
return axios.post(
'https://www.arirang.com/v1.0/open/program/detail',
{
'bis_program_code': item.pgmCd
return axios
.post(
'https://www.arirang.com/v1.0/open/program/detail',
{
bis_program_code: item.pgmCd
},
{
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json',
Origin: 'https://www.arirang.com',
Referer: 'https://www.arirang.com/schedule',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'
},
{
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json',
'Origin': 'https://www.arirang.com',
'Referer': 'https://www.arirang.com/schedule',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'
},
timeout: 5000,
cache: { ttl: 60 * 1000 },
}
).then(function (response) {
return response.data
}).catch(function (error) {
// console.log(error)
timeout: 5000,
cache: { ttl: 60 * 1000 }
}
)
.then(response => {
return response.data
})
.catch(error => {
console.log(error)
})
}
function parseIcon(programDetail) {
if (programDetail && programDetail.image && programDetail.image[0].url) {
return programDetail.image[0].url
} else {
return ''
}
if (programDetail && programDetail.image && programDetail.image[0].url) {
return programDetail.image[0].url
} else {
return ''
}
}
function parseCategory(programDetail) {
if (programDetail && programDetail.category_Info && programDetail.category_Info[0].title) {
return programDetail.category_Info[0].title
} else {
return ''
}
if (programDetail && programDetail.category_Info && programDetail.category_Info[0].title) {
return programDetail.category_Info[0].title
} else {
return ''
}
}
function parseDescription(programDetail) {
if (programDetail && programDetail.content && programDetail.content[0] && programDetail.content[0].text) {
let description = programDetail.content[0].text
let regex = /(<([^>]+)>)/ig
return description.replace(regex, '')
} else {
return ''
}
if (
programDetail &&
programDetail.content &&
programDetail.content[0] &&
programDetail.content[0].text
) {
let description = programDetail.content[0].text
let regex = /(<([^>]+)>)/gi
return description.replace(regex, '')
} else {
return ''
}
}

View file

@ -1,4 +1,4 @@
// npx epg-grabber --config=sites/arirang.com/arirang.com.config.js --channels=sites/arirang.com/arirang.com.channels.xml --output=guide.xml --days=2
// npm run grab -- --site=arirang.com
// npx jest arirang.com.test.js
const { url, parser } = require('./arirang.com.config.js')
@ -7,53 +7,68 @@ const path = require('path')
const axios = require('axios')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const { program } = require('commander')
dayjs.extend(utc)
jest.mock('axios')
const date = dayjs.tz('2023-08-25', 'Asia/Seoul').startOf('d')
const channel = { xmltv_id: 'ArirangWorld.kr', site_id: 'CH_W', name: 'Arirang World', lang: 'en', logo: 'https://i.imgur.com/5Aoithj.png' }
const channel = {
xmltv_id: 'ArirangWorld.kr',
site_id: 'CH_W',
name: 'Arirang World',
lang: 'en',
logo: 'https://i.imgur.com/5Aoithj.png'
}
const content = fs.readFileSync(path.resolve(__dirname, '__data__/schedule.json'), 'utf8')
const programDetail = fs.readFileSync(path.resolve(__dirname, '__data__/detail.json'), 'utf8')
const context = { 'channel': channel, 'content': content, 'date': date }
const context = { channel: channel, content: content, date: date }
it('can generate valid url', () => {
expect(url).toBe('https://www.arirang.com/v1.0/open/external/proxy')
expect(url).toBe('https://www.arirang.com/v1.0/open/external/proxy')
})
it('can handle empty guide', async () => {
const results = await parser({ 'channel': channel, 'content': '', 'date': date })
expect(results).toMatchObject([])
const results = await parser({ channel: channel, content: '', date: date })
expect(results).toMatchObject([])
})
it('can parse response', async () => {
axios.post.mockImplementation((url, data) => {
if (url === 'https://www.arirang.com/v1.0/open/external/proxy' && JSON.stringify(data) === JSON.stringify({ "address": "https://script.arirang.com/api/v1/bis/listScheduleV3.do", "method": "POST", "headers": {}, "body": { "data": { "dmParam": { "chanId": "CH_W", "broadYmd": "20230825", "planNo": "1" } } } })) {
return Promise.resolve({
data: JSON.parse(content)
})
} else if (url === 'https://www.arirang.com/v1.0/open/program/detail' && JSON.stringify(data) === JSON.stringify({ "bis_program_code": "2023004T" })) {
return Promise.resolve({
data: JSON.parse(programDetail)
})
} else {
return Promise.resolve({
data: ''
})
}
})
axios.post.mockImplementation((url, data) => {
if (
url === 'https://www.arirang.com/v1.0/open/external/proxy' &&
JSON.stringify(data) ===
JSON.stringify({
address: 'https://script.arirang.com/api/v1/bis/listScheduleV3.do',
method: 'POST',
headers: {},
body: { data: { dmParam: { chanId: 'CH_W', broadYmd: '20230825', planNo: '1' } } }
})
) {
return Promise.resolve({
data: JSON.parse(content)
})
} else if (
url === 'https://www.arirang.com/v1.0/open/program/detail' &&
JSON.stringify(data) === JSON.stringify({ bis_program_code: '2023004T' })
) {
return Promise.resolve({
data: JSON.parse(programDetail)
})
} else {
return Promise.resolve({
data: ''
})
}
})
const results = await parser(context)
const results = await parser(context)
expect(results[0]).toMatchObject(
{
title: "WITHIN THE FRAME [R]",
start: dayjs.tz(date, 'Asia/Seoul'),
stop: dayjs.tz(date, 'Asia/Seoul').add(30, 'minute'),
icon: "https://img.arirang.com/v1/AUTH_d52449c16d3b4bbca17d4fffd9fc44af/public/images/202308/2080840096998752900.png",
description: "NEWS",
category: "Current Affairs"
}
)
})
expect(results[0]).toMatchObject({
title: 'WITHIN THE FRAME [R]',
start: dayjs.tz(date, 'Asia/Seoul'),
stop: dayjs.tz(date, 'Asia/Seoul').add(30, 'minute'),
icon: 'https://img.arirang.com/v1/AUTH_d52449c16d3b4bbca17d4fffd9fc44af/public/images/202308/2080840096998752900.png',
description: 'NEWS',
category: 'Current Affairs'
})
})

View file

@ -1,10 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="artonline.tv">
<channels>
<channel lang="ar" xmltv_id="ARTAflam1.sa" site_id="">ART Aflam 1</channel>
<channel lang="ar" xmltv_id="ARTAflam2.sa" site_id="Aflam2">ART Aflam 2</channel>
<channel lang="ar" xmltv_id="ARTCinema.sa" site_id="Cinema">ART Cinema</channel>
<channel lang="ar" xmltv_id="ARTHekayat.sa" site_id="Hekayat">ART Hekayat</channel>
<channel lang="ar" xmltv_id="ARTHekayat2.sa" site_id="Hekayat2">ART Hekayat 2</channel>
</channels>
</site>
<channels>
<channel site="artonline.tv" lang="ar" xmltv_id="ARTAflam1.sa" site_id="">ART Aflam 1</channel>
<channel site="artonline.tv" lang="ar" xmltv_id="ARTAflam2.sa" site_id="Aflam2">ART Aflam 2</channel>
<channel site="artonline.tv" lang="ar" xmltv_id="ARTCinema.sa" site_id="Cinema">ART Cinema</channel>
<channel site="artonline.tv" lang="ar" xmltv_id="ARTHekayat.sa" site_id="Hekayat">ART Hekayat</channel>
<channel site="artonline.tv" lang="ar" xmltv_id="ARTHekayat2.sa" site_id="Hekayat2">ART Hekayat 2</channel>
</channels>

View file

@ -51,14 +51,14 @@ module.exports = {
}
function parseStart(item) {
const [_, M, D, YYYY] = item.adddate.match(/(\d+)\/(\d+)\/(\d+) /)
const [, M, D, YYYY] = item.adddate.match(/(\d+)\/(\d+)\/(\d+) /)
const [HH, mm] = item.start_Time.split(':')
return dayjs.tz(`${YYYY}-${M}-${D}T${HH}:${mm}:00`, 'YYYY-M-DTHH:mm:ss', 'Asia/Riyadh')
}
function parseDuration(item) {
const [__, HH, mm, ss] = item.duration.match(/(\d+):(\d+):(\d+)/)
const [, HH, mm, ss] = item.duration.match(/(\d+):(\d+):(\d+)/)
return parseInt(HH) * 3600 + parseInt(mm) * 60 + parseInt(ss)
}

View file

@ -1,4 +1,4 @@
// npx epg-grabber --config=sites/artonline.tv/artonline.tv.config.js --channels=sites/artonline.tv/artonline.tv.channels.xml --output=guide.xml --days=2
// npm run grab -- --site=artonline.tv
const { parser, url, request } = require('./artonline.tv.config.js')
const dayjs = require('dayjs')
@ -39,7 +39,8 @@ it('can generate valid request data for tomorrow', () => {
})
it('can parse response', () => {
const content = `[{"id":158963,"eventid":null,"duration":"01:34:00","lang":"Arabic","title":"الراقصه و السياسي","description":"تقرر الراقصه سونيا انشاء دار حضانه للأطفال اليتامى و عندما تتقدم بمشورعها للمسئول يرفض فتتحداه ، تلجأ للوزير عبد الحميد رأفت تربطه بها علاقة قديمة ، يخشى على مركزه و يرفض مساعدتها فتقرر كتابة مذكراتها بمساعدة أحد الصحفيين ، يتخوف عبد الحميد و المسئولين ثم يفاجأ عبد الحميد بحصول سونيا على الموافقه للمشورع و البدء في تنفيذه و ذلك لعلاقتها بأحد كبار المسئولين .","thumbnail":"/UploadImages/Channel/ARTAFLAM1/03/AlRaqesaWaAlSeyasi.jpg","image":"0","start_Time":"00:30","adddate":"3/4/2022 12:00:00 AM","repeat1":null,"iD_genre":0,"iD_Show_Type":0,"iD_Channel":77,"iD_country":0,"iD_rating":0,"end_time":"02:04","season_Number":0,"epoisode_Number":0,"hasCatchup":0,"cmsid":0,"containerID":0,"imagePath":"../../UploadImages/Channel/ARTAFLAM1/3/","youtube":"0","published_at":"0","directed_by":"0","composition":"0","cast":"0","timeShow":null,"short_description":"تقرر الراقصه سونيا انشاء دار حضانه للأطفال اليتامى و عندما تتقدم بمشورعها للمسئول يرفض فتتحداه ، تلجأ للوزير عبد الحميد رأفت تربطه بها علاقة قديمة ، يخشى على مركزه و يرفض مساعدتها فتقرر كتابة مذكراتها بمساعدة أحد الصحفيين ، يتخوف عبد الحميد و المسئولين ثم يفاجأ عبد الحميد بحصول سونيا على الموافقه للمشورع و البدء في تنفيذه و ذلك لعلاقتها بأحد كبار المسئولين .","seOdescription":null,"tagseo":null,"channel_name":null,"pathimage":null,"pathThumbnail":null}]`
const content =
'[{"id":158963,"eventid":null,"duration":"01:34:00","lang":"Arabic","title":"الراقصه و السياسي","description":"تقرر الراقصه سونيا انشاء دار حضانه للأطفال اليتامى و عندما تتقدم بمشورعها للمسئول يرفض فتتحداه ، تلجأ للوزير عبد الحميد رأفت تربطه بها علاقة قديمة ، يخشى على مركزه و يرفض مساعدتها فتقرر كتابة مذكراتها بمساعدة أحد الصحفيين ، يتخوف عبد الحميد و المسئولين ثم يفاجأ عبد الحميد بحصول سونيا على الموافقه للمشورع و البدء في تنفيذه و ذلك لعلاقتها بأحد كبار المسئولين .","thumbnail":"/UploadImages/Channel/ARTAFLAM1/03/AlRaqesaWaAlSeyasi.jpg","image":"0","start_Time":"00:30","adddate":"3/4/2022 12:00:00 AM","repeat1":null,"iD_genre":0,"iD_Show_Type":0,"iD_Channel":77,"iD_country":0,"iD_rating":0,"end_time":"02:04","season_Number":0,"epoisode_Number":0,"hasCatchup":0,"cmsid":0,"containerID":0,"imagePath":"../../UploadImages/Channel/ARTAFLAM1/3/","youtube":"0","published_at":"0","directed_by":"0","composition":"0","cast":"0","timeShow":null,"short_description":"تقرر الراقصه سونيا انشاء دار حضانه للأطفال اليتامى و عندما تتقدم بمشورعها للمسئول يرفض فتتحداه ، تلجأ للوزير عبد الحميد رأفت تربطه بها علاقة قديمة ، يخشى على مركزه و يرفض مساعدتها فتقرر كتابة مذكراتها بمساعدة أحد الصحفيين ، يتخوف عبد الحميد و المسئولين ثم يفاجأ عبد الحميد بحصول سونيا على الموافقه للمشورع و البدء في تنفيذه و ذلك لعلاقتها بأحد كبار المسئولين .","seOdescription":null,"tagseo":null,"channel_name":null,"pathimage":null,"pathThumbnail":null}]'
const result = parser({ content }).map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()

View file

@ -1,143 +1,141 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="astro.com.my">
<channels>
<channel lang="ms" xmltv_id="8TV.my" site_id="115">8TV</channel>
<!-- <channel lang="ms" xmltv_id="" site_id="461">ABC</channel> -->
<channel lang="ms" xmltv_id="AdithyaTV.in" site_id="67">Adithya TV</channel>
<channel lang="ms" xmltv_id="AlJazeeraEnglish.qa" site_id="374">Aljazeera</channel>
<channel lang="ms" xmltv_id="AnimalPlanetMalaysia.my" site_id="377">Animal Planet</channel>
<channel lang="ms" xmltv_id="AsianFoodNetwork.sg" site_id="91">Asian Food Network</channel>
<channel lang="ms" xmltv_id="AstroAEC.my" site_id="182">Astro AEC</channel>
<channel lang="ms" xmltv_id="AstroAOD311.my" site_id="172">Astro AOD 311</channel>
<channel lang="ms" xmltv_id="AstroAOD352.my" site_id="87">Astro AOD 352</channel>
<channel lang="ms" xmltv_id="AstroAOD353.my" site_id="114">Astro AOD 353</channel>
<channel lang="ms" xmltv_id="AstroAOD354.my" site_id="65">Astro AOD 354</channel>
<channel lang="ms" xmltv_id="AstroAOD355.my" site_id="66">Astro AOD 355</channel>
<channel lang="ms" xmltv_id="AstroArena.my" site_id="235">Astro Arena</channel>
<channel lang="ms" xmltv_id="AstroArena2.my" site_id="457">Astro Arena 2</channel>
<channel lang="ms" xmltv_id="AstroAura.my" site_id="400">Astro Aura</channel>
<channel lang="ms" xmltv_id="AstroAwani.my" site_id="436">Astro Awani</channel>
<channel lang="ms" xmltv_id="AstroBollyOneHD.my" site_id="178">Astro BollyOne HD</channel>
<channel lang="ms" xmltv_id="AstroBoxOfficeTayanganHebat.my" site_id="176">Astro Box Office Tayangan Hebat</channel>
<channel lang="ms" xmltv_id="AstroBoxOfficeThangathirai.my" site_id="177">Astro Box Office Thangathirai</channel>
<channel lang="ms" xmltv_id="AstroCeria.my" site_id="386">Astro Ceria</channel>
<channel lang="ms" xmltv_id="AstroCh100.my" site_id="471">Gemilang</channel>
<channel lang="ms" xmltv_id="AstroCitra.my" site_id="301">Astro Citra</channel>
<channel lang="ms" xmltv_id="AstroCricket.my" site_id="197">Astro Cricket</channel>
<channel lang="ms" xmltv_id="AstroHuaHeeDai.my" site_id="162">Astro Hua Hee Dai</channel>
<channel lang="ms" xmltv_id="AstroOasis.my" site_id="315">Astro Oasis</channel>
<channel lang="ms" xmltv_id="AstroPrima.my" site_id="316">Astro Prima</channel>
<channel lang="ms" xmltv_id="AstroQuanJiaHD.my" site_id="158">Astro Quan Jia HD</channel>
<channel lang="ms" xmltv_id="AstroRania.my" site_id="401">Astro Rania</channel>
<channel lang="ms" xmltv_id="AstroRia.my" site_id="193">Astro Ria</channel>
<channel lang="ms" xmltv_id="AstroShuangXing.my" site_id="183">Astro Shuang Xing</channel>
<channel lang="ms" xmltv_id="AstroSuperSport.my" site_id="154">Astro SuperSport</channel>
<channel lang="ms" xmltv_id="AstroSuperSport2.my" site_id="138">Astro SuperSport 2</channel>
<channel lang="ms" xmltv_id="AstroSuperSport3.my" site_id="164">Astro SuperSport 3</channel>
<channel lang="ms" xmltv_id="AstroSuperSport4.my" site_id="241">Astro SuperSport 4</channel>
<channel lang="ms" xmltv_id="AstroSuperSport5.my" site_id="455">Astro SuperSport 5</channel>
<channel lang="ms" xmltv_id="AstroTutorTVPT3.my" site_id="410">Astro Tutor TV PT3</channel>
<channel lang="ms" xmltv_id="AstroTutorTVSPM.my" site_id="411">Astro Tutor TV SPM</channel>
<channel lang="ms" xmltv_id="AstroTutorTVUPSR.my" site_id="412">Astro Tutor TV UPSR</channel>
<channel lang="ms" xmltv_id="AstroUHD.my" site_id="308">Astro UHD</channel>
<channel lang="ms" xmltv_id="AstroVaanavil.my" site_id="397">Astro Vaanavil</channel>
<channel lang="ms" xmltv_id="AstroVellithirai.my" site_id="399">Astro Vellithirai</channel>
<channel lang="ms" xmltv_id="AstroVinmeenHD.my" site_id="167">Astro Vinmeen HD</channel>
<channel lang="ms" xmltv_id="AstroWahLaiToi.my" site_id="129">Astro Wah Lai Toi</channel>
<channel lang="ms" xmltv_id="AstroWarna.my" site_id="272">Astro Warna</channel>
<channel lang="ms" xmltv_id="AstroXiaoTaiYang.my" site_id="387">Astro Xiao Tai Yang</channel>
<channel lang="ms" xmltv_id="AwesomeTV.my" site_id="433">Awesome TV</channel>
<channel lang="ms" xmltv_id="AXNMalaysia.my" site_id="131">AXN</channel>
<channel lang="ms" xmltv_id="BBCEarthAsia.uk" site_id="452">BBC Earth</channel>
<channel lang="ms" xmltv_id="BBCFirstAsia.uk" site_id="458">BBC First</channel>
<channel lang="ms" xmltv_id="BBCLifestyleAsia.uk" site_id="451">BBC Lifestyle</channel>
<channel lang="ms" xmltv_id="BBCWorldNewsAsiaPacific.uk" site_id="366">BBC World News</channel>
<channel lang="ms" xmltv_id="beINSports.qa" site_id="236">beIN Sports HD</channel>
<channel lang="ms" xmltv_id="beINSports2.qa" site_id="466">beIN Sports 2</channel>
<channel lang="ms" xmltv_id="beINSports3.qa" site_id="313">beIN Sports 3</channel>
<channel lang="ms" xmltv_id="BernamaTV.my" site_id="160">Bernama TV</channel>
<channel lang="ms" xmltv_id="BloombergTVAsia.hk" site_id="422">Bloomberg TV</channel>
<channel lang="ms" xmltv_id="Boo.my" site_id="251">Boo</channel>
<channel lang="ms" xmltv_id="BoomerangSoutheastAsia.us" site_id="430">Boomerang</channel>
<channel lang="ms" xmltv_id="CartoonNetworkAsia.sg" site_id="371">Cartoon Network HD</channel>
<channel lang="ms" xmltv_id="CCTV4Asia.cn" site_id="385">CCTV 4</channel>
<channel lang="ms" xmltv_id="CCM.hk" site_id="187">Celestial Classic Movies</channel>
<channel lang="ms" xmltv_id="CelestialMoviesMalaysia.my" site_id="134">Celestial Movies</channel>
<channel lang="ms" xmltv_id="CGTN.cn" site_id="426">CGTN</channel>
<channel lang="ms" xmltv_id="ChuttiTVMalaysia.my" site_id="51">Chutti TV</channel>
<channel lang="ms" xmltv_id="CinemaxAsia.sg" site_id="337">Cinemax</channel>
<channel lang="ms" xmltv_id="CNA.sg" site_id="295">CNA</channel>
<channel lang="ms" xmltv_id="CNBCAsia.sg" site_id="423">CNBC Asia-Pacific</channel>
<channel lang="ms" xmltv_id="CNNInternationalAsiaPacific.hk" site_id="336">CNN</channel>
<channel lang="ms" xmltv_id="Colors.in" site_id="365">Colors</channel>
<channel lang="ms" xmltv_id="ColorsTamil.in" site_id="298">Colors Tamil</channel>
<channel lang="ms" xmltv_id="CrimePlusInvestigationAsia.sg" site_id="369">Crime + Investigation</channel>
<channel lang="ms" xmltv_id="CTiAsia.tw" site_id="424">CTI TV</channel>
<channel lang="ms" xmltv_id="DiscoveryAsia.sg" site_id="136">Discovery Asia</channel>
<channel lang="ms" xmltv_id="DiscoveryChannelIndonesia.id" site_id="376">Discovery Channel</channel>
<channel lang="ms" xmltv_id="DMAXSoutheastAsia.sg" site_id="367">DMAX</channel>
<channel lang="ms" xmltv_id="DWEnglish.de" site_id="287">DW English</channel>
<channel lang="ms" xmltv_id="eGGNetwork.my" site_id="206">Egg Network</channel>
<channel lang="ms" xmltv_id="EurosportAsia.fr" site_id="339">Eurosport</channel>
<channel lang="ms" xmltv_id="FoodNetworkAsia.sg" site_id="153">Food Network</channel>
<channel lang="ms" xmltv_id="France24English.fr" site_id="289">France 24 English</channel>
<channel lang="ms" xmltv_id="GolfChannelMalaysia.my" site_id="189">Golf Channel</channel>
<channel lang="ms" xmltv_id="GoShopChinese.my" site_id="202">Go Shop Chinese</channel>
<channel lang="ms" xmltv_id="GoShopMalay111.my" site_id="403">Go Shop Malay 111</channel>
<channel lang="ms" xmltv_id="GoShopMalay118.my" site_id="192">Go Shop Malay 118</channel>
<channel lang="ms" xmltv_id="GoShopMalay120.my" site_id="294">Go Shop Malay 120</channel>
<channel lang="ms" xmltv_id="HBOAsia.sg" site_id="143">HBO</channel>
<channel lang="ms" xmltv_id="HBOFamilyAsia.sg" site_id="450">HBO Family</channel>
<channel lang="ms" xmltv_id="HBOHitsAsia.sg" site_id="449">HBO Hits</channel>
<channel lang="ms" xmltv_id="HGTVAsia.us" site_id="198">HGTV</channel>
<channel lang="ms" xmltv_id="HistoryAsia.us" site_id="144">History</channel>
<channel lang="ms" xmltv_id="HITS.sg" site_id="179">Hits</channel>
<channel lang="ms" xmltv_id="HITSMovies.sg" site_id="391">Hits Movies</channel>
<channel lang="ms" xmltv_id="iQIYI.cn" site_id="355">Iqiyi</channel>
<channel lang="ms" xmltv_id="KBSWorld.kr" site_id="161">KBS World</channel>
<channel lang="ms" xmltv_id="KIX.hk" site_id="157">Kix</channel>
<channel lang="ms" xmltv_id="KPlus.sg" site_id="266">K+</channel>
<channel lang="ms" xmltv_id="LifetimeAsia.us" site_id="447">Lifetime</channel>
<channel lang="ms" xmltv_id="MoonbugKids.uk" site_id="465">Moonbug Kids</channel>
<channel lang="ms" xmltv_id="MTVAsia.sg" site_id="420">MTV</channel>
<channel lang="ms" xmltv_id="NatGeoPeopleMalaysia.my" site_id="199">Nat Geo People</channel>
<channel lang="ms" xmltv_id="NationalGeographicMalaysia.my" site_id="140">National Geographic</channel>
<channel lang="ms" xmltv_id="NationalGeographicWildMalaysia.my" site_id="322">National Geographic Wild</channel>
<channel lang="ms" xmltv_id="NHKWorldPremium.jp" site_id="428">NHK World Premium</channel>
<channel lang="ms" xmltv_id="NickelodeonAsia.sg" site_id="370">Nickelodeon</channel>
<channel lang="ms" xmltv_id="NickJrAsia.sg" site_id="392">Nick Jr</channel>
<channel lang="ms" xmltv_id="NjoiTV.my" site_id="302">Njoi TV</channel>
<channel lang="ms" xmltv_id="NTV7.my" site_id="93">NTV 7</channel>
<channel lang="ms" xmltv_id="OneTVAsia.sg" site_id="133">One</channel>
<channel lang="ms" xmltv_id="ParamountNetworkMalaysia.my" site_id="448">Paramount Network</channel>
<channel lang="ms" xmltv_id="PhoenixChineseChannel.hk" site_id="382">Phoenix Chinese Channel</channel>
<channel lang="ms" xmltv_id="PhoenixInfoNewsChannel.hk" site_id="43">Phoenix InfoNews Channel</channel>
<channel lang="ms" xmltv_id="PremierSports1Asia.ie" site_id="393">Premier Sports</channel>
<channel lang="ms" xmltv_id="PRIMEtime.my" site_id="453">PRIMEtime</channel>
<channel lang="ms" xmltv_id="TV1.my" site_id="395">RTM TV 1</channel>
<channel lang="ms" xmltv_id="TV2.my" site_id="396">RTM TV2</channel>
<channel lang="ms" xmltv_id="Okey.my" site_id="97">RTM TV Okey</channel>
<channel lang="ms" xmltv_id="ShowcaseMovies.my" site_id="454">Showcase Movies</channel>
<channel lang="ms" xmltv_id="SkyNews.uk" site_id="155">Sky News UK</channel>
<channel lang="ms" xmltv_id="SPOTV.kr" site_id="456">SPOTV</channel>
<channel lang="ms" xmltv_id="StarVijay.in" site_id="357">Star Vijay</channel>
<channel lang="ms" xmltv_id="SunMusic.in" site_id="417">Sun Music</channel>
<channel lang="ms" xmltv_id="SunTVMalaysia.my" site_id="358">Sun TV</channel>
<channel lang="ms" xmltv_id="TADAA.my" site_id="432">Ta-Daa!</channel>
<channel lang="ms" xmltv_id="TLCSoutheastAsia.sg" site_id="338">TLC</channel>
<channel lang="ms" xmltv_id="TV3.my" site_id="106">TV 3</channel>
<channel lang="ms" xmltv_id="TV9.my" site_id="48">TV 9</channel>
<channel lang="ms" xmltv_id="TVAlhijrah.my" site_id="149">TV Alhijrah</channel>
<channel lang="ms" xmltv_id="TVBClassic.hk" site_id="425">TVB Classic</channel>
<channel lang="ms" xmltv_id="EntertainmentNews.hk" site_id="427">TVB Entertainment News</channel>
<channel lang="ms" xmltv_id="Jade.hk" site_id="203">TVB Jade</channel>
<channel lang="ms" xmltv_id="TVBSAsia.tw" site_id="384">TVBS Asia</channel>
<channel lang="ms" xmltv_id="TVBXingHe.hk" site_id="383">TVB Xing He</channel>
<channel lang="ms" xmltv_id="tvNAsia.hk" site_id="190">TVN HD</channel>
<channel lang="ms" xmltv_id="tvNMoviesAsia.hk" site_id="274">TVN Movies</channel>
<channel lang="ms" xmltv_id="TVS.my" site_id="429">TVS</channel>
<channel lang="ms" xmltv_id="WarnerTVAsia.us" site_id="270">Warner TV</channel>
<channel lang="ms" xmltv_id="WWENetwork.us" site_id="194">WWE Network</channel>
<channel lang="ms" xmltv_id="ZeeTamil.in" site_id="297">Zee Tamil</channel>
</channels>
</site>
<channels>
<!-- <channel site="astro.com.my" lang="ms" xmltv_id="" site_id="461">ABC</channel> -->
<channel site="astro.com.my" lang="ms" xmltv_id="8TV.my" site_id="115">8TV</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AdithyaTV.in" site_id="67">Adithya TV</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AlJazeeraEnglish.qa" site_id="374">Aljazeera</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AnimalPlanetMalaysia.my" site_id="377">Animal Planet</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AsianFoodNetwork.sg" site_id="91">Asian Food Network</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroAEC.my" site_id="182">Astro AEC</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroAOD311.my" site_id="172">Astro AOD 311</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroAOD352.my" site_id="87">Astro AOD 352</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroAOD353.my" site_id="114">Astro AOD 353</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroAOD354.my" site_id="65">Astro AOD 354</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroAOD355.my" site_id="66">Astro AOD 355</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroArena.my" site_id="235">Astro Arena</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroArena2.my" site_id="457">Astro Arena 2</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroAura.my" site_id="400">Astro Aura</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroAwani.my" site_id="436">Astro Awani</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroBollyOneHD.my" site_id="178">Astro BollyOne HD</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroBoxOfficeTayanganHebat.my" site_id="176">Astro Box Office Tayangan Hebat</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroBoxOfficeThangathirai.my" site_id="177">Astro Box Office Thangathirai</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroCeria.my" site_id="386">Astro Ceria</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroCh100.my" site_id="471">Gemilang</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroCitra.my" site_id="301">Astro Citra</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroCricket.my" site_id="197">Astro Cricket</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroHuaHeeDai.my" site_id="162">Astro Hua Hee Dai</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroOasis.my" site_id="315">Astro Oasis</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroPrima.my" site_id="316">Astro Prima</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroQuanJiaHD.my" site_id="158">Astro Quan Jia HD</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroRania.my" site_id="401">Astro Rania</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroRia.my" site_id="193">Astro Ria</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroShuangXing.my" site_id="183">Astro Shuang Xing</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroSuperSport.my" site_id="154">Astro SuperSport</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroSuperSport2.my" site_id="138">Astro SuperSport 2</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroSuperSport3.my" site_id="164">Astro SuperSport 3</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroSuperSport4.my" site_id="241">Astro SuperSport 4</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroSuperSport5.my" site_id="455">Astro SuperSport 5</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroTutorTVPT3.my" site_id="410">Astro Tutor TV PT3</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroTutorTVSPM.my" site_id="411">Astro Tutor TV SPM</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroTutorTVUPSR.my" site_id="412">Astro Tutor TV UPSR</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroUHD.my" site_id="308">Astro UHD</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroVaanavil.my" site_id="397">Astro Vaanavil</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroVellithirai.my" site_id="399">Astro Vellithirai</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroVinmeenHD.my" site_id="167">Astro Vinmeen HD</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroWahLaiToi.my" site_id="129">Astro Wah Lai Toi</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroWarna.my" site_id="272">Astro Warna</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AstroXiaoTaiYang.my" site_id="387">Astro Xiao Tai Yang</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AwesomeTV.my" site_id="433">Awesome TV</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="AXNMalaysia.my" site_id="131">AXN</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="BBCEarthAsia.uk" site_id="452">BBC Earth</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="BBCFirstAsia.uk" site_id="458">BBC First</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="BBCLifestyleAsia.uk" site_id="451">BBC Lifestyle</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="BBCWorldNewsAsiaPacific.uk" site_id="366">BBC World News</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="beINSports.qa" site_id="236">beIN Sports HD</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="beINSports2.qa" site_id="466">beIN Sports 2</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="beINSports3.qa" site_id="313">beIN Sports 3</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="BernamaTV.my" site_id="160">Bernama TV</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="BloombergTVAsia.hk" site_id="422">Bloomberg TV</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="Boo.my" site_id="251">Boo</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="BoomerangSoutheastAsia.us" site_id="430">Boomerang</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="CartoonNetworkAsia.sg" site_id="371">Cartoon Network HD</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="CCM.hk" site_id="187">Celestial Classic Movies</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="CCTV4Asia.cn" site_id="385">CCTV 4</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="CelestialMoviesMalaysia.my" site_id="134">Celestial Movies</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="CGTN.cn" site_id="426">CGTN</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="ChuttiTVMalaysia.my" site_id="51">Chutti TV</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="CinemaxAsia.sg" site_id="337">Cinemax</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="CNA.sg" site_id="295">CNA</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="CNBCAsia.sg" site_id="423">CNBC Asia-Pacific</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="CNNInternationalAsiaPacific.hk" site_id="336">CNN</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="Colors.in" site_id="365">Colors</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="ColorsTamil.in" site_id="298">Colors Tamil</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="CrimePlusInvestigationAsia.sg" site_id="369">Crime + Investigation</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="CTiAsia.tw" site_id="424">CTI TV</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="DiscoveryAsia.sg" site_id="136">Discovery Asia</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="DiscoveryChannelIndonesia.id" site_id="376">Discovery Channel</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="DMAXSoutheastAsia.sg" site_id="367">DMAX</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="DWEnglish.de" site_id="287">DW English</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="eGGNetwork.my" site_id="206">Egg Network</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="EntertainmentNews.hk" site_id="427">TVB Entertainment News</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="EurosportAsia.fr" site_id="339">Eurosport</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="FoodNetworkAsia.sg" site_id="153">Food Network</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="France24English.fr" site_id="289">France 24 English</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="GolfChannelMalaysia.my" site_id="189">Golf Channel</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="GoShopChinese.my" site_id="202">Go Shop Chinese</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="GoShopMalay111.my" site_id="403">Go Shop Malay 111</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="GoShopMalay118.my" site_id="192">Go Shop Malay 118</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="GoShopMalay120.my" site_id="294">Go Shop Malay 120</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="HBOAsia.sg" site_id="143">HBO</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="HBOFamilyAsia.sg" site_id="450">HBO Family</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="HBOHitsAsia.sg" site_id="449">HBO Hits</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="HGTVAsia.us" site_id="198">HGTV</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="HistoryAsia.us" site_id="144">History</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="HITS.sg" site_id="179">Hits</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="HITSMovies.sg" site_id="391">Hits Movies</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="iQIYI.cn" site_id="355">Iqiyi</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="Jade.hk" site_id="203">TVB Jade</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="KBSWorld.kr" site_id="161">KBS World</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="KIX.hk" site_id="157">Kix</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="KPlus.sg" site_id="266">K+</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="LifetimeAsia.us" site_id="447">Lifetime</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="MoonbugKids.uk" site_id="465">Moonbug Kids</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="MTVAsia.sg" site_id="420">MTV</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="NatGeoPeopleMalaysia.my" site_id="199">Nat Geo People</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="NationalGeographicMalaysia.my" site_id="140">National Geographic</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="NationalGeographicWildMalaysia.my" site_id="322">National Geographic Wild</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="NHKWorldPremium.jp" site_id="428">NHK World Premium</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="NickelodeonAsia.sg" site_id="370">Nickelodeon</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="NickJrAsia.sg" site_id="392">Nick Jr</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="NjoiTV.my" site_id="302">Njoi TV</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="NTV7.my" site_id="93">NTV 7</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="Okey.my" site_id="97">RTM TV Okey</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="OneTVAsia.sg" site_id="133">One</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="ParamountNetworkMalaysia.my" site_id="448">Paramount Network</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="PhoenixChineseChannel.hk" site_id="382">Phoenix Chinese Channel</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="PhoenixInfoNewsChannel.hk" site_id="43">Phoenix InfoNews Channel</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="PremierSports1Asia.ie" site_id="393">Premier Sports</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="PRIMEtime.my" site_id="453">PRIMEtime</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="ShowcaseMovies.my" site_id="454">Showcase Movies</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="SkyNews.uk" site_id="155">Sky News UK</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="SPOTV.kr" site_id="456">SPOTV</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="StarVijay.in" site_id="357">Star Vijay</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="SunMusic.in" site_id="417">Sun Music</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="SunTVMalaysia.my" site_id="358">Sun TV</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="TADAA.my" site_id="432">Ta-Daa!</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="TLCSoutheastAsia.sg" site_id="338">TLC</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="TV1.my" site_id="395">RTM TV 1</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="TV2.my" site_id="396">RTM TV2</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="TV3.my" site_id="106">TV 3</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="TV9.my" site_id="48">TV 9</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="TVAlhijrah.my" site_id="149">TV Alhijrah</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="TVBClassic.hk" site_id="425">TVB Classic</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="TVBSAsia.tw" site_id="384">TVBS Asia</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="TVBXingHe.hk" site_id="383">TVB Xing He</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="tvNAsia.hk" site_id="190">TVN HD</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="tvNMoviesAsia.hk" site_id="274">TVN Movies</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="TVS.my" site_id="429">TVS</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="WarnerTVAsia.us" site_id="270">Warner TV</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="WWENetwork.us" site_id="194">WWE Network</channel>
<channel site="astro.com.my" lang="ms" xmltv_id="ZeeTamil.in" site_id="297">Zee Tamil</channel>
</channels>

View file

@ -4,7 +4,7 @@ const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
const API_ENDPOINT = `https://contenthub-api.eco.astro.com.my`
const API_ENDPOINT = 'https://contenthub-api.eco.astro.com.my'
module.exports = {
site: 'astro.com.my',
@ -41,13 +41,13 @@ module.exports = {
}
function parseEpisode(item) {
const [_, number] = item.title.match(/Ep(\d+)$/) || [null, null]
const [, number] = item.title.match(/Ep(\d+)$/) || [null, null]
return number ? parseInt(number) : null
}
function parseSeason(details) {
const [_, season] = details.title ? details.title.match(/ S(\d+)/) || [null, null] : [null, null]
const [, season] = details.title ? details.title.match(/ S(\d+)/) || [null, null] : [null, null]
return season ? parseInt(season) : null
}
@ -116,7 +116,7 @@ async function loadProgramDetails(item) {
const data = await axios
.get(url)
.then(r => r.data)
.catch(err => {})
.catch(error => console.log(error.message))
if (!data) return {}
return data.response || {}

View file

@ -1,4 +1,4 @@
// npx epg-grabber --config=sites/astro.com.my/astro.com.my.config.js --channels=sites/astro.com.my/astro.com.my.channels.xml --output=guide.xml --timeout=30000 --days=2
// npm run grab -- --site=astro.com.my
const { parser, url } = require('./astro.com.my.config.js')
const fs = require('fs')

View file

@ -1,162 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="bein.com">
<channels>
<!-- If updating, note that the English and Arabic channels are in a different order so have different IDs -->
<channel lang="en" xmltv_id="beINSportsNews.qa" site_id="sports#1">BeIn Sports News</channel>
<channel lang="en" xmltv_id="beINSports.qa" site_id="sports#2">BeIn Sports</channel>
<channel lang="en" xmltv_id="beINSports1.qa" site_id="sports#3">BeIn Sports 1</channel>
<channel lang="en" xmltv_id="beINSports2.qa" site_id="sports#4">BeIn Sports 2</channel>
<channel lang="en" xmltv_id="beINSports3.qa" site_id="sports#5">BeIn Sports 3</channel>
<channel lang="en" xmltv_id="beINSports4.qa" site_id="sports#6">BeIn Sports 4</channel>
<channel lang="en" xmltv_id="beINSports5.qa" site_id="sports#7">BeIn Sports 5</channel>
<channel lang="en" xmltv_id="beINSports6.qa" site_id="sports#8">BeIn Sports 6</channel>
<channel lang="en" xmltv_id="beINSports7.qa" site_id="sports#9">BeIn Sports 7</channel>
<channel lang="en" xmltv_id="beINSportsPremium1.qa" site_id="sports#10">BeIn Sports Premium 1</channel>
<channel lang="en" xmltv_id="beINSportsPremium2.qa" site_id="sports#11">BeIn Sports Premium 2</channel>
<channel lang="en" xmltv_id="beINSportsPremium3.qa" site_id="sports#12">BeIn Sports Premium 3</channel>
<channel lang="en" xmltv_id="beINSportsXtra1.qa" site_id="sports#13">BeIn Sports Xtra 1</channel>
<channel lang="en" xmltv_id="beINSportsXtra2.qa" site_id="sports#14">BeIn Sports Xtra 2</channel>
<channel lang="en" xmltv_id="beIN4K.qa" site_id="sports#15">BeIn 4K</channel>
<channel lang="en" xmltv_id="beINSportsAFC.qa" site_id="sports#16">BeIN Sports AFC</channel>
<channel lang="en" xmltv_id="beINSportsAFC1.qa" site_id="sports#17">BeIN Sports AFC 1</channel>
<channel lang="en" xmltv_id="beINSportsAFC2.qa" site_id="sports#18">BeIN Sports AFC 2</channel>
<channel lang="en" xmltv_id="beINSportsAFC3.qa" site_id="sports#19">BeIN Sports AFC 3</channel>
<channel lang="en" xmltv_id="beINSportsEnglish1.qa" site_id="sports#20">BeIn Sports English 1</channel>
<channel lang="en" xmltv_id="beINSportsEnglish2.qa" site_id="sports#21">BeIn Sports English 2</channel>
<channel lang="en" xmltv_id="beINSportsEnglish3.qa" site_id="sports#22">BeIn Sports English 3</channel>
<channel lang="en" xmltv_id="beINSportsNBA.qa" site_id="sports#23">BeIn NBA</channel>
<channel lang="en" xmltv_id="beINSportsFrench1.qa" site_id="sports#24">BeIn Sports French 1</channel>
<channel lang="en" xmltv_id="beINSportsFrench2.qa" site_id="sports#25">BeIn Sports French 2</channel>
<channel lang="en" xmltv_id="beINSportsFrench3.qa" site_id="sports#26">BeIn Sports French 3</channel>
<channel lang="en" xmltv_id="beINSportsMax1.qa" site_id="sports#27">beIN Sports Max 1</channel>
<channel lang="en" xmltv_id="beINSportsMax2.qa" site_id="sports#28">beIN Sports Max 2</channel>
<channel lang="en" xmltv_id="beINSportsMax3.qa" site_id="sports#29">beIN Sports Max 3</channel>
<channel lang="en" xmltv_id="beINSportsMax4.qa" site_id="sports#30">beIN Sports Max 4</channel>
<channel lang="en" xmltv_id="beINSportsMax5.qa" site_id="sports#31">beIN Sports Max 5</channel>
<channel lang="en" xmltv_id="beINSportsMax6.qa" site_id="sports#32">beIN Sports Max 6</channel>
<channel lang="en" xmltv_id="AlkassOne.qa" site_id="sports#33">Alkass One</channel>
<channel lang="en" xmltv_id="AlkassTwo.qa" site_id="sports#34">Alkass Two</channel>
<channel lang="en" xmltv_id="AlkassThree.qa" site_id="sports#35">Alkass Three</channel>
<channel lang="en" xmltv_id="AlkassFour.qa" site_id="sports#36">Alkass Four</channel>
<channel lang="en" xmltv_id="AlkassFive.qa" site_id="sports#37">Alkass Five</channel>
<channel lang="en" xmltv_id="AlkassSix.qa" site_id="sports#38">Alkass Six</channel>
<channel lang="en" xmltv_id="AlkassSeven.qa" site_id="sports#39">Alkass Seven</channel>
<channel lang="en" xmltv_id="AlkassEight.qa" site_id="sports#40">Alkass Eight</channel>
<channel lang="ar" xmltv_id="beINSportsNews.qa" site_id="sports#1">BeIn Sports News</channel>
<channel lang="ar" xmltv_id="beINSports.qa" site_id="sports#2">BeIn Sports</channel>
<channel lang="ar" xmltv_id="beINSports1.qa" site_id="sports#3">BeIn Sports 1</channel>
<channel lang="ar" xmltv_id="beINSports2.qa" site_id="sports#4">BeIn Sports 2</channel>
<channel lang="ar" xmltv_id="beINSports3.qa" site_id="sports#5">BeIn Sports 3</channel>
<channel lang="ar" xmltv_id="beINSports4.qa" site_id="sports#6">BeIn Sports 4</channel>
<channel lang="ar" xmltv_id="beINSports5.qa" site_id="sports#7">BeIn Sports 5</channel>
<channel lang="ar" xmltv_id="beINSports6.qa" site_id="sports#8">BeIn Sports 6</channel>
<channel lang="ar" xmltv_id="beINSports7.qa" site_id="sports#9">BeIn Sports 7</channel>
<channel lang="ar" xmltv_id="beINSportsPremium1.qa" site_id="sports#10">BeIn Sports Premium 1</channel>
<channel lang="ar" xmltv_id="beINSportsPremium2.qa" site_id="sports#11">BeIn Sports Premium 2</channel>
<channel lang="ar" xmltv_id="beINSportsPremium3.qa" site_id="sports#12">BeIn Sports Premium 3</channel>
<channel lang="ar" xmltv_id="beINSportsXtra1.qa" site_id="sports#13">BeIn Sports Xtra 1</channel>
<channel lang="ar" xmltv_id="beINSportsXtra2.qa" site_id="sports#14">BeIn Sports Xtra 2</channel>
<channel lang="ar" xmltv_id="beIN4K.qa" site_id="sports#15">BeIn 4K</channel>
<channel lang="ar" xmltv_id="beINSportsAFC.qa" site_id="sports#16">BeIN Sports AFC</channel>
<channel lang="ar" xmltv_id="beINSportsAFC1.qa" site_id="sports#17">BeIN Sports AFC 1</channel>
<channel lang="ar" xmltv_id="beINSportsAFC2.qa" site_id="sports#18">BeIN Sports AFC 2</channel>
<channel lang="ar" xmltv_id="beINSportsAFC3.qa" site_id="sports#19">BeIN Sports AFC 3</channel>
<channel lang="ar" xmltv_id="beINSportsEnglish1.qa" site_id="sports#20">BeIn Sports English 1</channel>
<channel lang="ar" xmltv_id="beINSportsEnglish2.qa" site_id="sports#21">BeIn Sports English 2</channel>
<channel lang="ar" xmltv_id="beINSportsEnglish3.qa" site_id="sports#22">BeIn Sports English 3</channel>
<channel lang="ar" xmltv_id="beINSportsNBA.qa" site_id="sports#23">BeIn NBA</channel>
<channel lang="ar" xmltv_id="beINSportsFrench1.qa" site_id="sports#24">BeIn Sports French 1</channel>
<channel lang="ar" xmltv_id="beINSportsFrench2.qa" site_id="sports#25">BeIn Sports French 2</channel>
<channel lang="ar" xmltv_id="beINSportsFrench3.qa" site_id="sports#26">BeIn Sports French 3</channel>
<channel lang="ar" xmltv_id="beINSportsMax1.qa" site_id="sports#27">beIN Sports Max 1</channel>
<channel lang="ar" xmltv_id="beINSportsMax2.qa" site_id="sports#28">beIN Sports Max 2</channel>
<channel lang="ar" xmltv_id="beINSportsMax3.qa" site_id="sports#29">beIN Sports Max 3</channel>
<channel lang="ar" xmltv_id="beINSportsMax4.qa" site_id="sports#30">beIN Sports Max 4</channel>
<channel lang="ar" xmltv_id="beINSportsMax5.qa" site_id="sports#31">beIN Sports Max 5</channel>
<channel lang="ar" xmltv_id="beINSportsMax6.qa" site_id="sports#32">beIN Sports Max 6</channel>
<channel lang="ar" xmltv_id="AlkassThree.qa" site_id="sports#33">Alkass Three</channel>
<channel lang="ar" xmltv_id="AlkassOne.qa" site_id="sports#34">Alkass One</channel>
<channel lang="ar" xmltv_id="AlkassTwo.qa" site_id="sports#35">Alkass Two</channel>
<channel lang="ar" xmltv_id="AlkassFour.qa" site_id="sports#36">Alkass Four</channel>
<channel lang="ar" xmltv_id="AlkassFive.qa" site_id="sports#37">Alkass Five</channel>
<channel lang="ar" xmltv_id="AlkassSix.qa" site_id="sports#38">Alkass Six</channel>
<channel lang="ar" xmltv_id="AlkassSeven.qa" site_id="sports#39">Alkass Seven</channel>
<channel lang="ar" xmltv_id="AlkassEight.qa" site_id="sports#40">Alkass Eight</channel>
<channel lang="en" xmltv_id="beINMovies1Premiere.qa" site_id="entertainment#1">beIN Movies Premiere</channel>
<channel lang="en" xmltv_id="beINMovies2Action.qa" site_id="entertainment#2">beIN Movies Action</channel>
<channel lang="en" xmltv_id="beINMovies3Drama.qa" site_id="entertainment#3">bein Movies Drama</channel>
<channel lang="en" xmltv_id="beINMovies4Family.qa" site_id="entertainment#4">beIN Movies Family</channel>
<channel lang="en" xmltv_id="FoxMoviesMiddleEast.us" site_id="entertainment#5">FOX Movies</channel>
<channel lang="en" xmltv_id="FoxActionMoviesMiddleEast.hk" site_id="entertainment#6">FOX Action Movies</channel>
<channel lang="en" xmltv_id="StarMoviesMiddleEast.ae" site_id="entertainment#7">Star Movies</channel>
<channel lang="en" xmltv_id="beINSeries1.qa" site_id="entertainment#8">beIN Series 1</channel>
<channel lang="en" xmltv_id="beINSeries2.qa" site_id="entertainment#9">beIN Series 2</channel>
<channel lang="en" xmltv_id="beINDrama1.qa" site_id="entertainment#10">beIN Drama 1</channel>
<channel lang="en" xmltv_id="beINGourmet.qa" site_id="entertainment#11">beIN Gourmet</channel>
<channel lang="en" xmltv_id="TravelChannelEMEA.uk" site_id="entertainment#12">Travel Channel</channel>
<channel lang="en" xmltv_id="FoxArabia.ae" site_id="entertainment#13">FOX</channel>
<channel lang="en" xmltv_id="FoodNetworkEMEA.us" site_id="entertainment#14">Food Network</channel>
<channel lang="en" xmltv_id="HGTVArabia.us" site_id="entertainment#15">HGTV</channel>
<channel lang="en" xmltv_id="StarWorldMiddleEast.ae" site_id="entertainment#16">Star World</channel>
<channel lang="en" xmltv_id="Fatafeat.ae" site_id="entertainment#17">Fatafeat</channel>
<channel lang="en" xmltv_id="FoxLifeMiddleEast.ae" site_id="entertainment#18">FOX Life</channel>
<channel lang="en" xmltv_id="MTV80s.uk" site_id="entertainment#19">MTV 80s</channel>
<channel lang="en" xmltv_id="MTV90s.uk" site_id="entertainment#20">MTV 90s</channel>
<channel lang="en" xmltv_id="ClubMTVEurope.uk" site_id="entertainment#21">Club MTV</channel>
<channel lang="en" xmltv_id="BloombergTVMiddleEast.ae" site_id="entertainment#22">Bloomberg TV</channel>
<channel lang="en" xmltv_id="NationalGeographicMiddleEast.uk" site_id="entertainment#23">National Geographic</channel>
<channel lang="en" xmltv_id="NationalGeographicWildMiddleEast.uk" site_id="entertainment#24">National Geographic Wild</channel>
<channel lang="en" xmltv_id="BBCEarthMiddleEast.uk" site_id="entertainment#25">BBC Earth</channel>
<channel lang="en" xmltv_id="CNNArabic.ae" site_id="entertainment#26">CNN</channel>
<channel lang="en" xmltv_id="EuronewsEnglish.fr" site_id="entertainment#27">EuroNews</channel>
<channel lang="en" xmltv_id="DiscoveryChannelMiddleEastAfrica.us" site_id="entertainment#28">Discovery</channel>
<channel lang="en" xmltv_id="BeJunior.qa" site_id="entertainment#29">be Junior</channel>
<channel lang="en" xmltv_id="JeemTV.qa" site_id="entertainment#30">Jeem</channel>
<channel lang="en" xmltv_id="Baraem.qa" site_id="entertainment#31">Baraem</channel>
<channel lang="en" xmltv_id="CartoonNetworkMENA.uk" site_id="entertainment#32">Cartoon Network</channel>
<channel lang="en" xmltv_id="CartoonNetworkArabic.ae" site_id="entertainment#33">Cartoon Network Arabic</channel>
<channel lang="en" xmltv_id="CartoonNetworkHindi.in" site_id="entertainment#34">Cartoon Network Hindi</channel>
<channel lang="en" xmltv_id="BabyTV.uk" site_id="entertainment#35">Baby TV</channel>
<channel lang="en" xmltv_id="CBeebiesMiddleEast.uk" site_id="entertainment#36">CBeebies</channel>
<channel lang="en" xmltv_id="DreamWorksChannelMiddleEast.us" site_id="entertainment#37">DreamWorks</channel>
<channel lang="ar" xmltv_id="beINMovies1Premiere.qa" site_id="entertainment#1">beIN Movies Premiere</channel>
<channel lang="ar" xmltv_id="beINMovies2Action.qa" site_id="entertainment#2">beIN Movies Action</channel>
<channel lang="ar" xmltv_id="beINMovies3Drama.qa" site_id="entertainment#3">bein Movies Drama</channel>
<channel lang="ar" xmltv_id="beINMovies4Family.qa" site_id="entertainment#4">beIN Movies Family</channel>
<channel lang="ar" xmltv_id="FoxMoviesMiddleEast.us" site_id="entertainment#5">FOX Movies</channel>
<channel lang="ar" xmltv_id="FoxActionMoviesMiddleEast.hk" site_id="entertainment#6">FOX Action Movies</channel>
<channel lang="ar" xmltv_id="StarMoviesMiddleEast.ae" site_id="entertainment#7">Star Movies</channel>
<channel lang="ar" xmltv_id="beINSeries1.qa" site_id="entertainment#8">beIN Series 1</channel>
<channel lang="ar" xmltv_id="beINSeries2.qa" site_id="entertainment#9">beIN Series 2</channel>
<channel lang="ar" xmltv_id="beINDrama1.qa" site_id="entertainment#10">beIN Drama 1</channel>
<channel lang="ar" xmltv_id="beINGourmet.qa" site_id="entertainment#11">beIN Gourmet</channel>
<channel lang="ar" xmltv_id="TravelChannelEMEA.uk" site_id="entertainment#12">Travel Channel</channel>
<channel lang="ar" xmltv_id="FoxArabia.ae" site_id="entertainment#13">FOX</channel>
<channel lang="ar" xmltv_id="FoodNetworkEMEA.us" site_id="entertainment#14">Food Network</channel>
<channel lang="ar" xmltv_id="HGTVArabia.us" site_id="entertainment#15">HGTV</channel>
<channel lang="ar" xmltv_id="StarWorldMiddleEast.ae" site_id="entertainment#16">Star World</channel>
<channel lang="ar" xmltv_id="Fatafeat.ae" site_id="entertainment#17">Fatafeat</channel>
<channel lang="ar" xmltv_id="MTV80s.uk" site_id="entertainment#18">MTV 80s</channel>
<channel lang="ar" xmltv_id="MTV90s.uk" site_id="entertainment#19">MTV 90s</channel>
<channel lang="ar" xmltv_id="ClubMTVEurope.uk" site_id="entertainment#20">Club MTV</channel>
<channel lang="ar" xmltv_id="BeJunior.qa" site_id="entertainment#21">be Junior</channel>
<channel lang="ar" xmltv_id="BloombergTVMiddleEast.ae" site_id="entertainment#22">Bloomberg TV</channel>
<channel lang="ar" xmltv_id="NationalGeographicMiddleEast.uk" site_id="entertainment#23">National Geographic</channel>
<channel lang="ar" xmltv_id="NationalGeographicWildMiddleEast.uk" site_id="entertainment#24">National Geographic Wild</channel>
<channel lang="ar" xmltv_id="BBCEarthMiddleEast.uk" site_id="entertainment#25">BBC Earth</channel>
<channel lang="ar" xmltv_id="AlJazeeraDocumentary.qa" site_id="entertainment#26">Al Jazeera Documentary</channel>
<channel lang="ar" xmltv_id="CNNArabic.ae" site_id="entertainment#27">CNN</channel>
<channel lang="ar" xmltv_id="EuronewsEnglish.fr" site_id="entertainment#28">EuroNews</channel>
<channel lang="ar" xmltv_id="JeemTV.qa" site_id="entertainment#29">Jeem</channel>
<channel lang="ar" xmltv_id="Baraem.qa" site_id="entertainment#30">Baraem</channel>
<channel lang="ar" xmltv_id="CBeebiesMiddleEast.uk" site_id="entertainment#31">CBeebies</channel>
<channel lang="ar" xmltv_id="BabyTV.uk" site_id="entertainment#32">Baby TV</channel>
<channel lang="ar" xmltv_id="CartoonNetworkMENA.uk" site_id="entertainment#33">Cartoon Network</channel>
<channel lang="ar" xmltv_id="CartoonNetworkArabic.ae" site_id="entertainment#34">Cartoon Network Arabic</channel>
<channel lang="ar" xmltv_id="CartoonNetworkHindi.in" site_id="entertainment#35">Cartoon Network Hindi</channel>
<channel lang="ar" xmltv_id="DreamWorksChannelMiddleEast.us" site_id="entertainment#36">DreamWorks</channel>
</channels>
</site>

View file

@ -4,7 +4,6 @@ const { DateTime } = require('luxon')
module.exports = {
site: 'bein.com',
days: 2,
timeout: 30000, // 30 seconds
request: {
cache: {
ttl: 60 * 60 * 1000 // 1 hour
@ -63,7 +62,7 @@ function parseCategory($item) {
}
function parseTime($item, date) {
let [_, time] = $item('.time')
let [, time] = $item('.time')
.text()
.match(/^(\d{2}:\d{2})/) || [null, null]
if (!time) return null
@ -73,7 +72,7 @@ function parseTime($item, date) {
}
function parseItems(content, channel) {
const [_, channelId] = channel.site_id.split('#')
const [, channelId] = channel.site_id.split('#')
const $ = cheerio.load(content)
return $(`#channels_${channelId} .slider > ul:first-child > li`).toArray()

View file

@ -1,4 +1,4 @@
// npx epg-grabber --config=sites/bein.com/bein.com.config.js --channels=sites/bein.com/bein.com.channels.xml --output=guide.xml
// npm run grab -- --site=bein.com
const fs = require('fs')
const path = require('path')

View file

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<channels>
<channel site="bein.com" lang="ar" xmltv_id="AlJazeeraDocumentary.qa" site_id="entertainment#26">Al Jazeera Documentary</channel>
<channel site="bein.com" lang="ar" xmltv_id="AlkassEight.qa" site_id="sports#40">Alkass Eight</channel>
<channel site="bein.com" lang="ar" xmltv_id="AlkassFive.qa" site_id="sports#37">Alkass Five</channel>
<channel site="bein.com" lang="ar" xmltv_id="AlkassFour.qa" site_id="sports#36">Alkass Four</channel>
<channel site="bein.com" lang="ar" xmltv_id="AlkassOne.qa" site_id="sports#34">Alkass One</channel>
<channel site="bein.com" lang="ar" xmltv_id="AlkassSeven.qa" site_id="sports#39">Alkass Seven</channel>
<channel site="bein.com" lang="ar" xmltv_id="AlkassSix.qa" site_id="sports#38">Alkass Six</channel>
<channel site="bein.com" lang="ar" xmltv_id="AlkassThree.qa" site_id="sports#33">Alkass Three</channel>
<channel site="bein.com" lang="ar" xmltv_id="AlkassTwo.qa" site_id="sports#35">Alkass Two</channel>
<channel site="bein.com" lang="ar" xmltv_id="BabyTV.uk" site_id="entertainment#32">Baby TV</channel>
<channel site="bein.com" lang="ar" xmltv_id="Baraem.qa" site_id="entertainment#30">Baraem</channel>
<channel site="bein.com" lang="ar" xmltv_id="BBCEarthMiddleEast.uk" site_id="entertainment#25">BBC Earth</channel>
<channel site="bein.com" lang="ar" xmltv_id="beIN4K.qa" site_id="sports#15">BeIn 4K</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINDrama1.qa" site_id="entertainment#10">beIN Drama 1</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINGourmet.qa" site_id="entertainment#11">beIN Gourmet</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINMovies1Premiere.qa" site_id="entertainment#1">beIN Movies Premiere</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINMovies2Action.qa" site_id="entertainment#2">beIN Movies Action</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINMovies3Drama.qa" site_id="entertainment#3">bein Movies Drama</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINMovies4Family.qa" site_id="entertainment#4">beIN Movies Family</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSeries1.qa" site_id="entertainment#8">beIN Series 1</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSeries2.qa" site_id="entertainment#9">beIN Series 2</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSports.qa" site_id="sports#2">BeIn Sports</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSports1.qa" site_id="sports#3">BeIn Sports 1</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSports2.qa" site_id="sports#4">BeIn Sports 2</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSports3.qa" site_id="sports#5">BeIn Sports 3</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSports4.qa" site_id="sports#6">BeIn Sports 4</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSports5.qa" site_id="sports#7">BeIn Sports 5</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSports6.qa" site_id="sports#8">BeIn Sports 6</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSports7.qa" site_id="sports#9">BeIn Sports 7</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsAFC.qa" site_id="sports#16">BeIN Sports AFC</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsAFC1.qa" site_id="sports#17">BeIN Sports AFC 1</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsAFC2.qa" site_id="sports#18">BeIN Sports AFC 2</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsAFC3.qa" site_id="sports#19">BeIN Sports AFC 3</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsEnglish1.qa" site_id="sports#20">BeIn Sports English 1</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsEnglish2.qa" site_id="sports#21">BeIn Sports English 2</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsEnglish3.qa" site_id="sports#22">BeIn Sports English 3</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsFrench1.qa" site_id="sports#24">BeIn Sports French 1</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsFrench2.qa" site_id="sports#25">BeIn Sports French 2</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsFrench3.qa" site_id="sports#26">BeIn Sports French 3</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsMax1.qa" site_id="sports#27">beIN Sports Max 1</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsMax2.qa" site_id="sports#28">beIN Sports Max 2</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsMax3.qa" site_id="sports#29">beIN Sports Max 3</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsMax4.qa" site_id="sports#30">beIN Sports Max 4</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsMax5.qa" site_id="sports#31">beIN Sports Max 5</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsMax6.qa" site_id="sports#32">beIN Sports Max 6</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsNBA.qa" site_id="sports#23">BeIn NBA</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsNews.qa" site_id="sports#1">BeIn Sports News</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsPremium1.qa" site_id="sports#10">BeIn Sports Premium 1</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsPremium2.qa" site_id="sports#11">BeIn Sports Premium 2</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsPremium3.qa" site_id="sports#12">BeIn Sports Premium 3</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsXtra1.qa" site_id="sports#13">BeIn Sports Xtra 1</channel>
<channel site="bein.com" lang="ar" xmltv_id="beINSportsXtra2.qa" site_id="sports#14">BeIn Sports Xtra 2</channel>
<channel site="bein.com" lang="ar" xmltv_id="BeJunior.qa" site_id="entertainment#21">be Junior</channel>
<channel site="bein.com" lang="ar" xmltv_id="BloombergTVMiddleEast.ae" site_id="entertainment#22">Bloomberg TV</channel>
<channel site="bein.com" lang="ar" xmltv_id="CartoonNetworkArabic.ae" site_id="entertainment#34">Cartoon Network Arabic</channel>
<channel site="bein.com" lang="ar" xmltv_id="CartoonNetworkHindi.in" site_id="entertainment#35">Cartoon Network Hindi</channel>
<channel site="bein.com" lang="ar" xmltv_id="CartoonNetworkMENA.uk" site_id="entertainment#33">Cartoon Network</channel>
<channel site="bein.com" lang="ar" xmltv_id="CBeebiesMiddleEast.uk" site_id="entertainment#31">CBeebies</channel>
<channel site="bein.com" lang="ar" xmltv_id="ClubMTVEurope.uk" site_id="entertainment#20">Club MTV</channel>
<channel site="bein.com" lang="ar" xmltv_id="CNNArabic.ae" site_id="entertainment#27">CNN</channel>
<channel site="bein.com" lang="ar" xmltv_id="DreamWorksChannelMiddleEast.us" site_id="entertainment#36">DreamWorks</channel>
<channel site="bein.com" lang="ar" xmltv_id="EuronewsEnglish.fr" site_id="entertainment#28">EuroNews</channel>
<channel site="bein.com" lang="ar" xmltv_id="Fatafeat.ae" site_id="entertainment#17">Fatafeat</channel>
<channel site="bein.com" lang="ar" xmltv_id="FoodNetworkEMEA.us" site_id="entertainment#14">Food Network</channel>
<channel site="bein.com" lang="ar" xmltv_id="FoxActionMoviesMiddleEast.hk" site_id="entertainment#6">FOX Action Movies</channel>
<channel site="bein.com" lang="ar" xmltv_id="FoxArabia.ae" site_id="entertainment#13">FOX</channel>
<channel site="bein.com" lang="ar" xmltv_id="FoxMoviesMiddleEast.us" site_id="entertainment#5">FOX Movies</channel>
<channel site="bein.com" lang="ar" xmltv_id="HGTVArabia.us" site_id="entertainment#15">HGTV</channel>
<channel site="bein.com" lang="ar" xmltv_id="JeemTV.qa" site_id="entertainment#29">Jeem</channel>
<channel site="bein.com" lang="ar" xmltv_id="MTV80s.uk" site_id="entertainment#18">MTV 80s</channel>
<channel site="bein.com" lang="ar" xmltv_id="MTV90s.uk" site_id="entertainment#19">MTV 90s</channel>
<channel site="bein.com" lang="ar" xmltv_id="NationalGeographicMiddleEast.uk" site_id="entertainment#23">National Geographic</channel>
<channel site="bein.com" lang="ar" xmltv_id="NationalGeographicWildMiddleEast.uk" site_id="entertainment#24">National Geographic Wild</channel>
<channel site="bein.com" lang="ar" xmltv_id="StarMoviesMiddleEast.ae" site_id="entertainment#7">Star Movies</channel>
<channel site="bein.com" lang="ar" xmltv_id="StarWorldMiddleEast.ae" site_id="entertainment#16">Star World</channel>
<channel site="bein.com" lang="ar" xmltv_id="TravelChannelEMEA.uk" site_id="entertainment#12">Travel Channel</channel>
</channels>

View file

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<channels>
<channel site="bein.com" lang="en" xmltv_id="AlkassEight.qa" site_id="sports#40">Alkass Eight</channel>
<channel site="bein.com" lang="en" xmltv_id="AlkassFive.qa" site_id="sports#37">Alkass Five</channel>
<channel site="bein.com" lang="en" xmltv_id="AlkassFour.qa" site_id="sports#36">Alkass Four</channel>
<channel site="bein.com" lang="en" xmltv_id="AlkassOne.qa" site_id="sports#33">Alkass One</channel>
<channel site="bein.com" lang="en" xmltv_id="AlkassSeven.qa" site_id="sports#39">Alkass Seven</channel>
<channel site="bein.com" lang="en" xmltv_id="AlkassSix.qa" site_id="sports#38">Alkass Six</channel>
<channel site="bein.com" lang="en" xmltv_id="AlkassThree.qa" site_id="sports#35">Alkass Three</channel>
<channel site="bein.com" lang="en" xmltv_id="AlkassTwo.qa" site_id="sports#34">Alkass Two</channel>
<channel site="bein.com" lang="en" xmltv_id="BabyTV.uk" site_id="entertainment#35">Baby TV</channel>
<channel site="bein.com" lang="en" xmltv_id="Baraem.qa" site_id="entertainment#31">Baraem</channel>
<channel site="bein.com" lang="en" xmltv_id="BBCEarthMiddleEast.uk" site_id="entertainment#25">BBC Earth</channel>
<channel site="bein.com" lang="en" xmltv_id="beIN4K.qa" site_id="sports#15">BeIn 4K</channel>
<channel site="bein.com" lang="en" xmltv_id="beINDrama1.qa" site_id="entertainment#10">beIN Drama 1</channel>
<channel site="bein.com" lang="en" xmltv_id="beINGourmet.qa" site_id="entertainment#11">beIN Gourmet</channel>
<channel site="bein.com" lang="en" xmltv_id="beINMovies1Premiere.qa" site_id="entertainment#1">beIN Movies Premiere</channel>
<channel site="bein.com" lang="en" xmltv_id="beINMovies2Action.qa" site_id="entertainment#2">beIN Movies Action</channel>
<channel site="bein.com" lang="en" xmltv_id="beINMovies3Drama.qa" site_id="entertainment#3">bein Movies Drama</channel>
<channel site="bein.com" lang="en" xmltv_id="beINMovies4Family.qa" site_id="entertainment#4">beIN Movies Family</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSeries1.qa" site_id="entertainment#8">beIN Series 1</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSeries2.qa" site_id="entertainment#9">beIN Series 2</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSports.qa" site_id="sports#2">BeIn Sports</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSports1.qa" site_id="sports#3">BeIn Sports 1</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSports2.qa" site_id="sports#4">BeIn Sports 2</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSports3.qa" site_id="sports#5">BeIn Sports 3</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSports4.qa" site_id="sports#6">BeIn Sports 4</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSports5.qa" site_id="sports#7">BeIn Sports 5</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSports6.qa" site_id="sports#8">BeIn Sports 6</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSports7.qa" site_id="sports#9">BeIn Sports 7</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsAFC.qa" site_id="sports#16">BeIN Sports AFC</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsAFC1.qa" site_id="sports#17">BeIN Sports AFC 1</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsAFC2.qa" site_id="sports#18">BeIN Sports AFC 2</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsAFC3.qa" site_id="sports#19">BeIN Sports AFC 3</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsEnglish1.qa" site_id="sports#20">BeIn Sports English 1</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsEnglish2.qa" site_id="sports#21">BeIn Sports English 2</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsEnglish3.qa" site_id="sports#22">BeIn Sports English 3</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsFrench1.qa" site_id="sports#24">BeIn Sports French 1</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsFrench2.qa" site_id="sports#25">BeIn Sports French 2</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsFrench3.qa" site_id="sports#26">BeIn Sports French 3</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsMax1.qa" site_id="sports#27">beIN Sports Max 1</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsMax2.qa" site_id="sports#28">beIN Sports Max 2</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsMax3.qa" site_id="sports#29">beIN Sports Max 3</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsMax4.qa" site_id="sports#30">beIN Sports Max 4</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsMax5.qa" site_id="sports#31">beIN Sports Max 5</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsMax6.qa" site_id="sports#32">beIN Sports Max 6</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsNBA.qa" site_id="sports#23">BeIn NBA</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsNews.qa" site_id="sports#1">BeIn Sports News</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsPremium1.qa" site_id="sports#10">BeIn Sports Premium 1</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsPremium2.qa" site_id="sports#11">BeIn Sports Premium 2</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsPremium3.qa" site_id="sports#12">BeIn Sports Premium 3</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsXtra1.qa" site_id="sports#13">BeIn Sports Xtra 1</channel>
<channel site="bein.com" lang="en" xmltv_id="beINSportsXtra2.qa" site_id="sports#14">BeIn Sports Xtra 2</channel>
<channel site="bein.com" lang="en" xmltv_id="BeJunior.qa" site_id="entertainment#29">be Junior</channel>
<channel site="bein.com" lang="en" xmltv_id="BloombergTVMiddleEast.ae" site_id="entertainment#22">Bloomberg TV</channel>
<channel site="bein.com" lang="en" xmltv_id="CartoonNetworkArabic.ae" site_id="entertainment#33">Cartoon Network Arabic</channel>
<channel site="bein.com" lang="en" xmltv_id="CartoonNetworkHindi.in" site_id="entertainment#34">Cartoon Network Hindi</channel>
<channel site="bein.com" lang="en" xmltv_id="CartoonNetworkMENA.uk" site_id="entertainment#32">Cartoon Network</channel>
<channel site="bein.com" lang="en" xmltv_id="CBeebiesMiddleEast.uk" site_id="entertainment#36">CBeebies</channel>
<channel site="bein.com" lang="en" xmltv_id="ClubMTVEurope.uk" site_id="entertainment#21">Club MTV</channel>
<channel site="bein.com" lang="en" xmltv_id="CNNArabic.ae" site_id="entertainment#26">CNN</channel>
<channel site="bein.com" lang="en" xmltv_id="DiscoveryChannelMiddleEastAfrica.us" site_id="entertainment#28">Discovery</channel>
<channel site="bein.com" lang="en" xmltv_id="DreamWorksChannelMiddleEast.us" site_id="entertainment#37">DreamWorks</channel>
<channel site="bein.com" lang="en" xmltv_id="EuronewsEnglish.fr" site_id="entertainment#27">EuroNews</channel>
<channel site="bein.com" lang="en" xmltv_id="Fatafeat.ae" site_id="entertainment#17">Fatafeat</channel>
<channel site="bein.com" lang="en" xmltv_id="FoodNetworkEMEA.us" site_id="entertainment#14">Food Network</channel>
<channel site="bein.com" lang="en" xmltv_id="FoxActionMoviesMiddleEast.hk" site_id="entertainment#6">FOX Action Movies</channel>
<channel site="bein.com" lang="en" xmltv_id="FoxArabia.ae" site_id="entertainment#13">FOX</channel>
<channel site="bein.com" lang="en" xmltv_id="FoxLifeMiddleEast.ae" site_id="entertainment#18">FOX Life</channel>
<channel site="bein.com" lang="en" xmltv_id="FoxMoviesMiddleEast.us" site_id="entertainment#5">FOX Movies</channel>
<channel site="bein.com" lang="en" xmltv_id="HGTVArabia.us" site_id="entertainment#15">HGTV</channel>
<channel site="bein.com" lang="en" xmltv_id="JeemTV.qa" site_id="entertainment#30">Jeem</channel>
<channel site="bein.com" lang="en" xmltv_id="MTV80s.uk" site_id="entertainment#19">MTV 80s</channel>
<channel site="bein.com" lang="en" xmltv_id="MTV90s.uk" site_id="entertainment#20">MTV 90s</channel>
<channel site="bein.com" lang="en" xmltv_id="NationalGeographicMiddleEast.uk" site_id="entertainment#23">National Geographic</channel>
<channel site="bein.com" lang="en" xmltv_id="NationalGeographicWildMiddleEast.uk" site_id="entertainment#24">National Geographic Wild</channel>
<channel site="bein.com" lang="en" xmltv_id="StarMoviesMiddleEast.ae" site_id="entertainment#7">Star Movies</channel>
<channel site="bein.com" lang="en" xmltv_id="StarWorldMiddleEast.ae" site_id="entertainment#16">Star World</channel>
<channel site="bein.com" lang="en" xmltv_id="TravelChannelEMEA.uk" site_id="entertainment#12">Travel Channel</channel>
</channels>

View file

@ -26,7 +26,7 @@ module.exports = {
'YYYY-MM-DD'
)}`
},
parser: function ({ content, channel, date, cached }) {
parser: function ({ content, channel, date }) {
let programs = []
const items = parseItems(content, channel)
let i = 0
@ -68,15 +68,15 @@ module.exports = {
.then(r => r.data)
.catch(console.log)
const $ = cheerio.load(content)
const items = $(`.container > div, #epg_div > div`).toArray()
const items = $('.container > div, #epg_div > div').toArray()
return items
.map(item => {
const $item = cheerio.load(item)
const id = $item('*').attr('id')
if (!/^channels\_[0-9]+$/.test(id)) return null
if (!/^channels_[0-9]+$/.test(id)) return null
const channelId = id.replace('channels_', '')
const imgSrc = $item('img').attr('src')
const [_, __, name] = imgSrc.match(/(\/|)([a-z0-9-_.]+)(.png|.svg)$/i) || [null, null, '']
const [, , name] = imgSrc.match(/(\/|)([a-z0-9-_.]+)(.png|.svg)$/i) || [null, null, '']
return {
lang,
@ -103,7 +103,7 @@ function parseCategory($item) {
function parseStart($item, date) {
let time = $item('.time').text()
if (!time) return null
let [_, start, period] = time.match(/^(\d{2}:\d{2})( AM| PM|)/) || [null, null, null]
let [, start, period] = time.match(/^(\d{2}:\d{2})( AM| PM|)/) || [null, null, null]
if (!start) return null
start = `${date.format('YYYY-MM-DD')} ${start}${period}`
const format = period ? 'YYYY-MM-DD hh:mm A' : 'YYYY-MM-DD HH:mm'
@ -114,7 +114,7 @@ function parseStart($item, date) {
function parseStop($item, date) {
let time = $item('.time').text()
if (!time) return null
let [_, stop, period] = time.match(/(\d{2}:\d{2})( AM| PM|)$/) || [null, null, null]
let [, stop, period] = time.match(/(\d{2}:\d{2})( AM| PM|)$/) || [null, null, null]
if (!stop) return null
stop = `${date.format('YYYY-MM-DD')} ${stop}${period}`
const format = period ? 'YYYY-MM-DD hh:mm A' : 'YYYY-MM-DD HH:mm'
@ -123,7 +123,7 @@ function parseStop($item, date) {
}
function parseItems(content, channel) {
const [_, channelId] = channel.site_id.split('#')
const [, channelId] = channel.site_id.split('#')
const $ = cheerio.load(content)
return $(`#channels_${channelId} .slider > ul:first-child > li`).toArray()

View file

@ -1,6 +1,6 @@
// npm run channels:parse -- --config=./sites/beinsports.com/beinsports.com.config.js --output=./sites/beinsports.com/beinsports.com_qa-ar.channels.xml --set=lang:ar --set=region:ar
// npx epg-grabber --config=sites/beinsports.com/beinsports.com.config.js --channels=sites/beinsports.com/beinsports.com_qa-en.channels.xml --output=guide.xml --timeout=30000 --days=2
// npx epg-grabber --config=sites/beinsports.com/beinsports.com.config.js --channels=sites/beinsports.com/beinsports.com_us-en.channels.xml --output=guide.xml --timeout=30000 --days=2
// npm run grab -- --site=beinsports.com
// npm run grab -- --site=beinsports.com
const { parser, url } = require('./beinsports.com.config.js')
const fs = require('fs')

View file

@ -1,8 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="beinsports.com">
<channels>
<channel lang="en" xmltv_id="beINSports1Australia.au" site_id="au#1">BeIn Sports 1 Australia</channel>
<channel lang="en" xmltv_id="beINSports2Australia.au" site_id="au#2">BeIn Sports 2 Australia</channel>
<channel lang="en" xmltv_id="beINSports3Australia.au" site_id="au#3">BeIn Sports 3 Australia</channel>
</channels>
</site>
<channels>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports1Australia.au" site_id="au#1">BeIn Sports 1 Australia</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports2Australia.au" site_id="au#2">BeIn Sports 2 Australia</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports3Australia.au" site_id="au#3">BeIn Sports 3 Australia</channel>
</channels>

View file

@ -1,15 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="beinsports.com">
<channels>
<channel lang="fr" xmltv_id="beINSports1France.fr" site_id="france#1">BeIN Sports 1 HD France</channel>
<channel lang="fr" xmltv_id="beINSports2France.fr" site_id="france#2">BeIN Sports 2 HD France</channel>
<channel lang="fr" xmltv_id="beINSports3France.fr" site_id="france#3">BeIN Sports 3 France</channel>
<channel lang="fr" xmltv_id="beINSportsMax4France.fr" site_id="france#4">BeIN Sports Max 4 France</channel>
<channel lang="fr" xmltv_id="beINSportsMax5France.fr" site_id="france#5">BeIN Sports Max 5 France</channel>
<channel lang="fr" xmltv_id="beINSportsMax6France.fr" site_id="france#6">BeIN Sports Max 6 France</channel>
<channel lang="fr" xmltv_id="beINSportsMax7France.fr" site_id="france#7">BeIN Sports Max 7 France</channel>
<channel lang="fr" xmltv_id="beINSportsMax8France.fr" site_id="france#8">BeIN Sports Max 8 France</channel>
<channel lang="fr" xmltv_id="beINSportsMax9France.fr" site_id="france#9">BeIN Sports Max 9 France</channel>
<channel lang="fr" xmltv_id="beINSportsMax10France.fr" site_id="france#10">BeIN Sports Max 10 France</channel>
</channels>
</site>
<channels>
<channel site="beinsports.com" lang="fr" xmltv_id="beINSports1France.fr" site_id="france#1">BeIN Sports 1 HD France</channel>
<channel site="beinsports.com" lang="fr" xmltv_id="beINSports2France.fr" site_id="france#2">BeIN Sports 2 HD France</channel>
<channel site="beinsports.com" lang="fr" xmltv_id="beINSports3France.fr" site_id="france#3">BeIN Sports 3 France</channel>
<channel site="beinsports.com" lang="fr" xmltv_id="beINSportsMax4France.fr" site_id="france#4">BeIN Sports Max 4 France</channel>
<channel site="beinsports.com" lang="fr" xmltv_id="beINSportsMax5France.fr" site_id="france#5">BeIN Sports Max 5 France</channel>
<channel site="beinsports.com" lang="fr" xmltv_id="beINSportsMax6France.fr" site_id="france#6">BeIN Sports Max 6 France</channel>
<channel site="beinsports.com" lang="fr" xmltv_id="beINSportsMax7France.fr" site_id="france#7">BeIN Sports Max 7 France</channel>
<channel site="beinsports.com" lang="fr" xmltv_id="beINSportsMax8France.fr" site_id="france#8">BeIN Sports Max 8 France</channel>
<channel site="beinsports.com" lang="fr" xmltv_id="beINSportsMax9France.fr" site_id="france#9">BeIN Sports Max 9 France</channel>
<channel site="beinsports.com" lang="fr" xmltv_id="beINSportsMax10France.fr" site_id="france#10">BeIN Sports Max 10 France</channel>
</channels>

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="beinsports.com">
<channels>
<channel lang="en" xmltv_id="beINSports1HongKong.hk" site_id="hk#1">BeIN Sports 1 Hong Kong</channel>
<channel lang="en" xmltv_id="beINSports2HongKong.hk" site_id="hk#2">BeIN Sports 2 Hong Kong</channel>
</channels>
</site>
<channels>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports1HongKong.hk" site_id="hk#1">BeIN Sports 1 Hong Kong</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports2HongKong.hk" site_id="hk#2">BeIN Sports 2 Hong Kong</channel>
</channels>

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="beinsports.com">
<channels>
<channel lang="en" xmltv_id="beINSports1Indonesia.id" site_id="id#1">BeIN Sports 1 Indonesia</channel>
<channel lang="en" xmltv_id="beINSports3Indonesia.id" site_id="id#2">BeIN Sports 3 Indonesia</channel>
</channels>
</site>
<channels>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports1Indonesia.id" site_id="id#1">BeIN Sports 1 Indonesia</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports3Indonesia.id" site_id="id#2">BeIN Sports 3 Indonesia</channel>
</channels>

View file

@ -1,6 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="beinsports.com">
<channels>
<channel lang="en" xmltv_id="beINSports1MalaysiaSingapore.my" site_id="my#1">BeIN Sports 1 Malaysia &amp; Singapore</channel>
</channels>
</site>
<channels>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports1MalaysiaSingapore.my" site_id="my#1">BeIN Sports 1 Malaysia &amp; Singapore</channel>
</channels>

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="beinsports.com">
<channels>
<channel lang="en" xmltv_id="beINSports1Philippines.ph" site_id="id#1">BeIN Sports 1 Philippines</channel>
<channel lang="en" xmltv_id="beINSports3Philippines.ph" site_id="id#2">BeIN Sports 3 Philippines</channel>
</channels>
</site>
<channels>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports1Philippines.ph" site_id="id#1">BeIN Sports 1 Philippines</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports3Philippines.ph" site_id="id#2">BeIN Sports 3 Philippines</channel>
</channels>

View file

@ -1,30 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="beinsports.com">
<channels>
<channel lang="ar" xmltv_id="beINSports.qa" site_id="ar#1">beIN SPORTS FTA</channel>
<channel lang="ar" xmltv_id="beINSportsNews.qa" site_id="ar#2">beIN SPORTS News</channel>
<channel lang="ar" xmltv_id="beINSports2.qa" site_id="ar#3">beIN SPORTS2</channel>
<channel lang="ar" xmltv_id="beINSports3.qa" site_id="ar#4">beIN SPORTS3</channel>
<channel lang="ar" xmltv_id="beINSports4.qa" site_id="ar#5">beIN SPORTS4</channel>
<channel lang="ar" xmltv_id="beINSports5.qa" site_id="ar#6">beIN SPORTS5</channel>
<channel lang="ar" xmltv_id="beINSports6.qa" site_id="ar#7">beIN SPORTS6</channel>
<channel lang="ar" xmltv_id="beINSports7.qa" site_id="ar#8">beIN SPORTS7</channel>
<channel lang="ar" xmltv_id="beINSportsPremium3.qa" site_id="ar#9">beIN SPORTS3 PREMIUM</channel>
<channel lang="ar" xmltv_id="beINSportsXtra1.qa" site_id="ar#10">beIN SPORTS XTRA1</channel>
<channel lang="ar" xmltv_id="beINSportsXtra2.qa" site_id="ar#11">beIN SPORTS XTRA2</channel>
<channel lang="ar" xmltv_id="beIN4K.qa" site_id="ar#12">beIN 4k</channel>
<channel lang="ar" xmltv_id="beINSportsAFC.qa" site_id="ar#13">beIN SPORTS AFC</channel>
<channel lang="ar" xmltv_id="beINSportsAFC1.qa" site_id="ar#14">beIN SPORTS AFC1</channel>
<channel lang="ar" xmltv_id="beINSportsAFC2.qa" site_id="ar#15">beIN SPORTS AFC2</channel>
<channel lang="ar" xmltv_id="beINSportsAFC3.qa" site_id="ar#16">beIN SPORTS AFC3</channel>
<channel lang="ar" xmltv_id="beINSportsNBA.qa" site_id="ar#17">beIN SPORTS NBA</channel>
<channel lang="ar" xmltv_id="beINSportsEnglish1.qa" site_id="ar#18">beIN SPORTS1 ENGLISH</channel>
<channel lang="ar" xmltv_id="beINSportsEnglish2.qa" site_id="ar#19">beIN SPORTS2 ENGLISH</channel>
<channel lang="ar" xmltv_id="beINSportsEnglish3.qa" site_id="ar#20">beIN SPORTS3 ENGLISH</channel>
<channel lang="ar" xmltv_id="beINSportsFrench1.qa" site_id="ar#21">beIN SPORTS1 FRENCH</channel>
<channel lang="ar" xmltv_id="beINSportsFrench2.qa" site_id="ar#22">beIN SPORTS2 FRENCH</channel>
<channel lang="ar" xmltv_id="beINSportsFrench3.qa" site_id="ar#23">beIN SPORTS3 FRENCH</channel>
<channel lang="ar" xmltv_id="beINSportsMax1.qa" site_id="ar#24">beIN SPORTS MAX 1</channel>
<channel lang="ar" xmltv_id="beINSportsMax2.qa" site_id="ar#25">beIN SPORTS MAX 2</channel>
</channels>
</site>
<channels>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSports.qa" site_id="ar#1">beIN SPORTS FTA</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSportsNews.qa" site_id="ar#2">beIN SPORTS News</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSports2.qa" site_id="ar#3">beIN SPORTS2</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSports3.qa" site_id="ar#4">beIN SPORTS3</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSports4.qa" site_id="ar#5">beIN SPORTS4</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSports5.qa" site_id="ar#6">beIN SPORTS5</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSports6.qa" site_id="ar#7">beIN SPORTS6</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSports7.qa" site_id="ar#8">beIN SPORTS7</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSportsPremium3.qa" site_id="ar#9">beIN SPORTS3 PREMIUM</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSportsXtra1.qa" site_id="ar#10">beIN SPORTS XTRA1</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSportsXtra2.qa" site_id="ar#11">beIN SPORTS XTRA2</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beIN4K.qa" site_id="ar#12">beIN 4k</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSportsAFC.qa" site_id="ar#13">beIN SPORTS AFC</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSportsAFC1.qa" site_id="ar#14">beIN SPORTS AFC1</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSportsAFC2.qa" site_id="ar#15">beIN SPORTS AFC2</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSportsAFC3.qa" site_id="ar#16">beIN SPORTS AFC3</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSportsNBA.qa" site_id="ar#17">beIN SPORTS NBA</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSportsEnglish1.qa" site_id="ar#18">beIN SPORTS1 ENGLISH</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSportsEnglish2.qa" site_id="ar#19">beIN SPORTS2 ENGLISH</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSportsEnglish3.qa" site_id="ar#20">beIN SPORTS3 ENGLISH</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSportsFrench1.qa" site_id="ar#21">beIN SPORTS1 FRENCH</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSportsFrench2.qa" site_id="ar#22">beIN SPORTS2 FRENCH</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSportsFrench3.qa" site_id="ar#23">beIN SPORTS3 FRENCH</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSportsMax1.qa" site_id="ar#24">beIN SPORTS MAX 1</channel>
<channel site="beinsports.com" lang="ar" xmltv_id="beINSportsMax2.qa" site_id="ar#25">beIN SPORTS MAX 2</channel>
</channels>

View file

@ -1,24 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="beinsports.com">
<channels>
<channel lang="en" xmltv_id="beINSports.qa" site_id="#1">BeIn Sports</channel>
<channel lang="en" xmltv_id="beINSportsNews.qa" site_id="#2">BeIn Sports News</channel>
<channel lang="en" xmltv_id="beINSports2.qa" site_id="#3">BeIn Sports 2</channel>
<channel lang="en" xmltv_id="beINSports3.qa" site_id="#4">BeIn Sports 3</channel>
<channel lang="en" xmltv_id="beINSports4.qa" site_id="#5">BeIn Sports 4</channel>
<channel lang="en" xmltv_id="beINSports5.qa" site_id="#6">BeIn Sports 5</channel>
<channel lang="en" xmltv_id="beINSports6.qa" site_id="#7">BeIn Sports 6</channel>
<channel lang="en" xmltv_id="beINSports7.qa" site_id="#8">BeIn Sports 7</channel>
<channel lang="en" xmltv_id="beINSportsPremium3.qa" site_id="#9">BeIn Sports Premium 3</channel>
<channel lang="en" xmltv_id="beINSportsXtra1.qa" site_id="#10">BeIn Sports Xtra 1</channel>
<channel lang="en" xmltv_id="beINSportsXtra2.qa" site_id="#11">BeIn Sports Xtra 2</channel>
<channel lang="en" xmltv_id="beIN4K.qa" site_id="#12">BeIn 4K</channel>
<channel lang="en" xmltv_id="beINSportsNBA.qa" site_id="#13">BeIn NBA</channel>
<channel lang="en" xmltv_id="beINSportsEnglish1.qa" site_id="#14">BeIn Sports English 1</channel>
<channel lang="en" xmltv_id="beINSportsEnglish2.qa" site_id="#15">BeIn Sports English 2</channel>
<channel lang="en" xmltv_id="beINSportsEnglish3.qa" site_id="#16">BeIn Sports English 3</channel>
<channel lang="fr" xmltv_id="beINSportsFrench1.qa" site_id="#17">BeIn Sports French 1</channel>
<channel lang="fr" xmltv_id="beINSportsFrench2.qa" site_id="#18">BeIn Sports French 2</channel>
<channel lang="fr" xmltv_id="beINSportsFrench3.qa" site_id="#19">BeIn Sports French 3</channel>
</channels>
</site>
<channels>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports.qa" site_id="#1">BeIn Sports</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSportsNews.qa" site_id="#2">BeIn Sports News</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports2.qa" site_id="#3">BeIn Sports 2</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports3.qa" site_id="#4">BeIn Sports 3</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports4.qa" site_id="#5">BeIn Sports 4</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports5.qa" site_id="#6">BeIn Sports 5</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports6.qa" site_id="#7">BeIn Sports 6</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports7.qa" site_id="#8">BeIn Sports 7</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSportsPremium3.qa" site_id="#9">BeIn Sports Premium 3</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSportsXtra1.qa" site_id="#10">BeIn Sports Xtra 1</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSportsXtra2.qa" site_id="#11">BeIn Sports Xtra 2</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beIN4K.qa" site_id="#12">BeIn 4K</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSportsNBA.qa" site_id="#13">BeIn NBA</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSportsEnglish1.qa" site_id="#14">BeIn Sports English 1</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSportsEnglish2.qa" site_id="#15">BeIn Sports English 2</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSportsEnglish3.qa" site_id="#16">BeIn Sports English 3</channel>
<channel site="beinsports.com" lang="fr" xmltv_id="beINSportsFrench1.qa" site_id="#17">BeIn Sports French 1</channel>
<channel site="beinsports.com" lang="fr" xmltv_id="beINSportsFrench2.qa" site_id="#18">BeIn Sports French 2</channel>
<channel site="beinsports.com" lang="fr" xmltv_id="beINSportsFrench3.qa" site_id="#19">BeIn Sports French 3</channel>
</channels>

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="beinsports.com">
<channels>
<channel lang="en" xmltv_id="beINSports1Thailand.th" site_id="th#1">BeIN Sports 1 Thailand</channel>
<channel lang="en" xmltv_id="beINSports3Thailand.th" site_id="th#2">BeIN Sports 3 Thailand</channel>
</channels>
</site>
<channels>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports1Thailand.th" site_id="th#1">BeIN Sports 1 Thailand</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports3Thailand.th" site_id="th#2">BeIN Sports 3 Thailand</channel>
</channels>

View file

@ -1,15 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="beinsports.com">
<channels>
<channel lang="en" xmltv_id="beINSportsUSA.us" site_id="us#1">BeIN Sports USA</channel>
<channel lang="en" xmltv_id="beINSportsenEspanol.us" site_id="us#2">BeIN Sports en Español</channel>
<channel lang="en" xmltv_id="beINSPORTSXTRA.us" site_id="us#3">BeIN Sports Xtra USA</channel>
<channel lang="en" xmltv_id="beINSPORTSXTRAenEspanol.us" site_id="us#4">BeIN Sports Xtra en Español</channel>
<channel lang="en" xmltv_id="beINSports3USA.us" site_id="us#5">BeIN Sports 3 USA</channel>
<channel lang="en" xmltv_id="beINSports4USA.us" site_id="us#6">BeIN Sports 4 USA</channel>
<channel lang="en" xmltv_id="beINSports5USA.us" site_id="us#7">BeIN Sports 5 USA</channel>
<channel lang="en" xmltv_id="beINSports6USA.us" site_id="us#8">BeIN Sports 6 USA</channel>
<channel lang="en" xmltv_id="beINSports7USA.us" site_id="us#9">BeIN Sports 7 USA</channel>
<channel lang="en" xmltv_id="beINSports8USA.us" site_id="us#10">BeIN Sports 8 USA</channel>
</channels>
</site>
<channels>
<channel site="beinsports.com" lang="en" xmltv_id="beINSportsUSA.us" site_id="us#1">BeIN Sports USA</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSportsenEspanol.us" site_id="us#2">BeIN Sports en Español</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSPORTSXTRA.us" site_id="us#3">BeIN Sports Xtra USA</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSPORTSXTRAenEspanol.us" site_id="us#4">BeIN Sports Xtra en Español</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports3USA.us" site_id="us#5">BeIN Sports 3 USA</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports4USA.us" site_id="us#6">BeIN Sports 4 USA</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports5USA.us" site_id="us#7">BeIN Sports 5 USA</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports6USA.us" site_id="us#8">BeIN Sports 6 USA</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports7USA.us" site_id="us#9">BeIN Sports 7 USA</channel>
<channel site="beinsports.com" lang="en" xmltv_id="beINSports8USA.us" site_id="us#10">BeIN Sports 8 USA</channel>
</channels>

View file

@ -1,15 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="beinsports.com">
<channels>
<channel lang="es" xmltv_id="beINSportsUSA.us" site_id="us_es#1">BeIN Sports USA</channel>
<channel lang="es" xmltv_id="beINSportsenEspanol.us" site_id="us_es#2">BeIN Sports en Español</channel>
<channel lang="es" xmltv_id="beINSPORTSXTRA.us" site_id="us_es#3">BeIN Sports Xtra USA</channel>
<channel lang="es" xmltv_id="beINSPORTSXTRAenEspanol.us" site_id="us_es#4">BeIN Sports Xtra en Español</channel>
<channel lang="es" xmltv_id="beINSports3USA.us" site_id="us_es#5">BeIN Sports 3 USA</channel>
<channel lang="es" xmltv_id="beINSports4USA.us" site_id="us_es#6">BeIN Sports 4 USA</channel>
<channel lang="es" xmltv_id="beINSports5USA.us" site_id="us_es#7">BeIN Sports 5 USA</channel>
<channel lang="es" xmltv_id="beINSports6USA.us" site_id="us_es#8">BeIN Sports 6 USA</channel>
<channel lang="es" xmltv_id="beINSports7USA.us" site_id="us_es#9">BeIN Sports 7 USA</channel>
<channel lang="es" xmltv_id="beINSports8USA.us" site_id="us_es#10">BeIN Sports 8 USA</channel>
</channels>
</site>
<channels>
<channel site="beinsports.com" lang="es" xmltv_id="beINSportsUSA.us" site_id="us_es#1">BeIN Sports USA</channel>
<channel site="beinsports.com" lang="es" xmltv_id="beINSportsenEspanol.us" site_id="us_es#2">BeIN Sports en Español</channel>
<channel site="beinsports.com" lang="es" xmltv_id="beINSPORTSXTRA.us" site_id="us_es#3">BeIN Sports Xtra USA</channel>
<channel site="beinsports.com" lang="es" xmltv_id="beINSPORTSXTRAenEspanol.us" site_id="us_es#4">BeIN Sports Xtra en Español</channel>
<channel site="beinsports.com" lang="es" xmltv_id="beINSports3USA.us" site_id="us_es#5">BeIN Sports 3 USA</channel>
<channel site="beinsports.com" lang="es" xmltv_id="beINSports4USA.us" site_id="us_es#6">BeIN Sports 4 USA</channel>
<channel site="beinsports.com" lang="es" xmltv_id="beINSports5USA.us" site_id="us_es#7">BeIN Sports 5 USA</channel>
<channel site="beinsports.com" lang="es" xmltv_id="beINSports6USA.us" site_id="us_es#8">BeIN Sports 6 USA</channel>
<channel site="beinsports.com" lang="es" xmltv_id="beINSports7USA.us" site_id="us_es#9">BeIN Sports 7 USA</channel>
<channel site="beinsports.com" lang="es" xmltv_id="beINSports8USA.us" site_id="us_es#10">BeIN Sports 8 USA</channel>
</channels>

Some files were not shown because too many files have changed in this diff Show more