Merge branch 'dev-branch' of https://github.com/arifbudiman/epg into dev-branch

This commit is contained in:
Arif Budiman 2023-08-26 11:52:38 -07:00
commit 16370d2c2e
30 changed files with 2036 additions and 1479 deletions

63
Jenkinsfile vendored
View file

@ -1,63 +0,0 @@
List target_sites = (env.TARGET_SITES == null) ? [] : env.TARGET_SITES.split(';')
List exclude_sites = (env.EXCLUDE_SITES == null) ? [] : env.EXCLUDE_SITES.split(';')
target_sites.removeAll { it in exclude_sites }
Map matrix_axes = [
SITE: target_sites
]
@NonCPS
List getMatrixAxes(Map matrix_axes) {
List axes = []
matrix_axes.each { axis, values ->
List axisList = []
values.each { value ->
axisList << [(axis): value]
}
axes << axisList
}
axes.combinations()*.sum()
}
List axes = getMatrixAxes(matrix_axes)
Map tasks = [failFast: false]
for(int i = 0; i < axes.size(); i++) {
Map axis = axes[i]
List axisEnv = axis.collect { k, v ->
"${k}=${v}"
}
tasks[axisEnv.join(', ')] = { ->
env.NODEJS_HOME = "${tool 'node'}"
env.PATH="${env.NODEJS_HOME}/bin:${env.PATH}"
node {
skipDefaultCheckout()
withEnv(axisEnv) {
try {
cleanWs()
checkout scm
sh 'npm install'
sh "npm run grab"
} finally {
archiveArtifacts artifacts: "guides/**/*.xml", onlyIfSuccessful: true
cleanWs(
cleanWhenNotBuilt: false,
deleteDirs: true,
disableDeferredWipeout: true,
notFailBuild: true,
patterns: [[pattern: '.gitignore', type: 'INCLUDE'],
[pattern: '.propsfile', type: 'EXCLUDE']])
}
}
}
}
}
node {
stage('Load') {
parallel(tasks)
}
}

View file

