Files
epg/sites/guidetnt.com/guidetnt.com.config.js

339 lines
10 KiB
JavaScript
Raw Normal View History

2025-09-28 17:55:05 +03:00
const cheerio = require('cheerio')
const axios = require('axios')
const dayjs = require('dayjs')
const customParseFormat = require('dayjs/plugin/customParseFormat')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')
require('dayjs/locale/fr')
dayjs.extend(customParseFormat)
dayjs.extend(utc)
dayjs.extend(timezone)
const PARIS_TZ = 'Europe/Paris'
module.exports = {
site: 'guidetnt.com',
days: 2,
url({ channel, date }) {
const now = dayjs()
const demain = now.add(1, 'd')
if (date && date.isSame(demain, 'day')) {
return `https://www.guidetnt.com/tv-demain/programme-${channel.site_id}`
} else if (!date || date.isSame(now, 'day')) {
return `https://www.guidetnt.com/tv/programme-${channel.site_id}`
} else {
return null
}
},
async parser({ content, date }) {
const programs = []
const allItems = parseItems(content)
const items = allItems?.rows
const itemDate = allItems?.formattedDate
for (const item of items) {
const prev = programs[programs.length - 1]
const $item = cheerio.load(item)
const title = parseTitle($item)
let start = parseStart($item, itemDate)
if (!start || !title) return
if (prev) {
if (start.isBefore(prev.start)) {
start = start.add(1, 'd')
date = date.add(1, 'd')
}
prev.stop = start
}
let stop = start.add(30, 'm')
let itemDetails = null
let subTitle = null
//let duration = null
let country = null
let productionDate = null
let episode = null
let season = null
let category = parseCategory($item)
let description = parseDescription($item)
const itemDetailsURL = parseDescriptionURL($item)
if (itemDetailsURL) {
const url = 'https://www.guidetnt.com' + itemDetailsURL
try {
const response = await axios.get(url)
itemDetails = parseItemDetails(response.data)
} catch (err) {
console.error(`Erreur lors du fetch des détails pour l'item: ${url}`, err)
}
const timeRange = parseTimeRange(itemDetails?.programHour, date.format('YYYY-MM-DD'))
start = timeRange?.start
stop = timeRange?.stop
subTitle = itemDetails?.subTitle
if (title == subTitle) subTitle = null
description = itemDetails?.description
const categoryDetails = parseCategoryText(itemDetails?.category)
//duration = categoryDetails?.duration
country = categoryDetails?.country
productionDate = categoryDetails?.productionDate
season = categoryDetails?.season
episode = categoryDetails?.episode
}
// See https://www.npmjs.com/package/epg-parser for parameters
programs.push({
title,
subTitle: subTitle,
description: description,
image: itemDetails?.image,
category: category,
directors: itemDetails?.directorActors?.Réalisateur,
actors: itemDetails?.directorActors?.Acteur,
country: country,
date: productionDate,
//duration: duration, // Tried with length: too, but does not work ! (stop-start is not accurate because of Ads)
season: season,
episode: episode,
start,
stop
})
}
return programs
},
async channels() {
const response = await axios.get('https://www.guidetnt.com')
const channels = []
const $ = cheerio.load(response.data)
// Look inside each .tvlogo container
$('.tvlogo').each((i, el) => {
// Find all descendants that have an alt attribute
$(el)
.find('[alt]')
.each((j, subEl) => {
const alt = $(subEl).attr('alt')
const href = $(subEl).attr('href')
if (href && alt && alt.trim() !== '') {
const name = alt.trim()
const site_id = href.replace(/^\/tv\/programme-/, '')
channels.push({
lang: 'fr',
name,
site_id
})
}
})
})
return channels
}
}
function parseTimeRange(timeRange, baseDate) {
// Split times
const [startStr, endStr] = timeRange.split(' - ').map(s => s.trim())
// Parse with base date
const start = dayjs(`${baseDate} ${startStr}`, 'YYYY-MM-DD HH:mm')
let end = dayjs(`${baseDate} ${endStr}`, 'YYYY-MM-DD HH:mm')
// Handle possible day wrap (e.g., 23:30 - 00:15)
if (end.isBefore(start)) {
end = end.add(1, 'day')
}
// Calculate duration in minutes
const diffMinutes = end.diff(start, 'minute')
return {
start: start.format(),
stop: end.format(),
duration: diffMinutes
}
}
function parseItemDetails(itemDetails) {
const $ = cheerio.load(itemDetails)
const program = $('.program-wrapper').first()
const programHour = program.find('.program-hour').text().trim()
const programTitle = program.find('.program-title').text().trim()
const programElementBold = program.find('.program-element-bold').text().trim()
const programArea1 = program.find('.program-element.program-area-1').text().trim()
let description = ''
const programElements = $('.program-element').filter((i, el) => {
const classAttr = $(el).attr('class')
// Return true only if it is exactly "program-element" (no extra classes)
return classAttr.trim() === 'program-element'
})
programElements.each((i, el) => {
description += $(el).text().trim()
})
const area2Node = $('.program-area-2').first()
const area2 = $(area2Node)
const data = {}
let currentLabel = null
let texts = []
area2.contents().each((i, node) => {
if (node.type === 'tag' && node.name === 'strong') {
// If we had collected some text for the previous label, save it
if (currentLabel && texts.length) {
data[currentLabel] = texts.join('').trim().replace(/,\s*$/, '') // Remove trailing comma
}
// New label - get text without colon
currentLabel = $(node).text().replace(/:$/, '').trim()
texts = []
} else if (currentLabel) {
// Append the text content (text node or others)
if (node.type === 'text') {
texts.push(node.data)
} else if (node.type === 'tag' && node.name !== 'strong' && node.name !== 'br') {
texts.push($(node).text())
}
}
})
// Save last label text
if (currentLabel && texts.length) {
data[currentLabel] = texts.join('').trim().replace(/,\s*$/, '')
}
const imgSrc = program.find('div[style*="float:left"]')?.find('img')?.attr('src') || null
return {
programHour,
title: programTitle,
subTitle: programElementBold,
category: programArea1,
description: description,
directorActors: data,
image: imgSrc
}
}
function parseCategoryText(text) {
if (!text) return null
const parts = text
.split(',')
.map(s => s.trim())
.filter(Boolean)
const len = parts.length
const category = parts[0] || null
if (len < 3) {
return {
category: category,
duration: null,
country: null,
productionDate: null,
season: null,
episode: null
}
}
// Check last part: date if numeric
const dateCandidate = parts[len - 1]
const productionDate = /^\d{4}$/.test(dateCandidate) ? dateCandidate : null
// Check for duration (first part containing "minutes")
let durationMinute = null
//let duration = null
let episode = null
let season = null
let durationIndex = -1
for (let i = 0; i < len; i++) {
if (parts[i].toLowerCase().includes('minute')) {
durationMinute = parts[i].trim()
durationMinute = durationMinute.replace('minutes', '')
durationMinute = durationMinute.replace('minute', '')
//duration = [{ units: 'minutes', value: durationMinute }],
durationIndex = i
} else if (parts[i].toLowerCase().includes('épisode')) {
const match = text.match(/épisode\s+(\d+)(?:\/(\d+))?/i)
if (match) {
episode = parseInt(match[1], 10)
}
} else if (parts[i].toLowerCase().includes('saison')) {
season = parts[i].replace('saison', '').trim()
}
}
// Country: second to last
const countryIndex = len - 2
let country = durationIndex === countryIndex ? null : parts[countryIndex]
return {
category,
durationMinute,
country,
productionDate,
season,
episode
}
}
function parseTitle($item) {
return $item('.channel-programs-title a').text().trim()
}
function parseDescription($item) {
return $item('#descr').text().trim() || null
}
function parseDescriptionURL($item) {
const descrLink = $item('#descr a')
return descrLink.attr('href') || null
}
function parseCategory($item) {
let type = null
$item('.channel-programs-title span').each((i, span) => {
const className = $item(span).attr('class')
if (className && className.startsWith('text_bg')) {
type = $item(span).text().trim()
}
})
return type
}
function parseStart($item, itemDate) {
const dt = $item('.channel-programs-time a').text().trim()
if (!dt) return null
const datetimeStr = `${itemDate} ${dt}`
return dayjs.tz(datetimeStr, 'YYYY-MM-DD HH:mm', PARIS_TZ)
}
function parseItems(content) {
const $ = cheerio.load(content)
// Extract header information
const logoSrc = $('#logo img').attr('src')
const title = $('#title h1').text().trim()
const subtitle = $('#subtitle').text().trim()
const dateMatch = subtitle.match(/(\d{1,2} \w+ \d{4})/)
const dateStr = dateMatch ? dateMatch[1].toLowerCase() : null
// Parse the French date string
const parsedDate = dayjs(dateStr, 'D MMMM YYYY', 'fr')
// Format it as YYYY-MM-DD
const formattedDate = parsedDate.format('YYYY-MM-DD')
const rows = $('.channel-row').toArray()
return {
rows,
logoSrc,
title,
formattedDate
}
}