Replace LF endings with CRLF

This commit is contained in:
freearhey
2025-07-31 22:29:01 +03:00
parent 17e3b4ddda
commit 29aa427923
379 changed files with 29332 additions and 29332 deletions

View File

@@ -1,197 +1,197 @@
const dayjs = require('dayjs')
const axios = require('axios')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
module.exports = {
site: 'canalplus.com',
days: 2,
url: async function ({ channel, date }) {
const [region, site_id] = channel.site_id.split('#')
const baseUrl =
region === 'pl'
? 'https://www.canalplus.com/pl/program-tv/'
: `https://www.canalplus.com/${region}/programme-tv/`
const data = await axios
.get(baseUrl)
.then(r => r.data.toString())
.catch(err => console.log(err))
const token = parseToken(data)
const path = region === 'pl' ? 'mycanalint' : 'mycanal'
const diff = date.diff(dayjs.utc().startOf('d'), 'd')
return `https://hodor.canalplus.pro/api/v2/${path}/channels/${token}/${site_id}/broadcasts/day/${diff}`
},
async parser({ content }) {
let programs = []
const items = parseItems(content)
for (let item of items) {
const prev = programs[programs.length - 1]
const details = await loadProgramDetails(item)
const info = parseInfo(details)
const start = parseStart(item)
if (prev) prev.stop = start
const stop = start.add(1, 'h')
programs.push({
title: item.title,
description: parseDescription(info),
image: parseImage(info),
actors: parseCast(info, 'Avec :'),
director: parseCast(info, 'De :'),
writer: parseCast(info, 'Scénario :'),
composer: parseCast(info, 'Musique :'),
presenter: parseCast(info, 'Présenté par :'),
date: parseDate(info),
rating: parseRating(info),
start,
stop
})
}
return programs
},
async channels({ country }) {
const paths = {
ad: 'cpafr/ad',
bf: 'cpafr/bf',
bi: 'cpafr/bi',
bj: 'cpafr/bj',
bl: 'cpant/bl',
cd: 'cpafr/cd',
cf: 'cpafr/cf',
cg: 'cpafr/cg',
ch: 'cpche',
ci: 'cpafr/ci',
cm: 'cpafr/cm',
cv: 'cpafr/cv',
dj: 'cpafr/dj',
fr: 'cpfra',
ga: 'cpafr/ga',
gf: 'cpant/gf',
gh: 'cpafr/gh',
gm: 'cpafr/gm',
gn: 'cpafr/gn',
gp: 'cpafr/gp',
gw: 'cpafr/gw',
ht: 'cpant/ht',
mf: 'cpant/mf',
mg: 'cpafr/mg',
ml: 'cpafr/ml',
mq: 'cpant/mq',
mr: 'cpafr/mr',
mu: 'cpmus/mu',
nc: 'cpncl/nc',
ne: 'cpafr/ne',
pf: 'cppyf/pf',
pl: 'cppol',
re: 'cpreu/re',
rw: 'cpafr/rw',
sl: 'cpafr/sl',
sn: 'cpafr/sn',
td: 'cpafr/td',
tg: 'cpafr/tg',
wf: 'cpncl/wf',
yt: 'cpreu/yt'
}
let channels = []
const path = paths[country]
const url = `https://secure-webtv-static.canal-plus.com/metadata/${path}/all/v2.2/globalchannels.json`
const data = await axios
.get(url)
.then(r => r.data)
.catch(console.log)
data.channels.forEach(channel => {
const site_id = country === 'fr' ? `#${channel.id}` : `${country}#${channel.id}`
if (channel.name === '.') return
channels.push({
lang: 'fr',
site_id,
name: channel.name
})
})
return channels
}
}
function parseToken(data) {
const [, token] = data.match(/"token":"([^"]+)/) || [null, null]
return token
}
function parseStart(item) {
return item && item.startTime ? dayjs(item.startTime) : null
}
function parseImage(info) {
return info ? info.URLImage : null
}
function parseDescription(info) {
return info ? info.summary : null
}
function parseInfo(data) {
if (!data || !data.detail || !data.detail.informations) return null
return data.detail.informations
}
async function loadProgramDetails(item) {
if (!item.onClick || !item.onClick.URLPage) return {}
return await axios
.get(item.onClick.URLPage)
.then(r => r.data)
.catch(console.error)
}
function parseItems(content) {
const data = JSON.parse(content)
if (!data || !Array.isArray(data.timeSlices)) return []
return data.timeSlices.reduce((acc, curr) => {
acc = acc.concat(curr.contents)
return acc
}, [])
}
function parseCast(info, type) {
let people = []
if (info && info.personnalities) {
const personnalities = info.personnalities.find(i => i.prefix == type)
if (!personnalities) return people
for (let person of personnalities.personnalitiesList) {
people.push(person.title)
}
}
return people
}
function parseDate(info) {
return info && info.productionYear ? info.productionYear : null
}
function parseRating(info) {
if (!info || !info.parentalRatings) return null
let rating = info.parentalRatings.find(i => i.authority === 'CSA')
if (!rating || Array.isArray(rating)) return null
if (rating.value === '1') return null
if (rating.value === '2') rating.value = '-10'
if (rating.value === '3') rating.value = '-12'
if (rating.value === '4') rating.value = '-16'
if (rating.value === '5') rating.value = '-18'
return {
system: rating.authority,
value: rating.value
}
}
const dayjs = require('dayjs')
const axios = require('axios')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
module.exports = {
site: 'canalplus.com',
days: 2,
url: async function ({ channel, date }) {
const [region, site_id] = channel.site_id.split('#')
const baseUrl =
region === 'pl'
? 'https://www.canalplus.com/pl/program-tv/'
: `https://www.canalplus.com/${region}/programme-tv/`
const data = await axios
.get(baseUrl)
.then(r => r.data.toString())
.catch(err => console.log(err))
const token = parseToken(data)
const path = region === 'pl' ? 'mycanalint' : 'mycanal'
const diff = date.diff(dayjs.utc().startOf('d'), 'd')
return `https://hodor.canalplus.pro/api/v2/${path}/channels/${token}/${site_id}/broadcasts/day/${diff}`
},
async parser({ content }) {
let programs = []
const items = parseItems(content)
for (let item of items) {
const prev = programs[programs.length - 1]
const details = await loadProgramDetails(item)
const info = parseInfo(details)
const start = parseStart(item)
if (prev) prev.stop = start
const stop = start.add(1, 'h')
programs.push({
title: item.title,
description: parseDescription(info),
image: parseImage(info),
actors: parseCast(info, 'Avec :'),
director: parseCast(info, 'De :'),
writer: parseCast(info, 'Scénario :'),
composer: parseCast(info, 'Musique :'),
presenter: parseCast(info, 'Présenté par :'),
date: parseDate(info),
rating: parseRating(info),
start,
stop
})
}
return programs
},
async channels({ country }) {
const paths = {
ad: 'cpafr/ad',
bf: 'cpafr/bf',
bi: 'cpafr/bi',
bj: 'cpafr/bj',
bl: 'cpant/bl',
cd: 'cpafr/cd',
cf: 'cpafr/cf',
cg: 'cpafr/cg',
ch: 'cpche',
ci: 'cpafr/ci',
cm: 'cpafr/cm',
cv: 'cpafr/cv',
dj: 'cpafr/dj',
fr: 'cpfra',
ga: 'cpafr/ga',
gf: 'cpant/gf',
gh: 'cpafr/gh',
gm: 'cpafr/gm',
gn: 'cpafr/gn',
gp: 'cpafr/gp',
gw: 'cpafr/gw',
ht: 'cpant/ht',
mf: 'cpant/mf',
mg: 'cpafr/mg',
ml: 'cpafr/ml',
mq: 'cpant/mq',
mr: 'cpafr/mr',
mu: 'cpmus/mu',
nc: 'cpncl/nc',
ne: 'cpafr/ne',
pf: 'cppyf/pf',
pl: 'cppol',
re: 'cpreu/re',
rw: 'cpafr/rw',
sl: 'cpafr/sl',
sn: 'cpafr/sn',
td: 'cpafr/td',
tg: 'cpafr/tg',
wf: 'cpncl/wf',
yt: 'cpreu/yt'
}
let channels = []
const path = paths[country]
const url = `https://secure-webtv-static.canal-plus.com/metadata/${path}/all/v2.2/globalchannels.json`
const data = await axios
.get(url)
.then(r => r.data)
.catch(console.log)
data.channels.forEach(channel => {
const site_id = country === 'fr' ? `#${channel.id}` : `${country}#${channel.id}`
if (channel.name === '.') return
channels.push({
lang: 'fr',
site_id,
name: channel.name
})
})
return channels
}
}
function parseToken(data) {
const [, token] = data.match(/"token":"([^"]+)/) || [null, null]
return token
}
function parseStart(item) {
return item && item.startTime ? dayjs(item.startTime) : null
}
function parseImage(info) {
return info ? info.URLImage : null
}
function parseDescription(info) {
return info ? info.summary : null
}
function parseInfo(data) {
if (!data || !data.detail || !data.detail.informations) return null
return data.detail.informations
}
async function loadProgramDetails(item) {
if (!item.onClick || !item.onClick.URLPage) return {}
return await axios
.get(item.onClick.URLPage)
.then(r => r.data)
.catch(console.error)
}
function parseItems(content) {
const data = JSON.parse(content)
if (!data || !Array.isArray(data.timeSlices)) return []
return data.timeSlices.reduce((acc, curr) => {
acc = acc.concat(curr.contents)
return acc
}, [])
}
function parseCast(info, type) {
let people = []
if (info && info.personnalities) {
const personnalities = info.personnalities.find(i => i.prefix == type)
if (!personnalities) return people
for (let person of personnalities.personnalitiesList) {
people.push(person.title)
}
}
return people
}
function parseDate(info) {
return info && info.productionYear ? info.productionYear : null
}
function parseRating(info) {
if (!info || !info.parentalRatings) return null
let rating = info.parentalRatings.find(i => i.authority === 'CSA')
if (!rating || Array.isArray(rating)) return null
if (rating.value === '1') return null
if (rating.value === '2') rating.value = '-10'
if (rating.value === '3') rating.value = '-12'
if (rating.value === '4') rating.value = '-16'
if (rating.value === '5') rating.value = '-18'
return {
system: rating.authority,
value: rating.value
}
}