@ -42,19 +42,34 @@ 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: 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:
```sh ```sh
# Windows npm run grab -- --site=example.com
set SITE=example.com&& npm run grab ```
# Linux/macOS 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:
SITE=example.com npm run grab
```sh
npm run grab -- --site=example.com --lang=fr
```
To also create a compressed version of the guide, add the `--gzip` flag:
```sh
npm run grab -- --site=example.com --gzip
``` ```
After the download is completed in the current directory will appear a new folder `guides`, which will store all XML files: After the download is completed in the current directory will appear a new folder `guides`, which will store all XML files:
```sh ```sh
guides guides
└── en └── fr
└── example.com.xml └── example.com.xml
└── example.com.xml.gz
```
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: Also you can make these guides available via URL by running your own server:
@ -66,13 +81,13 @@ npm run serve
After that all the downloaded guides will be available at a link like this: After that all the downloaded guides will be available at a link like this:
``` ```
http://localhost:3000/guides/en/example.com.xml http://localhost:3000/guides/fr/example.com.xml
``` ```
In addition, they will be available on your local network at: In addition, they will be available on your local network at:
``` ```
http://<your_local_ip_address>:3000/guides/en/example.com.xml http://<your_local_ip_address>:3000/guides/fr/example.com.xml
``` ```
## Playlists ## Playlists

1480
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,7 @@
"test:commands": "npx jest --runInBand -- commands", "test:commands": "npx jest --runInBand -- commands",
"test:sites": "TZ=Pacific/Nauru npx jest --runInBand -- sites", "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", "check": "npm run api:load && npm run channels:lint sites/**/*.js && npm run channels:validate sites/**/*.xml",
"grab": "cross-var epg-grabber --config=sites/$SITE/$SITE.config.js --channels=sites/$SITE/$SITE.channels.xml --output=guides/{lang}/{site}.xml", "grab": "node scripts/commands/epg/grab.js",
"serve": "npx serve" "serve": "npx serve"
}, },
"private": true, "private": true,
@ -33,10 +33,12 @@
"chalk": "^4.1.2", "chalk": "^4.1.2",
"cheerio": "^1.0.0-rc.10", "cheerio": "^1.0.0-rc.10",
"commander": "^8.2.0", "commander": "^8.2.0",
"consola": "^3.2.3",
"cron": "^2.3.1",
"cross-var": "^1.1.0", "cross-var": "^1.1.0",
"csv-parser": "^3.0.0", "csv-parser": "^3.0.0",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"epg-grabber": "^0.31.0", "epg-grabber": "^0.32.0",
"epg-parser": "^0.2.0", "epg-parser": "^0.2.0",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"fs-extra": "^10.0.1", "fs-extra": "^10.0.1",

View file

@ -57,10 +57,10 @@ async function main() {
} }
if (localErrors.length) { if (localErrors.length) {
logger.info(`\n${chalk.underline(filepath)}`) console.log(`\n${chalk.underline(filepath)}`)
localErrors.forEach(error => { localErrors.forEach(error => {
const position = `${error.line}:${error.column}` const position = `${error.line}:${error.column}`
logger.error(` ${chalk.gray(position.padEnd(4, ' '))} ${error.message.trim()}`) console.log(` ${chalk.gray(position.padEnd(4, ' '))} ${error.message.trim()}`)
}) })
errors = errors.concat(localErrors) errors = errors.concat(localErrors)
@ -68,7 +68,7 @@ async function main() {
} }
if (errors.length) { if (errors.length) {
logger.error(chalk.red(`\n${errors.length} error(s)`)) console.log(chalk.red(`\n${errors.length} error(s)`))
process.exit(1) process.exit(1)
} }
} }

View file

@ -52,7 +52,7 @@ async function main() {
} }
if (errors.length) { if (errors.length) {
logger.info(chalk.underline(filepath)) console.log(chalk.underline(filepath))
console.table(errors, ['type', 'lang', 'xmltv_id', 'site_id', 'name']) console.table(errors, ['type', 'lang', 'xmltv_id', 'site_id', 'name'])
console.log() console.log()
stats.files++ stats.files++
@ -60,7 +60,7 @@ async function main() {
} }
if (stats.errors > 0) { if (stats.errors > 0) {
logger.error(chalk.red(`${stats.errors} error(s) in ${stats.files} file(s)`)) console.log(chalk.red(`${stats.errors} error(s) in ${stats.files} file(s)`))
process.exit(1) process.exit(1)
} }
} }

View file

@ -0,0 +1,218 @@
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('--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)
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: config.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, 'id')
output.channels = _.uniqBy(output.channels, '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

@ -4,6 +4,23 @@ const fs = require('fs-extra')
const file = {} 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) { file.list = function (pattern) {
return new Promise(resolve => { return new Promise(resolve => {
glob(pattern, function (err, files) { glob(pattern, function (err, files) {

View file

@ -1,19 +1,3 @@
const { Signale } = require('signale') const { consola } = require('consola')
const options = {} module.exports = consola
const logger = new Signale(options)
logger.config({
displayLabel: false,
displayScope: false,
displayBadge: false
})
logger.memoryUsage = function () {
const used = process.memoryUsage().heapUsed / 1024 / 1024
logger.info(`memory: ${Math.round(used * 100) / 100} MB`)
}
module.exports = logger

View file

@ -1 +1 @@
{"timeSlices":[{"timeSlice":"0","contents":[{"contentID":"20482220_50001","title":"Le cercle","subtitle":"Emission du 06 janv. 2023","startTime":1673504880000,"onClick":{"displayTemplate":"detailSeason","displayName":"Le cercle","path":"/cinema/le-cercle/h/4501558_50001","URLPage":"https://hodor.canalplus.pro/api/v2/mycanal/detail/da2291af3b10e9900d1c55e1a65d3388/okapi/6564630_50001.json?detailType=detailSeason&objectType=season&broadcastID=PLM_1196447642&episodeId=20482220_50001&brandID=4501558_50001&fromDiff=true","parameters":[{"in":"parameters","id":"featureToggles","enum":["detailV5"]}]}}],"context":{"context_type":"epg","context_page_title":"Guide - Grille TV - CANAL+ CINEMA","context_list_title":"CANAL+ CINEMA - J - Maintenant - Matin","context_list_id":"198","context_list_type":"epgGrid","context_list_position":1}},{"timeSlice":"1","contents":[{"contentID":"17230453_50001","title":"Illusions perdues","subtitle":"Film Drame","startTime":1673525160000,"onClick":{"displayTemplate":"detailPage","displayName":"Illusions perdues","path":"/cinema/illusions-perdues-film-drame/h/17230453_50001","URLPage":"https://hodor.canalplus.pro/api/v2/mycanal/detail/da2291af3b10e9900d1c55e1a65d3388/okapi/17230453_50001.json?detailType=detailPage&objectType=unit&broadcastID=PLM_1196447637&fromDiff=true","parameters":[{"in":"parameters","id":"featureToggles","enum":["detailV5"]}]}}],"context":{"context_type":"epg","context_page_title":"Guide - Grille TV - CANAL+ CINEMA","context_list_title":"CANAL+ CINEMA - J - Maintenant - Apres-midi","context_list_id":"198","context_list_type":"epgGrid","context_list_position":2}},{"timeSlice":"2","contents":[],"context":{"context_type":"epg","context_page_title":"Guide - Grille TV - CANAL+ CINEMA","context_list_title":"CANAL+ CINEMA - J - Maintenant - Debut de soiree","context_list_id":"198","context_list_type":"epgGrid","context_list_position":3}},{"timeSlice":"3","contents":[],"context":{"context_type":"epg","context_page_title":"Guide - Grille TV - CANAL+ CINEMA","context_list_title":"CANAL+ CINEMA - J - Maintenant - Soiree","context_list_id":"198","context_list_type":"epgGrid","context_list_position":4}},{"timeSlice":"4","contents":[],"context":{"context_type":"epg","context_page_title":"Guide - Grille TV - CANAL+ CINEMA","context_list_title":"CANAL+ CINEMA - J - Maintenant - Nuit","context_list_id":"198","context_list_type":"epgGrid","context_list_position":5}}]} {"timeSlices":[{"timeSlice":"0","contents":[{"contentID":"20482220_50001","title":"Le cercle","subtitle":"Emission du 06 janv. 2023","startTime":1673504880000,"onClick":{"displayTemplate":"detailSeason","displayName":"Le cercle","path":"/cinema/le-cercle/h/4501558_50001","URLPage":"https://hodor.canalplus.pro/api/v2/mycanal/detail/f000c6f4ebf44647682b3a0fa66d7d99/okapi/6564630_50001.json?detailType=detailSeason&objectType=season&broadcastID=PLM_1196447642&episodeId=20482220_50001&brandID=4501558_50001&fromDiff=true","parameters":[{"in":"parameters","id":"featureToggles","enum":["detailV5"]}]}}],"context":{"context_type":"epg","context_page_title":"Guide - Grille TV - CANAL+ CINEMA","context_list_title":"CANAL+ CINEMA - J - Maintenant - Matin","context_list_id":"198","context_list_type":"epgGrid","context_list_position":1}},{"timeSlice":"1","contents":[{"contentID":"17230453_50001","title":"Illusions perdues","subtitle":"Film Drame","startTime":1673525160000,"onClick":{"displayTemplate":"detailPage","displayName":"Illusions perdues","path":"/cinema/illusions-perdues-film-drame/h/17230453_50001","URLPage":"https://hodor.canalplus.pro/api/v2/mycanal/detail/f000c6f4ebf44647682b3a0fa66d7d99/okapi/17230453_50001.json?detailType=detailPage&objectType=unit&broadcastID=PLM_1196447637&fromDiff=true","parameters":[{"in":"parameters","id":"featureToggles","enum":["detailV5"]}]}}],"context":{"context_type":"epg","context_page_title":"Guide - Grille TV - CANAL+ CINEMA","context_list_title":"CANAL+ CINEMA - J - Maintenant - Apres-midi","context_list_id":"198","context_list_type":"epgGrid","context_list_position":2}},{"timeSlice":"2","contents":[],"context":{"context_type":"epg","context_page_title":"Guide - Grille TV - CANAL+ CINEMA","context_list_title":"CANAL+ CINEMA - J - Maintenant - Debut de soiree","context_list_id":"198","context_list_type":"epgGrid","context_list_position":3}},{"timeSlice":"3","contents":[],"context":{"context_type":"epg","context_page_title":"Guide - Grille TV - CANAL+ CINEMA","context_list_title":"CANAL+ CINEMA - J - Maintenant - Soiree","context_list_id":"198","context_list_type":"epgGrid","context_list_position":4}},{"timeSlice":"4","contents":[],"context":{"context_type":"epg","context_page_title":"Guide - Grille TV - CANAL+ CINEMA","context_list_title":"CANAL+ CINEMA - J - Maintenant - Nuit","context_list_id":"198","context_list_type":"epgGrid","context_list_position":5}}]}

File diff suppressed because one or more lines are too long

View file

@ -1,231 +1,326 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<site site="canalplus.com"> <site site="canalplus.com">
<channels> <channels>
<channel lang="fr" xmltv_id="6ter.fr" site_id="521">6TER</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#100">TEST03</channel> -->
<channel lang="fr" xmltv_id="AB1.fr" site_id="611">AB1</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#1049">CANAL+SPORT 360 (CH)</channel> -->
<channel lang="fr" xmltv_id="Action.fr" site_id="504">ACTION</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#1051">MULTISPORTS 7</channel> -->
<channel lang="en" xmltv_id="AlJazeeraEnglish.qa" site_id="675">AL JAZEERA ENG</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#1053">CANAL+GRAND ECRAN (CH)</channel> -->
<channel lang="fr" xmltv_id="AlticeStudio.fr" site_id="820">ALTICE STUDIO</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#1067">CHAINE: 1067</channel> -->
<channel lang="fr" xmltv_id="Animaux.fr" site_id="503">ANIMAUX</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#1068">CHAINE: 1068</channel> -->
<channel lang="fr" xmltv_id="ArirangWorld.kr" site_id="252">ARIRANG TV</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#1069">CHAINE: 1069</channel> -->
<channel lang="fr" xmltv_id="ARTEFrance.fr" site_id="154">ARTE</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#1070">CHAINE: 1070</channel> -->
<channel lang="fr" xmltv_id="AstrocenterTV.fr" site_id="632">ASTROCENTER TV</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#1071">CHAINE: 1071</channel> -->
<channel lang="fr" xmltv_id="Automotolachaine.fr" site_id="612">AUTOMOTO LA CHAINE</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#184">DIEGO TV</channel> -->
<channel lang="en" xmltv_id="BBCWorldNewsEurope.uk" site_id="589">BBC WORLD NEWS</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#196">BOB TV</channel> -->
<channel lang="fr" xmltv_id="beINSports1France.fr" site_id="381">BEIN SPORTS 1</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#202">TEST04</channel> -->
<channel lang="fr" xmltv_id="beINSports2France.fr" site_id="384">BEIN SPORTS 2</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#21120">ARTE ALLEMAND</channel> -->
<channel lang="fr" xmltv_id="beINSports3France.fr" site_id="516">BEIN SPORTS 3</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#289">CHAINE EVENEMENT</channel> -->
<channel lang="fr" xmltv_id="beINSportsMax10France.fr" site_id="605">BEIN SPORTS MAX 10</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#477">VOYAGE</channel> -->
<channel lang="fr" xmltv_id="beINSportsMax4France.fr" site_id="596">BEIN SPORTS MAX 4</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#546">EVENEMENT SPORT 4K HDR UHD</channel> -->
<channel lang="fr" xmltv_id="beINSportsMax5France.fr" site_id="597">BEIN SPORTS MAX 5</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#589">BBC WORLD NEWS</channel> -->
<channel lang="fr" xmltv_id="beINSportsMax6France.fr" site_id="598">BEIN SPORTS MAX 6</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#639">LL EVENT UHD</channel> -->
<channel lang="fr" xmltv_id="beINSportsMax7France.fr" site_id="599">BEIN SPORTS MAX 7</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#696">EURONEWS ALL</channel> -->
<channel lang="fr" xmltv_id="beINSportsMax8France.fr" site_id="603">BEIN SPORTS MAX 8</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#700">CANAL+ FINALE UEFA CHAMPIONS LEAGUE</channel> -->
<channel lang="fr" xmltv_id="beINSportsMax9France.fr" site_id="604">BEIN SPORTS MAX 9</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#702">CANAL+ (CH)</channel> -->
<channel lang="fr" xmltv_id="BETFrance.fr" site_id="628">BET</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#726">EVENEMENT SPORT 4K UHD</channel> -->
<channel lang="fr" xmltv_id="BFMBusiness.fr" site_id="645">BFM BUSINESS</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#728">CANAL+ UHD</channel> -->
<channel lang="fr" xmltv_id="BFMTV.fr" site_id="633">BFM TV</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#737">RELIN PIWI</channel> -->
<channel lang="fr" xmltv_id="BloombergTVEurope.uk" site_id="47">BLOOMBERG TV</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#738">RELIN CINE+ CLASSIC</channel> -->
<channel lang="fr" xmltv_id="BlueZoomD.ch" site_id="893">BLUE ZOOM</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#739">TEST CSTAR</channel> -->
<channel lang="fr" xmltv_id="BoingFrance.fr" site_id="529">BOING</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#740">RELIN SEASONS</channel> -->
<channel lang="fr" xmltv_id="BoomerangFrance.fr" site_id="530">BOOMERANG</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#76">TEST05</channel> -->
<channel lang="fr" xmltv_id="BSmartTV.fr" site_id="849">BSMART TV</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#766">RMC SPORT LIVE 3</channel> -->
<channel lang="fr" xmltv_id="C8.fr" site_id="450">C8</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#767">RMC SPORT LIVE 4</channel> -->
<channel lang="fr" xmltv_id="CanalAlphaNE.ch" site_id="772">CANAL ALPHA NE</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#768">CANAL 9</channel> -->
<channel lang="fr" xmltv_id="CanalJ.fr" site_id="525">CANAL J</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#77">TEST06</channel> -->
<channel lang="fr" xmltv_id="CanalPlusCinemaFrance.fr" site_id="198">CANAL+CINEMA</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#770">RTS 1</channel> -->
<channel lang="fr" xmltv_id="CanalPlusDocs.fr" site_id="899">CANAL+DOCS</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#771">RTS 2</channel> -->
<channel lang="fr" xmltv_id="CanalPlusFoot.fr" site_id="19">CANAL+FOOT</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#776">RSI 1</channel> -->
<channel lang="fr" xmltv_id="CanalPlusFormula1.fr" site_id="824">CANAL+FORMULA1</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#777">RSI 2</channel> -->
<channel lang="fr" xmltv_id="CanalPlusFrance.fr" site_id="301">CANAL+</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#779">SRF 2</channel> -->
<channel lang="fr" xmltv_id="CanalPlusGrandEcran.fr" site_id="900">CANAL+GRAND ECRAN</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#78">TEST07</channel> -->
<channel lang="fr" xmltv_id="CanalPlusKids.fr" site_id="259">CANAL+KIDS</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#780">3 SAT</channel> -->
<channel lang="fr" xmltv_id="CanalPlusMotoGP.fr" site_id="823">CANAL+MOTOGP</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#787">OCS MAX (CH)</channel> -->
<channel lang="fr" xmltv_id="CanalPlusPremierLeague.fr" site_id="815">CANAL+PREMIER LEAGUE</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#788">OCS PULP (CH)</channel> -->
<channel lang="fr" xmltv_id="CanalPlusSeriesFrance.fr" site_id="481">CANAL+SERIES</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#789">OCS GEANTS (CH)</channel> -->
<channel lang="fr" xmltv_id="CanalPlusSport2.fr" site_id="861">CANAL+ SPORT 2 (CH)</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#79">TEST08</channel> -->
<channel lang="fr" xmltv_id="CanalPlusSport360.fr" site_id="83">CANAL+SPORT360</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#80">TEST09</channel> -->
<channel lang="fr" xmltv_id="CanalPlusSportFrance.fr" site_id="177">CANAL+SPORT</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#82">TEST10</channel> -->
<channel lang="fr" xmltv_id="CanalPlusTop14.fr" site_id="816">CANAL+TOP14</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#825">CANAL+ (CH)</channel> -->
<channel lang="fr" xmltv_id="CartoonNetworkFrance.fr" site_id="502">CARTOON NETWORK</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#827">CANAL+ CINEMA (CH)</channel> -->
<channel lang="fr" xmltv_id="ChassePeche.fr" site_id="681">CHASSE ET PECHE</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#829">CINE+ PREMIER (CH)</channel> -->
<channel lang="fr" xmltv_id="Cherie25.fr" site_id="440">CHERIE 25</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#830">CINE+ FRISSON (CH)</channel> -->
<channel lang="fr" xmltv_id="CinePlusClassic.fr" site_id="531">CINE+ CLASSIC</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#831">C8 (CH)</channel> -->
<channel lang="fr" xmltv_id="CinePlusClub.fr" site_id="532">CINE+ CLUB</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#832">CSTAR (CH)</channel> -->
<channel lang="fr" xmltv_id="CinePlusEmotion.fr" site_id="396">CINE+ EMOTION</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#85">TEST11</channel> -->
<channel lang="fr" xmltv_id="CinePlusFamiz.fr" site_id="533">CINE+ FAMIZ</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#872">EVENEMENT SPORT 4K HLG UHD</channel> -->
<channel lang="fr" xmltv_id="CinePlusFrisson.fr" site_id="398">CINE+ FRISSON</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#873">CANAL+ SPORT (CH)</channel> -->
<channel lang="fr" xmltv_id="CinePlusPremier.fr" site_id="322">CINE+ PREMIER</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#874">CANAL+ UHD (CH)</channel> -->
<channel lang="fr" xmltv_id="CliqueTV.fr" site_id="665">CLIQUE TV</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#876">TEST01</channel> -->
<channel lang="fr" xmltv_id="CNBCEurope.uk" site_id="64">CNBC</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#877">TEST02</channel> -->
<channel lang="fr" xmltv_id="CNews.fr" site_id="480">CNEWS</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#882">EVENEMENT A LA CARTE</channel> -->
<channel lang="en" xmltv_id="CNNInternationalEurope.us" site_id="30">CNN INT.</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#89">TEST12</channel> -->
<channel lang="fr" xmltv_id="ColmaxTV.fr" site_id="643">COLMAX TV</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#894">CANAL+ LIGUE1</channel> -->
<channel lang="fr" xmltv_id="ComediePlus.fr" site_id="534">COMEDIE+</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#90">TEST13</channel> -->
<channel lang="fr" xmltv_id="ComedyCentralFrance.fr" site_id="806">COMEDY CENTRAL</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#970">CHAINE : 970</channel> -->
<channel lang="fr" xmltv_id="CStar.fr" site_id="513">CSTAR</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#971">CHAINE : 971</channel> -->
<channel lang="fr" xmltv_id="CStarHitsFrance.fr" site_id="723">CSTAR HITS FRANCE</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#972">CHAINE : 972</channel> -->
<channel lang="fr" xmltv_id="DasErste.de" site_id="781">ARD DAS ERSTE</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#973">CHAINE : 973</channel> -->
<channel lang="fr" xmltv_id="DisneyChannelFrance.fr" site_id="282">DISNEY CHANNEL</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#974">CHAINE : 974</channel> -->
<channel lang="fr" xmltv_id="DisneyChannelFrancePlus1.fr" site_id="535">DISNEY CHANNEL+1</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#975">CHAINE : 975</channel> -->
<channel lang="fr" xmltv_id="DisneyJuniorFrance.fr" site_id="274">DISNEY JUNIOR</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#977">BEIN SPORTS 1</channel> -->
<channel lang="fr" xmltv_id="DorcelTV.nl" site_id="536">DORCEL TV</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#978">BEIN SPORTS 2</channel> -->
<channel lang="fr" xmltv_id="DorcelXXX.nl" site_id="537">DORCEL XXX</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#979">BEIN SPORTS 3</channel> -->
<channel lang="fr" xmltv_id="Equidia.fr" site_id="540">EQUIDIA</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#980">BEIN SPORTS MAX 4</channel> -->
<channel lang="fr" xmltv_id="EuronewsFrench.fr" site_id="324">EURONEWS</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#981">BEIN SPORTS MAX 5</channel> -->
<channel lang="fr" xmltv_id="Eurosport1.fr" site_id="101">EUROSPORT 1</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#982">BEIN SPORTS MAX 6</channel> -->
<channel lang="fr" xmltv_id="Eurosport2.fr" site_id="436">EUROSPORT 2</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#983">BEIN SPORTS MAX 7</channel> -->
<channel lang="fr" xmltv_id="Eurosport3.fr" site_id="985">EUROSPORT 3</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#984">CANAL+</channel> -->
<channel lang="fr" xmltv_id="Eurosport360HD1.fr" site_id="635">EUROSPORT 360 1</channel> <!-- <channel lang="fr" xmltv_id="" site_id="#993">CANAL+ UHD</channel> -->
<channel lang="fr" xmltv_id="Eurosport360HD2.fr" site_id="636">EUROSPORT 360 2</channel> <channel lang="en" xmltv_id="AlJazeeraEnglish.qa" site_id="#675">AL JAZEERA ENG</channel>
<channel lang="fr" xmltv_id="Eurosport360HD3.fr" site_id="637">EUROSPORT 360 3</channel> <channel lang="en" xmltv_id="BBCWorldNewsEurope.uk" site_id="#589">BBC WORLD NEWS</channel>
<channel lang="fr" xmltv_id="Eurosport360HD4.fr" site_id="638">EUROSPORT 360 4</channel> <channel lang="en" xmltv_id="CNNInternationalEurope.us" site_id="#30">CNN INT.</channel>
<channel lang="fr" xmltv_id="Eurosport360HD5.fr" site_id="650">EUROSPORT 360 5</channel> <channel lang="en" xmltv_id="France24English.fr" site_id="#311">FRANCE 24 ENG</channel>
<channel lang="fr" xmltv_id="Eurosport360HD6.fr" site_id="651">EUROSPORT 360 6</channel> <channel lang="en" xmltv_id="NHKWorldJapan.jp" site_id="#654">NHK WORLD-JAPAN</channel>
<channel lang="fr" xmltv_id="Eurosport360HD7.fr" site_id="652">EUROSPORT 360 7</channel> <channel lang="fr" xmltv_id="2STV.sn" site_id="sn#180122">2STV</channel>
<channel lang="fr" xmltv_id="Eurosport360HD8.fr" site_id="653">EUROSPORT 360 8</channel> <channel lang="fr" xmltv_id="6ter.fr" site_id="#521">6TER</channel>
<channel lang="fr" xmltv_id="Eurosport4.fr" site_id="986">EUROSPORT 4</channel> <channel lang="fr" xmltv_id="AB1.fr" site_id="#611">AB1</channel>
<channel lang="fr" xmltv_id="Eurosport5.fr" site_id="987">EUROSPORT 5</channel> <channel lang="fr" xmltv_id="Action.fr" site_id="#504">ACTION</channel>
<channel lang="fr" xmltv_id="FootPlus.fr" site_id="542">FOOT+ 24/24</channel> <channel lang="fr" xmltv_id="AlJazeeraEnglish.qa" site_id="#675">AL JAZEERA ENG</channel>
<channel lang="fr" xmltv_id="France2.fr" site_id="26">FRANCE 2</channel> <channel lang="fr" xmltv_id="AlticeStudio.fr" site_id="#820">ALTICE STUDIO</channel>
<channel lang="en" xmltv_id="France24English.fr" site_id="311">FRANCE 24 ENG</channel> <channel lang="fr" xmltv_id="Animaux.fr" site_id="#503">ANIMAUX</channel>
<channel lang="fr" xmltv_id="France24French.fr" site_id="310">FRANCE 24</channel> <channel lang="fr" xmltv_id="ArirangWorld.kr" site_id="#252">ARIRANG TV</channel>
<channel lang="fr" xmltv_id="France3.fr" site_id="543">FRANCE 3</channel> <channel lang="fr" xmltv_id="ARTEFrance.fr" site_id="#154">ARTE</channel>
<channel lang="fr" xmltv_id="France3Alpes.fr" site_id="926">F3 ALPES</channel> <channel lang="fr" xmltv_id="AstrocenterTV.fr" site_id="#632">ASTROCENTER TV</channel>
<channel lang="fr" xmltv_id="France3Alsace.fr" site_id="941">F3 ALSACE</channel> <channel lang="fr" xmltv_id="Automotolachaine.fr" site_id="#612">AUTOMOTO LA CHAINE</channel>
<channel lang="fr" xmltv_id="France3Aquitaine.fr" site_id="922">F3 AQUITAINE</channel> <channel lang="fr" xmltv_id="beINSports1France.fr" site_id="#381">BEIN SPORTS 1</channel>
<channel lang="fr" xmltv_id="France3Auvergne.fr" site_id="924">F3 AUVERGNE</channel> <channel lang="fr" xmltv_id="beINSports2France.fr" site_id="#384">BEIN SPORTS 2</channel>
<channel lang="fr" xmltv_id="France3BasseNormandie.fr" site_id="923">F3 BNORMANDIE</channel> <channel lang="fr" xmltv_id="beINSports3France.fr" site_id="#516">BEIN SPORTS 3</channel>
<channel lang="fr" xmltv_id="France3Bourgogne.fr" site_id="925">F3 BOURGOGNE</channel> <channel lang="fr" xmltv_id="beINSportsMax10France.fr" site_id="#605">BEIN SPORTS MAX 10</channel>
<channel lang="fr" xmltv_id="France3Bretagne.fr" site_id="939">F3 BRETAGNE</channel> <channel lang="fr" xmltv_id="beINSportsMax4France.fr" site_id="#596">BEIN SPORTS MAX 4</channel>
<channel lang="fr" xmltv_id="France3CentreValdeLoire.fr" site_id="935">F3 CENTRE</channel> <channel lang="fr" xmltv_id="beINSportsMax5France.fr" site_id="#597">BEIN SPORTS MAX 5</channel>
<channel lang="fr" xmltv_id="France3ChampagneArdenne.fr" site_id="938">F3 CHAMP ARDENNE</channel> <channel lang="fr" xmltv_id="beINSportsMax6France.fr" site_id="#598">BEIN SPORTS MAX 6</channel>
<channel lang="fr" xmltv_id="France3CorseViaStella.fr" site_id="943">F3 CORSEVIASTELLA</channel> <channel lang="fr" xmltv_id="beINSportsMax7France.fr" site_id="#599">BEIN SPORTS MAX 7</channel>
<channel lang="fr" xmltv_id="France3CotedAzur.fr" site_id="934">F3 COTE D&apos;AZUR</channel> <channel lang="fr" xmltv_id="beINSportsMax8France.fr" site_id="#603">BEIN SPORTS MAX 8</channel>
<channel lang="fr" xmltv_id="France3FrancheComte.fr" site_id="921">F3 FRANCHE COMTE</channel> <channel lang="fr" xmltv_id="beINSportsMax9France.fr" site_id="#604">BEIN SPORTS MAX 9</channel>
<channel lang="fr" xmltv_id="France3HauteNormandie.fr" site_id="940">F3 HNORMANDIE</channel> <channel lang="fr" xmltv_id="BETFrance.fr" site_id="#628">BET</channel>
<channel lang="fr" xmltv_id="France3LanguedocRoussillon.fr" site_id="931">F3 LANGUEDOCROU</channel> <channel lang="fr" xmltv_id="BFMBusiness.fr" site_id="#645">BFM BUSINESS</channel>
<channel lang="fr" xmltv_id="France3Limousin.fr" site_id="928">F3 LIMOUSIN</channel> <channel lang="fr" xmltv_id="BFMTV.fr" site_id="#633">BFM TV</channel>
<channel lang="fr" xmltv_id="France3Lorraine.fr" site_id="932">F3 LORRAINE</channel> <channel lang="fr" xmltv_id="BloombergTVEurope.uk" site_id="#47">BLOOMBERG TV</channel>
<channel lang="fr" xmltv_id="France3MidiPyrenees.fr" site_id="942">F3 MIDI PYRENEES</channel> <channel lang="fr" xmltv_id="BlueZoomD.ch" site_id="#893">BLUE ZOOM</channel>
<channel lang="fr" xmltv_id="France3NordPasdeCalais.fr" site_id="927">F3 NORD PDC</channel> <channel lang="fr" xmltv_id="BoingFrance.fr" site_id="#529">BOING</channel>
<channel lang="fr" xmltv_id="France3NouvelleAquitaine.fr" site_id="998">F3 NOUVELLE AQUITAINE</channel> <channel lang="fr" xmltv_id="BoomerangFrance.fr" site_id="#530">BOOMERANG</channel>
<channel lang="fr" xmltv_id="France3ParisIledeFrance.fr" site_id="936">F3 PARIS IDF</channel> <channel lang="fr" xmltv_id="BSmartTV.fr" site_id="#849">BSMART TV</channel>
<channel lang="fr" xmltv_id="France3PaysdelaLoire.fr" site_id="933">F3 PAYS DE LA LOIRE</channel> <channel lang="fr" xmltv_id="C8.fr" site_id="#450">C8</channel>
<channel lang="fr" xmltv_id="France3Picardie.fr" site_id="920">F3 PICARDIE</channel> <channel lang="fr" xmltv_id="CanalAlphaNE.ch" site_id="#772">CANAL ALPHA NE</channel>
<channel lang="fr" xmltv_id="France3PoitouCharentes.fr" site_id="937">F3 POITOUCHAR</channel> <channel lang="fr" xmltv_id="CanalJ.fr" site_id="#525">CANAL J</channel>
<channel lang="fr" xmltv_id="France3ProvenceAlpes.fr" site_id="930">F3 PROV ALPES</channel> <channel lang="fr" xmltv_id="CanalPlusCinemaFrance.fr" site_id="#198">CANAL+CINEMA</channel>
<channel lang="fr" xmltv_id="France3RhoneAlpes.fr" site_id="929">F3 RHONE ALPES</channel> <channel lang="fr" xmltv_id="CanalPlusDocs.fr" site_id="#899">CANAL+DOCS</channel>
<channel lang="fr" xmltv_id="France4.fr" site_id="544">FRANCE 4</channel> <channel lang="fr" xmltv_id="CanalPlusFoot.fr" site_id="#1045">CANAL+FOOT</channel>
<channel lang="fr" xmltv_id="France5.fr" site_id="545">FRANCE 5</channel> <channel lang="fr" xmltv_id="CanalPlusFoot.fr" site_id="#19">CANAL+FOOT</channel>
<channel lang="fr" xmltv_id="Franceinfo.fr" site_id="670">FRANCEINFO:</channel> <channel lang="fr" xmltv_id="CanalPlusFoot.fr" site_id="#992">CANAL+FOOT</channel>
<channel lang="fr" xmltv_id="GameOne.fr" site_id="592">GAME ONE</channel> <channel lang="fr" xmltv_id="CanalPlusFormula1.fr" site_id="#824">CANAL+FORMULA1</channel>
<channel lang="fr" xmltv_id="GolfPlus.fr" site_id="378">GOLF+</channel> <channel lang="fr" xmltv_id="CanalPlusFrance.fr" site_id="#301">CANAL+</channel>
<channel lang="fr" xmltv_id="Gulli.fr" site_id="549">GULLI</channel> <channel lang="fr" xmltv_id="CanalPlusFrance.fr" site_id="#573">CANAL+</channel>
<channel lang="fr" xmltv_id="HistoireTV.fr" site_id="550">HISTOIRE TV</channel> <channel lang="fr" xmltv_id="CanalPlusFrance.fr" site_id="#601">CANAL+</channel>
<channel lang="fr" xmltv_id="I24NewsFrench.il" site_id="362">I24 NEWS</channel> <channel lang="fr" xmltv_id="CanalPlusGrandEcran.fr" site_id="#900">CANAL+GRAND ECRAN</channel>
<channel lang="fr" xmltv_id="InfosportPlus.fr" site_id="551">INFOSPORT+</channel> <channel lang="fr" xmltv_id="CanalPlusKids.fr" site_id="#259">CANAL+KIDS</channel>
<channel lang="fr" xmltv_id="JacquieMichelTV.fr" site_id="850">JACQUIE &amp; MICHEL TV</channel> <channel lang="fr" xmltv_id="CanalPlusMotoGP.fr" site_id="#823">CANAL+MOTOGP</channel>
<channel lang="fr" xmltv_id="JOne.fr" site_id="484">J-ONE</channel> <channel lang="fr" xmltv_id="CanalPlusPremierLeague.fr" site_id="#815">CANAL+PREMIER LEAGUE</channel>
<channel lang="fr" xmltv_id="LaChaineMeteo.fr" site_id="552">LA CHAINE METEO</channel> <channel lang="fr" xmltv_id="CanalPlusSeriesFrance.fr" site_id="#481">CANAL+SERIES</channel>
<channel lang="fr" xmltv_id="LCI.fr" site_id="553">LCI</channel> <channel lang="fr" xmltv_id="CanalPlusSport2.fr" site_id="#861">CANAL+ SPORT 2 (CH)</channel>
<channel lang="fr" xmltv_id="LCP.fr" site_id="554">LCP</channel> <channel lang="fr" xmltv_id="CanalPlusSport360.fr" site_id="#1048">CANAL+SPORT360</channel>
<channel lang="fr" xmltv_id="LEquipe.fr" site_id="451">L&apos;EQUIPE</channel> <channel lang="fr" xmltv_id="CanalPlusSport360.fr" site_id="#83">CANAL+SPORT360</channel>
<channel lang="fr" xmltv_id="LFMTV.ch" site_id="879">LFM TV</channel> <channel lang="fr" xmltv_id="CanalPlusSportFrance.fr" site_id="#177">CANAL+SPORT</channel>
<channel lang="fr" xmltv_id="M6.fr" site_id="313">M6</channel> <channel lang="fr" xmltv_id="CanalPlusSportFrance.fr" site_id="#574">CANAL+SPORT</channel>
<channel lang="fr" xmltv_id="M6Music.fr" site_id="352">M6 MUSIC</channel> <channel lang="fr" xmltv_id="CanalPlusTop14.fr" site_id="#816">CANAL+TOP14</channel>
<channel lang="fr" xmltv_id="Mangas.fr" site_id="613">MANGAS</channel> <channel lang="fr" xmltv_id="CartoonNetworkFrance.fr" site_id="#502">CARTOON NETWORK</channel>
<channel lang="fr" xmltv_id="ManX.be" site_id="672">MAN X</channel> <channel lang="fr" xmltv_id="ChassePeche.fr" site_id="#681">CHASSE ET PECHE</channel>
<channel lang="fr" xmltv_id="MCM.fr" site_id="556">MCM</channel> <channel lang="fr" xmltv_id="Cherie25.fr" site_id="#440">CHERIE 25</channel>
<channel lang="fr" xmltv_id="Melody.fr" site_id="580">MELODY</channel> <channel lang="fr" xmltv_id="CinePlusClassic.fr" site_id="#531">CINE+ CLASSIC</channel>
<channel lang="fr" xmltv_id="Mezzo.fr" site_id="560">MEZZO</channel> <channel lang="fr" xmltv_id="CinePlusClub.fr" site_id="#532">CINE+ CLUB</channel>
<channel lang="fr" xmltv_id="MezzoLiveHD.fr" site_id="268">MEZZO LIVE</channel> <channel lang="fr" xmltv_id="CinePlusEmotion.fr" site_id="#396">CINE+ EMOTION</channel>
<channel lang="fr" xmltv_id="MGGTV.fr" site_id="819">MGG TV</channel> <channel lang="fr" xmltv_id="CinePlusFamiz.fr" site_id="#533">CINE+ FAMIZ</channel>
<channel lang="fr" xmltv_id="MTVFrance.fr" site_id="470">MTV</channel> <channel lang="fr" xmltv_id="CinePlusFrisson.fr" site_id="#398">CINE+ FRISSON</channel>
<channel lang="fr" xmltv_id="MTVHitsFrance.fr" site_id="629">MTV HITS</channel> <channel lang="fr" xmltv_id="CinePlusPremier.fr" site_id="#322">CINE+ PREMIER</channel>
<channel lang="fr" xmltv_id="MultiSports1.fr" site_id="562">MULTISPORTS 1</channel> <channel lang="fr" xmltv_id="CliqueTV.fr" site_id="#665">CLIQUE TV</channel>
<channel lang="fr" xmltv_id="MultiSports2.fr" site_id="563">MULTISPORTS 2</channel> <channel lang="fr" xmltv_id="CNBCEurope.uk" site_id="#64">CNBC</channel>
<channel lang="fr" xmltv_id="MultiSports3.fr" site_id="564">MULTISPORTS 3</channel> <channel lang="fr" xmltv_id="CNews.fr" site_id="#480">CNEWS</channel>
<channel lang="fr" xmltv_id="MultiSports4.fr" site_id="565">MULTISPORTS 4</channel> <channel lang="fr" xmltv_id="ColmaxTV.fr" site_id="#643">COLMAX TV</channel>
<channel lang="fr" xmltv_id="MultiSports5.fr" site_id="566">MULTISPORTS 5</channel> <channel lang="fr" xmltv_id="ComediePlus.fr" site_id="#534">COMEDIE+</channel>
<channel lang="fr" xmltv_id="MultiSports6.fr" site_id="567">MULTISPORTS 6</channel> <channel lang="fr" xmltv_id="ComedyCentralFrance.fr" site_id="#806">COMEDY CENTRAL</channel>
<channel lang="fr" xmltv_id="MuseumTV.fr" site_id="678">MUSEUM</channel> <channel lang="fr" xmltv_id="CStar.fr" site_id="#513">CSTAR</channel>
<channel lang="fr" xmltv_id="MyZenTV.fr" site_id="656">MY ZEN TV</channel> <channel lang="fr" xmltv_id="CStarHitsFrance.fr" site_id="#723">CSTAR HITS FRANCE</channel>
<channel lang="fr" xmltv_id="NationalGeographicFrance.fr" site_id="302">NATIONAL GEO</channel> <channel lang="fr" xmltv_id="DasErste.de" site_id="#781">ARD DAS ERSTE</channel>
<channel lang="fr" xmltv_id="NationalGeographicWildFrance.fr" site_id="290">NATIONAL GEOGRAPHIC WILD</channel> <channel lang="fr" xmltv_id="DisneyChannelFrance.fr" site_id="#282">DISNEY CHANNEL</channel>
<channel lang="en" xmltv_id="NHKWorldJapan.jp" site_id="654">NHK WORLD-JAPAN</channel> <channel lang="fr" xmltv_id="DisneyChannelFrancePlus1.fr" site_id="#535">DISNEY CHANNEL+1</channel>
<channel lang="fr" xmltv_id="NickelodeonFrance.fr" site_id="591">NICKELODEON</channel> <channel lang="fr" xmltv_id="DisneyJuniorFrance.fr" site_id="#274">DISNEY JUNIOR</channel>
<channel lang="fr" xmltv_id="NickelodeonJunior.fr" site_id="593">NICKELODEON JUNIOR</channel> <channel lang="fr" xmltv_id="DoraTVFrance.fr" site_id="#185">DORA TV</channel>
<channel lang="fr" xmltv_id="NickelodeonTeen.fr" site_id="518">NICKELODEON TEEN</channel> <channel lang="fr" xmltv_id="DorcelTV.nl" site_id="#536">DORCEL TV</channel>
<channel lang="fr" xmltv_id="NollywoodTV.fr" site_id="527">NOLLYWOOD TV</channel> <channel lang="fr" xmltv_id="DorcelXXX.nl" site_id="#537">DORCEL XXX</channel>
<channel lang="fr" xmltv_id="NonStopPeople.fr" site_id="434">NON STOP PEOPLE</channel> <channel lang="fr" xmltv_id="Equidia.fr" site_id="#540">EQUIDIA</channel>
<channel lang="fr" xmltv_id="NovelasTV.fr" site_id="693">NOVELAS TV</channel> <channel lang="fr" xmltv_id="EuronewsFrench.fr" site_id="#324">EURONEWS</channel>
<channel lang="fr" xmltv_id="NRJ12.fr" site_id="568">NRJ 12</channel> <channel lang="fr" xmltv_id="Eurosport1.fr" site_id="#101">EUROSPORT 1</channel>
<channel lang="fr" xmltv_id="NRJHits.fr" site_id="569">NRJ HITS</channel> <channel lang="fr" xmltv_id="Eurosport2.fr" site_id="#436">EUROSPORT 2</channel>
<channel lang="fr" xmltv_id="OCSChoc.fr" site_id="466">OCS CHOC</channel> <channel lang="fr" xmltv_id="Eurosport3.fr" site_id="#985">EUROSPORT 3</channel>
<channel lang="fr" xmltv_id="OCSCity.fr" site_id="467">OCS CITY</channel> <channel lang="fr" xmltv_id="Eurosport360HD1.fr" site_id="#635">EUROSPORT 360 1</channel>
<channel lang="fr" xmltv_id="OCSGeants.fr" site_id="468">OCS GEANTS</channel> <channel lang="fr" xmltv_id="Eurosport360HD2.fr" site_id="#636">EUROSPORT 360 2</channel>
<channel lang="fr" xmltv_id="OCSMax.fr" site_id="372">OCS MAX</channel> <channel lang="fr" xmltv_id="Eurosport360HD3.fr" site_id="#637">EUROSPORT 360 3</channel>
<channel lang="fr" xmltv_id="OlympiaTV.fr" site_id="813">OLYMPIA TV</channel> <channel lang="fr" xmltv_id="Eurosport360HD4.fr" site_id="#638">EUROSPORT 360 4</channel>
<channel lang="fr" xmltv_id="OneTV.ch" site_id="880">ONE TV</channel> <channel lang="fr" xmltv_id="Eurosport360HD5.fr" site_id="#650">EUROSPORT 360 5</channel>
<channel lang="fr" xmltv_id="ORF1.at" site_id="783">ORF 1</channel> <channel lang="fr" xmltv_id="Eurosport360HD6.fr" site_id="#651">EUROSPORT 360 6</channel>
<channel lang="fr" xmltv_id="ParamountChannelFrance.fr" site_id="487">PARAMOUNT CHANNEL</channel> <channel lang="fr" xmltv_id="Eurosport360HD7.fr" site_id="#652">EUROSPORT 360 7</channel>
<channel lang="fr" xmltv_id="ParisPremiere.fr" site_id="294">PARIS PREMIERE</channel> <channel lang="fr" xmltv_id="Eurosport360HD8.fr" site_id="#653">EUROSPORT 360 8</channel>
<channel lang="fr" xmltv_id="PenthouseBlack.us" site_id="686">PENTHOUSE BLACK</channel> <channel lang="fr" xmltv_id="Eurosport4.fr" site_id="#986">EUROSPORT 4</channel>
<channel lang="fr" xmltv_id="PenthouseGold.us" site_id="167">PENTHOUSE</channel> <channel lang="fr" xmltv_id="Eurosport5.fr" site_id="#987">EUROSPORT 5</channel>
<channel lang="fr" xmltv_id="PinkX.fr" site_id="575">PINK X</channel> <channel lang="fr" xmltv_id="Eurosport6.fr" site_id="#988">EUROSPORT 6</channel>
<channel lang="fr" xmltv_id="PiwiPlus.fr" site_id="576">PIWI+</channel> <channel lang="fr" xmltv_id="Eurosport7.fr" site_id="#989">EUROSPORT 7</channel>
<channel lang="fr" xmltv_id="PlanetePlus.fr" site_id="270">PLANETE+</channel> <channel lang="fr" xmltv_id="Eurosport8.fr" site_id="#990">EUROSPORT 8</channel>
<channel lang="fr" xmltv_id="PlanetePlusAE.fr" site_id="588">PLANETE+AVENTURE</channel> <channel lang="fr" xmltv_id="Eurosport9.fr" site_id="#991">EUROSPORT 9</channel>
<channel lang="fr" xmltv_id="PlanetePlusCI.fr" site_id="587">PLANETE+CRIME</channel> <channel lang="fr" xmltv_id="FootPlus.fr" site_id="#542">FOOT+ 24/24</channel>
<channel lang="fr" xmltv_id="PolarPlus.fr" site_id="692">POLAR+</channel> <channel lang="fr" xmltv_id="France2.fr" site_id="#26">FRANCE 2</channel>
<channel lang="fr" xmltv_id="Rai1.it" site_id="782">RAI UNO</channel> <channel lang="fr" xmltv_id="France24English.fr" site_id="#311">FRANCE 24 ENG</channel>
<channel lang="fr" xmltv_id="RFMTV.fr" site_id="557">RFM TV</channel> <channel lang="fr" xmltv_id="France24French.fr" site_id="#310">FRANCE 24</channel>
<channel lang="fr" xmltv_id="RMCDecouverte.fr" site_id="595">RMC DECOUVERTE</channel> <channel lang="fr" xmltv_id="France3.fr" site_id="#543">FRANCE 3</channel>
<channel lang="fr" xmltv_id="RMCSport1.fr" site_id="732">RMC SPORT 1</channel> <channel lang="fr" xmltv_id="France3Alpes.fr" site_id="#926">F3 ALPES</channel>
<channel lang="fr" xmltv_id="RMCSport2.fr" site_id="733">RMC SPORT 2</channel> <channel lang="fr" xmltv_id="France3Alsace.fr" site_id="#941">F3 ALSACE</channel>
<channel lang="fr" xmltv_id="RMCSport3.fr" site_id="734">RMC SPORT 3</channel> <channel lang="fr" xmltv_id="France3Aquitaine.fr" site_id="#922">F3 AQUITAINE</channel>
<channel lang="fr" xmltv_id="RMCSport4.fr" site_id="735">RMC SPORT 4</channel> <channel lang="fr" xmltv_id="France3Auvergne.fr" site_id="#924">F3 AUVERGNE</channel>
<channel lang="fr" xmltv_id="RMCSportLive5.fr" site_id="756">RMC SPORT LIVE 5</channel> <channel lang="fr" xmltv_id="France3BasseNormandie.fr" site_id="#923">F3 BNORMANDIE</channel>
<channel lang="fr" xmltv_id="RMCSportLive6.fr" site_id="757">RMC SPORT LIVE 6</channel> <channel lang="fr" xmltv_id="France3Bourgogne.fr" site_id="#925">F3 BOURGOGNE</channel>
<channel lang="fr" xmltv_id="RMCSportLive7.fr" site_id="758">RMC SPORT LIVE 7</channel> <channel lang="fr" xmltv_id="France3Bretagne.fr" site_id="#939">F3 BRETAGNE</channel>
<channel lang="fr" xmltv_id="RMCSportLive8.fr" site_id="759">RMC SPORT LIVE 8</channel> <channel lang="fr" xmltv_id="France3CentreValdeLoire.fr" site_id="#935">F3 CENTRE</channel>
<channel lang="fr" xmltv_id="RMCSportLive9.fr" site_id="760">RMC SPORT LIVE 9</channel> <channel lang="fr" xmltv_id="France3ChampagneArdenne.fr" site_id="#938">F3 CHAMP ARDENNE</channel>
<channel lang="fr" xmltv_id="RMCSportLive10.fr" site_id="761">RMC SPORT LIVE 10</channel> <channel lang="fr" xmltv_id="France3CorseViaStella.fr" site_id="#943">F3 CORSEVIASTELLA</channel>
<channel lang="fr" xmltv_id="RMCSportLive11.fr" site_id="762">RMC SPORT LIVE 11</channel> <channel lang="fr" xmltv_id="France3CotedAzur.fr" site_id="#934">F3 COTE D&apos;AZUR</channel>
<channel lang="fr" xmltv_id="RMCSportLive12.fr" site_id="763">RMC SPORT LIVE 12</channel> <channel lang="fr" xmltv_id="France3FrancheComte.fr" site_id="#921">F3 FRANCHE COMTE</channel>
<channel lang="fr" xmltv_id="RMCSportLive13.fr" site_id="764">RMC SPORT LIVE 13</channel> <channel lang="fr" xmltv_id="France3HauteNormandie.fr" site_id="#940">F3 HNORMANDIE</channel>
<channel lang="fr" xmltv_id="RMCSportLive14.fr" site_id="765">RMC SPORT LIVE 14</channel> <channel lang="fr" xmltv_id="France3LanguedocRoussillon.fr" site_id="#931">F3 LANGUEDOCROU</channel>
<channel lang="fr" xmltv_id="RMCStory.fr" site_id="571">RMC STORY</channel> <channel lang="fr" xmltv_id="France3Limousin.fr" site_id="#928">F3 LIMOUSIN</channel>
<channel lang="fr" xmltv_id="RougeTV.ch" site_id="878">ROUGE TV</channel> <channel lang="fr" xmltv_id="France3Lorraine.fr" site_id="#932">F3 LORRAINE</channel>
<channel lang="fr" xmltv_id="RTFrance.fr" site_id="479">RT FRANCE</channel> <channel lang="fr" xmltv_id="France3MidiPyrenees.fr" site_id="#942">F3 MIDI PYRENEES</channel>
<channel lang="fr" xmltv_id="RTL9.lu" site_id="505">RTL9</channel> <channel lang="fr" xmltv_id="France3NordPasdeCalais.fr" site_id="#927">F3 NORD PDC</channel>
<channel lang="fr" xmltv_id="ScienceVieTV.fr" site_id="614">SCIENCE ET VIE TV</channel> <channel lang="fr" xmltv_id="France3NouvelleAquitaine.fr" site_id="#998">F3 NOUVELLE AQUITAINE</channel>
<channel lang="fr" xmltv_id="Seasons.fr" site_id="577">SEASONS</channel> <channel lang="fr" xmltv_id="France3ParisIledeFrance.fr" site_id="#936">F3 PARIS IDF</channel>
<channel lang="fr" xmltv_id="SerieClub.fr" site_id="356">SERIE CLUB</channel> <channel lang="fr" xmltv_id="France3PaysdelaLoire.fr" site_id="#933">F3 PAYS DE LA LOIRE</channel>
<channel lang="fr" xmltv_id="SRF1.ch" site_id="778">SRF 1</channel> <channel lang="fr" xmltv_id="France3Picardie.fr" site_id="#920">F3 PICARDIE</channel>
<channel lang="fr" xmltv_id="StingrayClassica.ca" site_id="165">CLASSICA</channel> <channel lang="fr" xmltv_id="France3PoitouCharentes.fr" site_id="#937">F3 POITOUCHAR</channel>
<channel lang="fr" xmltv_id="StingrayDJAZZ.ca" site_id="407">DJAZZ</channel> <channel lang="fr" xmltv_id="France3ProvenceAlpes.fr" site_id="#930">F3 PROV ALPES</channel>
<channel lang="fr" xmltv_id="TCMCinema.fr" site_id="374">TCM CINEMA</channel> <channel lang="fr" xmltv_id="France3RhoneAlpes.fr" site_id="#929">F3 RHONE ALPES</channel>
<channel lang="fr" xmltv_id="TeleBielingue.ch" site_id="775">TELEBIELINGUE</channel> <channel lang="fr" xmltv_id="France4.fr" site_id="#544">FRANCE 4</channel>
<channel lang="fr" xmltv_id="TeletoonPlus.fr" site_id="581">TELETOON+</channel> <channel lang="fr" xmltv_id="France5.fr" site_id="#545">FRANCE 5</channel>
<channel lang="fr" xmltv_id="TeletoonPlus1.fr" site_id="582">TELETOON+1</channel> <channel lang="fr" xmltv_id="Franceinfo.fr" site_id="#670">FRANCEINFO:</channel>
<channel lang="fr" xmltv_id="Teva.fr" site_id="292">TEVA</channel> <channel lang="fr" xmltv_id="GameOne.fr" site_id="#592">GAME ONE</channel>
<channel lang="fr" xmltv_id="TF1.fr" site_id="312">TF1</channel> <channel lang="fr" xmltv_id="GolfPlus.fr" site_id="#378">GOLF+</channel>
<channel lang="fr" xmltv_id="TF1SeriesFilms.fr" site_id="526">TF1 SERIES FILMS</channel> <channel lang="fr" xmltv_id="Gulli.fr" site_id="#549">GULLI</channel>
<channel lang="fr" xmltv_id="TFX.fr" site_id="570">TFX</channel> <channel lang="fr" xmltv_id="HistoireTV.fr" site_id="#550">HISTOIRE TV</channel>
<channel lang="fr" xmltv_id="TiJi.fr" site_id="583">TIJI</channel> <channel lang="fr" xmltv_id="I24NewsFrench.il" site_id="#362">I24 NEWS</channel>
<channel lang="fr" xmltv_id="TMC.fr" site_id="584">TMC</channel> <channel lang="fr" xmltv_id="InfosportPlus.fr" site_id="#551">INFOSPORT+</channel>
<channel lang="fr" xmltv_id="ToonamiFrance.fr" site_id="683">TOONAMI</channel> <channel lang="fr" xmltv_id="JacquieMichelTV.fr" site_id="#850">JACQUIE &amp; MICHEL TV</channel>
<channel lang="fr" xmltv_id="ToutelHistoire.fr" site_id="620">TOUTE L&apos;HISTOIRE</channel> <channel lang="fr" xmltv_id="JOne.fr" site_id="#484">J-ONE</channel>
<channel lang="fr" xmltv_id="TraceUrban.fr" site_id="585">TRACE URBAN</channel> <channel lang="fr" xmltv_id="LaChaineMeteo.fr" site_id="#552">LA CHAINE METEO</channel>
<channel lang="fr" xmltv_id="TV5MondeFranceBelgiumSwitzerland.fr" site_id="520">TV5 MONDE</channel> <channel lang="fr" xmltv_id="LaTele.ch" site_id="#773">LA TELE</channel>
<channel lang="fr" xmltv_id="TVBreizh.fr" site_id="586">TV BREIZH</channel> <channel lang="fr" xmltv_id="LCI.fr" site_id="#553">LCI</channel>
<channel lang="fr" xmltv_id="UshuaiaTV.fr" site_id="142">USHUAIA TV</channel> <channel lang="fr" xmltv_id="LCP.fr" site_id="#554">LCP</channel>
<channel lang="fr" xmltv_id="VICETV.us" site_id="677">VICE TV</channel> <channel lang="fr" xmltv_id="LemanBleu.ch" site_id="#774">LEMAN BLEU</channel>
<channel lang="fr" xmltv_id="VixenTV.ca" site_id="547">VIXEN</channel> <channel lang="fr" xmltv_id="LEquipe.fr" site_id="#451">L&apos;EQUIPE</channel>
<channel lang="fr" xmltv_id="W9.fr" site_id="296">W9</channel> <channel lang="fr" xmltv_id="LFMTV.ch" site_id="#879">LFM TV</channel>
<channel lang="fr" xmltv_id="WarnerTVFrance.fr" site_id="694">WARNER TV</channel> <channel lang="fr" xmltv_id="M6.fr" site_id="#313">M6</channel>
<channel lang="fr" xmltv_id="XXL.fr" site_id="616">XXL</channel> <channel lang="fr" xmltv_id="M6Music.fr" site_id="#352">M6 MUSIC</channel>
<channel lang="fr" xmltv_id="Mangas.fr" site_id="#613">MANGAS</channel>
<channel lang="fr" xmltv_id="ManX.be" site_id="#672">MAN X</channel>
<channel lang="fr" xmltv_id="MCM.fr" site_id="#556">MCM</channel>
<channel lang="fr" xmltv_id="Melody.fr" site_id="#580">MELODY</channel>
<channel lang="fr" xmltv_id="Mezzo.fr" site_id="#560">MEZZO</channel>
<channel lang="fr" xmltv_id="MezzoLiveHD.fr" site_id="#268">MEZZO LIVE</channel>
<channel lang="fr" xmltv_id="MGGTV.fr" site_id="#819">MGG TV</channel>
<channel lang="fr" xmltv_id="MTVFrance.fr" site_id="#470">MTV</channel>
<channel lang="fr" xmltv_id="MTVHitsFrance.fr" site_id="#629">MTV HITS</channel>
<channel lang="fr" xmltv_id="MultiSports1.fr" site_id="#562">MULTISPORTS 1</channel>
<channel lang="fr" xmltv_id="MultiSports2.fr" site_id="#563">MULTISPORTS 2</channel>
<channel lang="fr" xmltv_id="MultiSports3.fr" site_id="#564">MULTISPORTS 3</channel>
<channel lang="fr" xmltv_id="MultiSports4.fr" site_id="#565">MULTISPORTS 4</channel>
<channel lang="fr" xmltv_id="MultiSports5.fr" site_id="#566">MULTISPORTS 5</channel>
<channel lang="fr" xmltv_id="MultiSports6.fr" site_id="#567">MULTISPORTS 6</channel>
<channel lang="fr" xmltv_id="MuseumTV.fr" site_id="#678">MUSEUM</channel>
<channel lang="fr" xmltv_id="MyZenTV.fr" site_id="#656">MY ZEN TV</channel>
<channel lang="fr" xmltv_id="NationalGeographicFrance.fr" site_id="#302">NATIONAL GEO</channel>
<channel lang="fr" xmltv_id="NationalGeographicWildFrance.fr" site_id="#290">NATIONAL GEOGRAPHIC WILD</channel>
<channel lang="fr" xmltv_id="NHKWorldJapan.jp" site_id="#654">NHK WORLD-JAPAN</channel>
<channel lang="fr" xmltv_id="NickelodeonFrance.fr" site_id="#591">NICKELODEON</channel>
<channel lang="fr" xmltv_id="NickelodeonJunior.fr" site_id="#593">NICKELODEON JUNIOR</channel>
<channel lang="fr" xmltv_id="NickelodeonTeen.fr" site_id="#518">NICKELODEON TEEN</channel>
<channel lang="fr" xmltv_id="NollywoodTV.fr" site_id="#527">NOLLYWOOD TV</channel>
<channel lang="fr" xmltv_id="NonStopPeople.fr" site_id="#434">NON STOP PEOPLE</channel>
<channel lang="fr" xmltv_id="NovelasTV.fr" site_id="#693">NOVELAS TV</channel>
<channel lang="fr" xmltv_id="NRJ12.fr" site_id="#568">NRJ 12</channel>
<channel lang="fr" xmltv_id="NRJHits.fr" site_id="#569">NRJ HITS</channel>
<channel lang="fr" xmltv_id="OCSChoc.fr" site_id="#466">OCS CHOC</channel>
<channel lang="fr" xmltv_id="OCSCity.fr" site_id="#467">OCS CITY</channel>
<channel lang="fr" xmltv_id="OCSGeants.fr" site_id="#468">OCS GEANTS</channel>
<channel lang="fr" xmltv_id="OCSMax.fr" site_id="#372">OCS MAX</channel>
<channel lang="fr" xmltv_id="OlympiaTV.fr" site_id="#813">OLYMPIA TV</channel>
<channel lang="fr" xmltv_id="OneTV.ch" site_id="#880">ONE TV</channel>
<channel lang="fr" xmltv_id="ORF1.at" site_id="#783">ORF 1</channel>
<channel lang="fr" xmltv_id="ParamountChannelFrance.fr" site_id="#487">PARAMOUNT CHANNEL</channel>
<channel lang="fr" xmltv_id="ParisPremiere.fr" site_id="#294">PARIS PREMIERE</channel>
<channel lang="fr" xmltv_id="PenthouseBlack.us" site_id="#686">PENTHOUSE BLACK</channel>
<channel lang="fr" xmltv_id="PenthouseGold.us" site_id="#167">PENTHOUSE</channel>
<channel lang="fr" xmltv_id="PinkX.fr" site_id="#575">PINK X</channel>
<channel lang="fr" xmltv_id="PiwiPlus.fr" site_id="#576">PIWI+</channel>
<channel lang="fr" xmltv_id="PlanetePlus.fr" site_id="#270">PLANETE+</channel>
<channel lang="fr" xmltv_id="PlanetePlusAE.fr" site_id="#588">PLANETE+AVENTURE</channel>
<channel lang="fr" xmltv_id="PlanetePlusCI.fr" site_id="#587">PLANETE+CRIME</channel>
<channel lang="fr" xmltv_id="PolarPlus.fr" site_id="#692">POLAR+</channel>
<channel lang="fr" xmltv_id="Rai1.it" site_id="#782">RAI UNO</channel>
<channel lang="fr" xmltv_id="RFMTV.fr" site_id="#557">RFM TV</channel>
<channel lang="fr" xmltv_id="RMCDecouverte.fr" site_id="#595">RMC DECOUVERTE</channel>
<channel lang="fr" xmltv_id="RMCSport1.fr" site_id="#732">RMC SPORT 1</channel>
<channel lang="fr" xmltv_id="RMCSport2.fr" site_id="#733">RMC SPORT 2</channel>
<channel lang="fr" xmltv_id="RMCSport3.fr" site_id="#734">RMC SPORT 3</channel>
<channel lang="fr" xmltv_id="RMCSport4.fr" site_id="#735">RMC SPORT 4</channel>
<channel lang="fr" xmltv_id="RMCSportLive10.fr" site_id="#761">RMC SPORT LIVE 10</channel>
<channel lang="fr" xmltv_id="RMCSportLive11.fr" site_id="#762">RMC SPORT LIVE 11</channel>
<channel lang="fr" xmltv_id="RMCSportLive12.fr" site_id="#763">RMC SPORT LIVE 12</channel>
<channel lang="fr" xmltv_id="RMCSportLive13.fr" site_id="#764">RMC SPORT LIVE 13</channel>
<channel lang="fr" xmltv_id="RMCSportLive14.fr" site_id="#765">RMC SPORT LIVE 14</channel>
<channel lang="fr" xmltv_id="RMCSportLive5.fr" site_id="#756">RMC SPORT LIVE 5</channel>
<channel lang="fr" xmltv_id="RMCSportLive6.fr" site_id="#757">RMC SPORT LIVE 6</channel>
<channel lang="fr" xmltv_id="RMCSportLive7.fr" site_id="#758">RMC SPORT LIVE 7</channel>
<channel lang="fr" xmltv_id="RMCSportLive8.fr" site_id="#759">RMC SPORT LIVE 8</channel>
<channel lang="fr" xmltv_id="RMCSportLive9.fr" site_id="#760">RMC SPORT LIVE 9</channel>
<channel lang="fr" xmltv_id="RMCStory.fr" site_id="#571">RMC STORY</channel>
<channel lang="fr" xmltv_id="RougeTV.ch" site_id="#878">ROUGE TV</channel>
<channel lang="fr" xmltv_id="RTFrance.fr" site_id="#479">RT FRANCE</channel>
<channel lang="fr" xmltv_id="RTL9.lu" site_id="#505">RTL9</channel>
<channel lang="fr" xmltv_id="ScienceVieTV.fr" site_id="#614">SCIENCE ET VIE TV</channel>
<channel lang="fr" xmltv_id="Seasons.fr" site_id="#577">SEASONS</channel>
<channel lang="fr" xmltv_id="SerieClub.fr" site_id="#356">SERIE CLUB</channel>
<channel lang="fr" xmltv_id="SRF1.ch" site_id="#778">SRF 1</channel>
<channel lang="fr" xmltv_id="StingrayClassica.ca" site_id="#165">CLASSICA</channel>
<channel lang="fr" xmltv_id="StingrayDJAZZ.ca" site_id="#407">DJAZZ</channel>
<channel lang="fr" xmltv_id="TCMCinema.fr" site_id="#374">TCM CINEMA</channel>
<channel lang="fr" xmltv_id="TeleBielingue.ch" site_id="#775">TELEBIELINGUE</channel>
<channel lang="fr" xmltv_id="TeletoonPlus.fr" site_id="#581">TELETOON+</channel>
<channel lang="fr" xmltv_id="TeletoonPlus1.fr" site_id="#582">TELETOON+1</channel>
<channel lang="fr" xmltv_id="Teva.fr" site_id="#292">TEVA</channel>
<channel lang="fr" xmltv_id="TF1.fr" site_id="#312">TF1</channel>
<channel lang="fr" xmltv_id="TF1SeriesFilms.fr" site_id="#526">TF1 SERIES FILMS</channel>
<channel lang="fr" xmltv_id="TFX.fr" site_id="#570">TFX</channel>
<channel lang="fr" xmltv_id="TiJi.fr" site_id="#583">TIJI</channel>
<channel lang="fr" xmltv_id="TMC.fr" site_id="#584">TMC</channel>
<channel lang="fr" xmltv_id="ToonamiFrance.fr" site_id="#683">TOONAMI</channel>
<channel lang="fr" xmltv_id="ToutelHistoire.fr" site_id="#620">TOUTE L&apos;HISTOIRE</channel>
<channel lang="fr" xmltv_id="TraceAfrica.fr" site_id="#674">TRACE AFRICA</channel>
<channel lang="fr" xmltv_id="TraceLatina.fr" site_id="#784">TRACE LATINA</channel>
<channel lang="fr" xmltv_id="TraceUrban.fr" site_id="#585">TRACE URBAN</channel>
<channel lang="fr" xmltv_id="TV5MondeFranceBelgiumSwitzerland.fr" site_id="#520">TV5 MONDE</channel>
<channel lang="fr" xmltv_id="TVBreizh.fr" site_id="#586">TV BREIZH</channel>
<channel lang="fr" xmltv_id="UshuaiaTV.fr" site_id="#142">USHUAIA TV</channel>
<channel lang="fr" xmltv_id="VICETV.us" site_id="#677">VICE TV</channel>
<channel lang="fr" xmltv_id="VixenTV.ca" site_id="#547">VIXEN</channel>
<channel lang="fr" xmltv_id="W9.fr" site_id="#296">W9</channel>
<channel lang="fr" xmltv_id="WarnerTVFrance.fr" site_id="#694">WARNER TV</channel>
<channel lang="fr" xmltv_id="XXL.fr" site_id="#616">XXL</channel>
</channels> </channels>
</site> </site>

View file

@ -1,19 +1,20 @@
const dayjs = require('dayjs') const dayjs = require('dayjs')
const axios = require('axios') const axios = require('axios')
// TODO: calculate API_KEY based on the current date
//
// const API_KEY = 'f55e5c7ddf0afba59d1c64581358910d' // 03.2022
//const API_KEY = 'c71b6b8eb30125dab9d10a3850131ac6' // 05.2022
const API_KEY = 'da2291af3b10e9900d1c55e1a65d3388' // 10.2022
module.exports = { module.exports = {
site: 'canalplus.com', site: 'canalplus.com',
days: 2, days: 2,
url: function ({ channel, date }) { url: async function ({ channel, date }) {
const [region, site_id] = channel.site_id.split('#')
const data = await axios
.get(`https://www.canalplus.com/${region}/programme-tv/`)
.then(r => r.data.toString())
.catch(err => console.log(err))
const token = parseToken(data)
const diff = date.diff(dayjs.utc().startOf('d'), 'd') const diff = date.diff(dayjs.utc().startOf('d'), 'd')
return `https://hodor.canalplus.pro/api/v2/mycanal/channels/${API_KEY}/${channel.site_id}/broadcasts/day/${diff}` return `https://hodor.canalplus.pro/api/v2/mycanal/channels/${token}/${site_id}/broadcasts/day/${diff}`
}, },
async parser({ content }) { async parser({ content }) {
let programs = [] let programs = []
@ -29,11 +30,11 @@ module.exports = {
title: item.title, title: item.title,
description: parseDescription(info), description: parseDescription(info),
icon: parseIcon(info), icon: parseIcon(info),
actors: parseCast(info,"Avec :"), actors: parseCast(info, 'Avec :'),
director: parseCast(info,"De :"), director: parseCast(info, 'De :'),
writer: parseCast(info,"Scénario :"), writer: parseCast(info, 'Scénario :'),
composer: parseCast(info,"Musique :"), composer: parseCast(info, 'Musique :'),
presenter: parseCast(info,"Présenté par :"), presenter: parseCast(info, 'Présenté par :'),
date: parseDate(info), date: parseDate(info),
rating: parseRating(info), rating: parseRating(info),
start, start,
@ -44,19 +45,76 @@ module.exports = {
return programs return programs
}, },
async channels() { async channels() {
const endpoints = {
ad: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/ad/all/v2.2/globalchannels.json',
bf: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/bf/all/v2.2/globalchannels.json',
bi: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/bi/all/v2.2/globalchannels.json',
bj: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/bj/all/v2.2/globalchannels.json',
bl: 'https://secure-webtv-static.canal-plus.com/metadata/cpant/bl/all/v2.2/globalchannels.json',
cd: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/cd/all/v2.2/globalchannels.json',
cf: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/cf/all/v2.2/globalchannels.json',
cg: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/cg/all/v2.2/globalchannels.json',
ch: 'https://secure-webtv-static.canal-plus.com/metadata/cpche/all/v2.2/globalchannels.json',
ci: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/ci/all/v2.2/globalchannels.json',
cm: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/cm/all/v2.2/globalchannels.json',
cv: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/cv/all/v2.2/globalchannels.json',
dj: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/dj/all/v2.2/globalchannels.json',
fr: 'https://secure-webtv-static.canal-plus.com/metadata/cpfra/all/v2.2/globalchannels.json',
ga: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/ga/all/v2.2/globalchannels.json',
gf: 'https://secure-webtv-static.canal-plus.com/metadata/cpant/gf/all/v2.2/globalchannels.json',
gh: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/gh/all/v2.2/globalchannels.json',
gm: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/gm/all/v2.2/globalchannels.json',
gn: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/gn/all/v2.2/globalchannels.json',
gp: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/gp/all/v2.2/globalchannels.json',
gp: 'https://secure-webtv-static.canal-plus.com/metadata/cpant/gp/all/v2.2/globalchannels.json',
gw: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/gw/all/v2.2/globalchannels.json',
mf: 'https://secure-webtv-static.canal-plus.com/metadata/cpant/mf/all/v2.2/globalchannels.json',
mg: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/mg/all/v2.2/globalchannels.json',
ml: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/ml/all/v2.2/globalchannels.json',
mq: 'https://secure-webtv-static.canal-plus.com/metadata/cpant/mq/all/v2.2/globalchannels.json',
mr: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/mr/all/v2.2/globalchannels.json',
mu: 'https://secure-webtv-static.canal-plus.com/metadata/cpmus/mu/all/v2.2/globalchannels.json',
nc: 'https://secure-webtv-static.canal-plus.com/metadata/cpncl/nc/all/v2.2/globalchannels.json',
ne: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/ne/all/v2.2/globalchannels.json',
pl: 'https://secure-webtv-static.canal-plus.com/metadata/cppol/all/v2.2/globalchannels.json',
re: 'https://secure-webtv-static.canal-plus.com/metadata/cpreu/re/all/v2.2/globalchannels.json',
rw: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/rw/all/v2.2/globalchannels.json',
sl: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/sl/all/v2.2/globalchannels.json',
sn: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/sn/all/v2.2/globalchannels.json',
td: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/td/all/v2.2/globalchannels.json',
tg: 'https://secure-webtv-static.canal-plus.com/metadata/cpafr/tg/all/v2.2/globalchannels.json',
wf: 'https://secure-webtv-static.canal-plus.com/metadata/cpncl/wf/all/v2.2/globalchannels.json',
yt: 'https://secure-webtv-static.canal-plus.com/metadata/cpreu/yt/all/v2.2/globalchannels.json'
}
let channels = []
for (let [region, url] of Object.entries(endpoints)) {
const data = await axios const data = await axios
.get(`https://secure-webtv-static.canal-plus.com/metadata/cpfra/all/v2.2/globalchannels.json`) .get(url)
.then(r => r.data) .then(r => r.data)
.catch(console.log) .catch(console.log)
return data.channels.map(item => { data.channels.forEach(channel => {
return { const site_id = region === 'fr' ? `#${channel.id}` : `${region}#${channel.id}`
if (channel.name === '.') return
channels.push({
lang: 'fr', lang: 'fr',
site_id: item.id, site_id,
name: item.name name: channel.name
} })
}) })
} }
return channels
}
}
function parseToken(data) {
const [, token] = data.match(/"token":"([^"]+)/) || [null, null]
return token
} }
function parseStart(item) { function parseStart(item) {
@ -101,7 +159,7 @@ function parseCast(info, type) {
if (info && info.personnalities) { if (info && info.personnalities) {
const personnalities = info.personnalities.find(i => i.prefix == type) const personnalities = info.personnalities.find(i => i.prefix == type)
if (!personnalities) return people if (!personnalities) return people
for(let person of personnalities.personnalitiesList) { for (let person of personnalities.personnalitiesList) {
people.push(person.title) people.push(person.title)
} }
} }
@ -109,7 +167,7 @@ function parseCast(info, type) {
} }
function parseDate(info) { function parseDate(info) {
return (info && info.productionYear) ? info.productionYear : null return info && info.productionYear ? info.productionYear : null
} }
function parseRating(info) { function parseRating(info) {

View file

@ -1,5 +1,5 @@
// npm run channels:parse -- --config=./sites/canalplus.com/canalplus.com.config.js --output=./sites/canalplus.com/canalplus.com.channels.xml // npm run channels:parse -- --config=./sites/canalplus.com/canalplus.com.config.js --output=./sites/canalplus.com/canalplus.com.channels.xml
// npx epg-grabber --config=sites/canalplus.com/canalplus.com.config.js --channels=sites/canalplus.com/canalplus.com.channels.xml --output=guide.xml --days=2 // npx epg-grabber --config=sites/canalplus.com/canalplus.com.config.js --channels=sites/canalplus.com/canalplus.com.channels.xml --output=guide.xml
const { parser, url } = require('./canalplus.com.config.js') const { parser, url } = require('./canalplus.com.config.js')
const fs = require('fs') const fs = require('fs')
@ -13,23 +13,52 @@ dayjs.extend(utc)
jest.mock('axios') jest.mock('axios')
const channel = { const channel = {
site_id: '198', site_id: 'bi#198',
xmltv_id: 'CanalPlusCinemaFrance.fr' xmltv_id: 'CanalPlusCinemaFrance.fr'
} }
it('can generate valid url for today', done => {
axios.get.mockImplementation(url => {
if (url === 'https://www.canalplus.com/bi/programme-tv/') {
return Promise.resolve({
data: fs.readFileSync(path.resolve(__dirname, '__data__/programme-tv.html'))
})
} else {
return Promise.resolve({ data: '' })
}
})
it('can generate valid url for today', () => {
const today = dayjs.utc().startOf('d') const today = dayjs.utc().startOf('d')
expect(url({ channel, date: today })).toBe( url({ channel, date: today })
'https://hodor.canalplus.pro/api/v2/mycanal/channels/da2291af3b10e9900d1c55e1a65d3388/198/broadcasts/day/0' .then(result => {
expect(result).toBe(
'https://hodor.canalplus.pro/api/v2/mycanal/channels/f000c6f4ebf44647682b3a0fa66d7d99/198/broadcasts/day/0'
) )
done()
})
.catch(done)
}) })
it('can generate valid url for tomorrow', () => { it('can generate valid url for tomorrow', done => {
axios.get.mockImplementation(url => {
if (url === 'https://www.canalplus.com/bi/programme-tv/') {
return Promise.resolve({
data: fs.readFileSync(path.resolve(__dirname, '__data__/programme-tv.html'))
})
} else {
return Promise.resolve({ data: '' })
}
})
const tomorrow = dayjs.utc().startOf('d').add(1, 'd') const tomorrow = dayjs.utc().startOf('d').add(1, 'd')
expect(url({ channel, date: tomorrow })).toBe( url({ channel, date: tomorrow })
'https://hodor.canalplus.pro/api/v2/mycanal/channels/da2291af3b10e9900d1c55e1a65d3388/198/broadcasts/day/1' .then(result => {
expect(result).toBe(
'https://hodor.canalplus.pro/api/v2/mycanal/channels/f000c6f4ebf44647682b3a0fa66d7d99/198/broadcasts/day/1'
) )
done()
})
.catch(done)
}) })
it('can parse response', done => { it('can parse response', done => {
@ -38,14 +67,14 @@ it('can parse response', done => {
axios.get.mockImplementation(url => { axios.get.mockImplementation(url => {
if ( if (
url === url ===
'https://hodor.canalplus.pro/api/v2/mycanal/detail/da2291af3b10e9900d1c55e1a65d3388/okapi/6564630_50001.json?detailType=detailSeason&objectType=season&broadcastID=PLM_1196447642&episodeId=20482220_50001&brandID=4501558_50001&fromDiff=true' 'https://hodor.canalplus.pro/api/v2/mycanal/detail/f000c6f4ebf44647682b3a0fa66d7d99/okapi/6564630_50001.json?detailType=detailSeason&objectType=season&broadcastID=PLM_1196447642&episodeId=20482220_50001&brandID=4501558_50001&fromDiff=true'
) { ) {
return Promise.resolve({ return Promise.resolve({
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program1.json'))) data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program1.json')))
}) })
} else if ( } else if (
url === url ===
'https://hodor.canalplus.pro/api/v2/mycanal/detail/da2291af3b10e9900d1c55e1a65d3388/okapi/17230453_50001.json?detailType=detailPage&objectType=unit&broadcastID=PLM_1196447637&fromDiff=true' 'https://hodor.canalplus.pro/api/v2/mycanal/detail/f000c6f4ebf44647682b3a0fa66d7d99/okapi/17230453_50001.json?detailType=detailPage&objectType=unit&broadcastID=PLM_1196447637&fromDiff=true'
) { ) {
return Promise.resolve({ return Promise.resolve({
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program2.json'))) data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program2.json')))

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?><tv date="20221020">
<channel id="6eren.dk"><display-name>6&apos;eren</display-name><icon src="https://upload.wikimedia.org/wikipedia/commons/6/64/6%27eren_2015.png"/><url>https://allente.se</url></channel>
<programme start="20221024040000 +0000" stop="20221024042500 +0000" channel="6eren.dk"><title lang="da">Diners, Drive-Ins and Dives</title><desc lang="da">Underholdning</desc><category lang="da">series</category><icon src="https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/487/2022-10-24/se.cs.6eren.event.B_0254194276971024040000.jpg?size=2560x1440"/><episode-num system="xmltv_ns">23.5.0/1</episode-num><episode-num system="onscreen">S24E06</episode-num></programme>
</tv>

View file

@ -1,3 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?><tv date="20221020">
<channel id="BravoEast.us"><display-name>Bravo East</display-name><icon src="https://www.directv.com/images/logos/channels/dark/large/579.png"/><url>https://directv.com</url></channel>
</tv>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" ?><tv date="20221020">
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example.com</url></channel>
<programme start="20220306043000 +0000" stop="20220306071000 +0000" channel="Channel1.us"><title lang="en">Program1</title></programme>
</tv>

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?><tv date="20221020">
<channel id="BBCNews.uk"><display-name>BBC News</display-name><icon src="https://i.imgur.com/rPzH88J.png"/><url>https://sky.com</url></channel>
<programme start="20221027120000 +0000" stop="20221027123000 +0000" channel="BBCNews.uk"><title lang="en">BBC News at One</title><desc lang="en">The latest national and international news from the BBC. [S,SL]</desc><icon src="http://epgstatic.sky.com/epgdata/1.0/paimage/46/1/lisa/5.2.2/linear/channel/ca247bc8-6be0-48f9-88d1-865f87f7680e/2011"/></programme>
</tv>

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?><tv date="20221020">
<channel id="BBCNews.uk"><display-name>BBC News</display-name><icon src="https://i.imgur.com/rPzH88J.png"/><url>https://virginmedia.com</url></channel>
<programme start="20221027120000 +0000" stop="20221027123000 +0000" channel="BBCNews.uk"><title lang="en">BBC News at One</title><desc lang="en">The latest national and international news, followed by weather.</desc><category lang="en">News</category><episode-num system="xmltv_ns">96839999.145799123.0/1</episode-num><episode-num system="onscreen">S96840000E145799124</episode-num></programme>
</tv>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" ?><tv date="20221020">
<channel id="Channel1.us"><display-name>Channel 1</display-name><url>https://example.com</url></channel>
<programme start="20220306043000 +0000" stop="20220306071000 +0000" channel="Channel1.us"><title lang="fr">Program1</title></programme>
</tv>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?><tv date="20221020">
<channel id="BBCNews.uk"><display-name>BBC News</display-name><icon src="https://i.imgur.com/rPzH88J.png"/><url>https://sky.com</url></channel>
<channel id="CNN.us"><display-name>CNN</display-name><icon src="https://www.directv.com/images/logos/channels/dark/large/579.png"/><url>https://sky.com</url></channel>
<programme start="20221027120000 +0000" stop="20221027123000 +0000" channel="BBCNews.uk"><title lang="fr">BBC News at One</title><desc lang="fr">Les dernières nouvelles nationales et internationales de la BBC. [S,SL]</desc><icon src="http://epgstatic.sky.com/epgdata/1.0/paimage/46/1/lisa/5.2.2/linear/channel/ca247bc8-6be0-48f9-88d1-865f87f7680e/2011"/></programme>
<programme start="20221027120000 +0000" stop="20221027123000 +0000" channel="CNN.us"><title lang="fr">French title</title></programme>
</tv>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<site site="example.com">
<channels>
<channel lang="en" xmltv_id="Channel1.us" site_id="140">Channel 1</channel>
<channel lang="fr" xmltv_id="Channel1.us" site_id="140">Channel 1</channel>
</channels>
</site>

View file

@ -0,0 +1,16 @@
module.exports = {
site: 'example.com',
days: 2,
url() {
return `https://example.com`
},
parser() {
return [
{
title: 'Program1',
start: '2022-03-06T04:30:00.000Z',
stop: '2022-03-06T07:10:00.000Z'
}
]
}
}

View file

@ -0,0 +1,39 @@
const { execSync } = require('child_process')
const fs = require('fs-extra')
const path = require('path')
beforeEach(() => {
fs.emptyDirSync('tests/__data__/output')
})
it('can grab epg', () => {
const stdout = execSync(
'BASE_DIR=tests/__data__/input CURR_DATE=2022-10-20 DATA_DIR=tests/__data__/input/tmp/data npm run grab -- --site=epg-grab --output=tests/__data__/output/{lang}/{site}.xml',
{ encoding: 'utf8' }
)
expect(content('tests/__data__/output/en/example.com.xml')).toEqual(
content('tests/__data__/expected/guides/en/example.com.xml')
)
expect(content('tests/__data__/output/fr/example.com.xml')).toEqual(
content('tests/__data__/expected/guides/fr/example.com.xml')
)
})
it('can grab epg with language filter enabled', () => {
const stdout = execSync(
'BASE_DIR=tests/__data__/input CURR_DATE=2022-10-20 DATA_DIR=tests/__data__/input/tmp/data npm run grab -- --site=epg-grab --lang=fr --output=tests/__data__/output/fr/guide.xml',
{ encoding: 'utf8' }
)
expect(content('tests/__data__/output/fr/guide.xml')).toEqual(
content('tests/__data__/expected/guides/fr/example.com.xml')
)
})
function content(filepath) {
return fs.readFileSync(path.resolve(filepath), {
encoding: 'utf8'
})
}

808
yarn.lock

File diff suppressed because it is too large Load diff