View File

@@ -1,146 +1,146 @@
const { parser, url } = require('./canalplus.com.config.js')
const fs = require('fs')
const path = require('path')
const axios = require('axios')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat')
dayjs.extend(customParseFormat)
dayjs.extend(utc)
jest.mock('axios')
const channel = {
site_id: 'bi#198',
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: '' })
}
})
const today = dayjs.utc().startOf('d')
url({ channel, date: today })
.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', 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')
url({ channel, date: tomorrow })
.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 => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
axios.get.mockImplementation(url => {
if (
url ===
'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({
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program1.json')))
})
} else if (
url ===
'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({
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program2.json')))
})
} else {
return Promise.resolve({ data: '' })
}
})
parser({ content })
.then(result => {
result.map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
expect(result).toMatchObject([
{
start: '2023-01-12T06:28:00.000Z',
stop: '2023-01-12T12:06:00.000Z',
title: 'Le cercle',
description:
"Tant qu'il y aura du cinéma, LE CERCLE sera là. C'est la seule émission télévisée de débats critiques 100% consacrée au cinéma et elle rentre dans sa 18e saison. Chaque semaine, elle offre des joutes enflammées, joyeuses et sans condescendance, sur les films à l'affiche ; et invite avec \"Le questionnaire du CERCLE\" les réalisatrices et réalisateurs à venir partager leur passion cinéphile.",
image:
'https://thumb.canalplus.pro/http/unsafe/{resolutionXY}/filters:quality({imageQualityPercentage})/img-hapi.canalplus.pro:80/ServiceImage/ImageID/107297573',
presenter: ['Lily Bloom'],
rating: {
system: 'CSA',
value: '-10'
}
},
{
start: '2023-01-12T12:06:00.000Z',
stop: '2023-01-12T13:06:00.000Z',
title: 'Illusions perdues',
description:
"Pendant la Restauration, Lucien de Rubempré, jeune provincial d'Angoulême, se rêve poète. Il débarque à Paris en quête de gloire. Il a le soutien de Louise de Bargeton, une aristocrate qui croit en son talent. Pour gagner sa vie, Lucien trouve un emploi dans le journal dirigé par le peu scrupuleux Etienne Lousteau...",
image:
'https://thumb.canalplus.pro/http/unsafe/{resolutionXY}/filters:quality({imageQualityPercentage})/img-hapi.canalplus.pro:80/ServiceImage/ImageID/107356485',
director: ['Xavier Giannoli'],
actors: [
'Benjamin Voisin',
'Cécile de France',
'Vincent Lacoste',
'Xavier Dolan',
'Gérard Depardieu',
'Salomé Dewaels',
'Jeanne Balibar',
'Louis-Do de Lencquesaing',
'Alexis Barbosa',
'Jean-François Stévenin',
'André Marcon',
'Marie Cornillon'
],
writer: ['Xavier Giannoli'],
rating: {
system: 'CSA',
value: '-10'
}
}
])
done()
})
.catch(done)
})
it('can handle empty guide', async () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
const result = await parser({ content })
expect(result).toMatchObject([])
})
const { parser, url } = require('./canalplus.com.config.js')
const fs = require('fs')
const path = require('path')
const axios = require('axios')
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat')
dayjs.extend(customParseFormat)
dayjs.extend(utc)
jest.mock('axios')
const channel = {
site_id: 'bi#198',
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: '' })
}
})
const today = dayjs.utc().startOf('d')
url({ channel, date: today })
.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', 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')
url({ channel, date: tomorrow })
.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 => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
axios.get.mockImplementation(url => {
if (
url ===
'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({
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program1.json')))
})
} else if (
url ===
'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({
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program2.json')))
})
} else {
return Promise.resolve({ data: '' })
}
})
parser({ content })
.then(result => {
result.map(p => {
p.start = p.start.toJSON()
p.stop = p.stop.toJSON()
return p
})
expect(result).toMatchObject([
{
start: '2023-01-12T06:28:00.000Z',
stop: '2023-01-12T12:06:00.000Z',
title: 'Le cercle',
description:
"Tant qu'il y aura du cinéma, LE CERCLE sera là. C'est la seule émission télévisée de débats critiques 100% consacrée au cinéma et elle rentre dans sa 18e saison. Chaque semaine, elle offre des joutes enflammées, joyeuses et sans condescendance, sur les films à l'affiche ; et invite avec \"Le questionnaire du CERCLE\" les réalisatrices et réalisateurs à venir partager leur passion cinéphile.",
image:
'https://thumb.canalplus.pro/http/unsafe/{resolutionXY}/filters:quality({imageQualityPercentage})/img-hapi.canalplus.pro:80/ServiceImage/ImageID/107297573',
presenter: ['Lily Bloom'],
rating: {
system: 'CSA',
value: '-10'
}
},
{
start: '2023-01-12T12:06:00.000Z',
stop: '2023-01-12T13:06:00.000Z',
title: 'Illusions perdues',
description:
"Pendant la Restauration, Lucien de Rubempré, jeune provincial d'Angoulême, se rêve poète. Il débarque à Paris en quête de gloire. Il a le soutien de Louise de Bargeton, une aristocrate qui croit en son talent. Pour gagner sa vie, Lucien trouve un emploi dans le journal dirigé par le peu scrupuleux Etienne Lousteau...",
image:
'https://thumb.canalplus.pro/http/unsafe/{resolutionXY}/filters:quality({imageQualityPercentage})/img-hapi.canalplus.pro:80/ServiceImage/ImageID/107356485',
director: ['Xavier Giannoli'],
actors: [
'Benjamin Voisin',
'Cécile de France',
'Vincent Lacoste',
'Xavier Dolan',
'Gérard Depardieu',
'Salomé Dewaels',
'Jeanne Balibar',
'Louis-Do de Lencquesaing',
'Alexis Barbosa',
'Jean-François Stévenin',
'André Marcon',
'Marie Cornillon'
],
writer: ['Xavier Giannoli'],
rating: {
system: 'CSA',
value: '-10'
}
}
])
done()
})
.catch(done)
})
it('can handle empty guide', async () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
const result = await parser({ content })
expect(result).toMatchObject([])
})