diff --git a/README.md b/README.md index 7d9283d..f75e3e8 100644 --- a/README.md +++ b/README.md @@ -246,4 +246,8 @@ Sometimes a new version of this image will be bound to the same source commit. T - 1.0.76 [02-10-2026](https://github.com/iptv-org/epg/commit/de08d7df85184bb6418cabf56501a9faed2889be) - 1.0.77 - [02-18-2026](https://github.com/iptv-org/epg/commit/a1168b74f5d51a26fdf51129c9af7a296f04b9f6) \ No newline at end of file + [02-18-2026](https://github.com/iptv-org/epg/commit/a1168b74f5d51a26fdf51129c9af7a296f04b9f6) +- 1.0.78 + [02-23-2026](https://github.com/iptv-org/epg/commit/e4f92bb2a2768dcba3dbbd52b19d78d96bebc31e) +
Updates custom fixes to latest code +
Includes fix for pickx.be provider \ No newline at end of file diff --git a/fixes/movistarplus.es/movistarplus.es.config.js b/fixes/movistarplus.es/movistarplus.es.config.js index 4292dc1..930b40e 100644 --- a/fixes/movistarplus.es/movistarplus.es.config.js +++ b/fixes/movistarplus.es/movistarplus.es.config.js @@ -1,31 +1,115 @@ +//https://github.com/iptv-org/epg/blob/master/sites/movistarplus.es/movistarplus.es.config.js + const axios = require('axios') const cheerio = require('cheerio') const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') + +dayjs.extend(utc) +dayjs.extend(timezone) module.exports = { site: 'movistarplus.es', days: 2, url({ channel, date }) { - return `https://www.movistarplus.es/programacion-tv/${channel.site_id}/${date.format( - 'YYYY-MM-DD' - )}` + return `https://www.movistarplus.es/programacion-tv/${channel.site_id}/${date.format('YYYY-MM-DD')}` }, - parser({ content }) { + request: { + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', + 'Accept-Language': 'es-ES,es;q=0.9,en;q=0.8', + Referer: 'https://www.movistarplus.es/programacion-tv' + }, + maxRedirects: 5 + }, + async parser({ content, date }) { let programs = [] - let items = parseItems(content) - if (!items.length) return programs - items.forEach(el => { + const $ = cheerio.load(content) + + const programDivs = $('div[id^="ele-"]').toArray() + + for (let i = 0; i < programDivs.length; i++) { + const el = $(programDivs[i]) + + const title = el.find('li.title').text().trim() + if (!title) continue + + const timeText = el.find('li.time').text().trim() + if (!timeText) continue + + const [hours, minutes] = timeText.split(':').map(h => parseInt(h, 10)) + + // Parse time in Spain timezone (Europe/Madrid) + let startDate = dayjs.tz( + `${date.format('YYYY-MM-DD')} ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`, + 'YYYY-MM-DD HH:mm', + 'Europe/Madrid' + ) + + // If the time is in early morning (before 5 AM), it's the next day + if (hours < 5) { + startDate = startDate.add(1, 'day') + } + + // Calculate end time from next program's start time + let endDate + if (i < programDivs.length - 1) { + const nextEl = $(programDivs[i + 1]) + const nextTimeText = nextEl.find('li.time').text().trim() + if (nextTimeText) { + const [nextHours, nextMinutes] = nextTimeText.split(':').map(h => parseInt(h, 10)) + endDate = dayjs.tz( + `${date.format('YYYY-MM-DD')} ${nextHours.toString().padStart(2, '0')}:${nextMinutes.toString().padStart(2, '0')}`, + 'YYYY-MM-DD HH:mm', + 'Europe/Madrid' + ) + + // If the next time is in early morning (before 5 AM), it's the next day + if (nextHours < 5) { + endDate = endDate.add(1, 'day') + } + + // If end time is still before or same as start time, add another day + if (endDate.isBefore(startDate) || endDate.isSame(startDate)) { + endDate = endDate.add(1, 'day') + } + } + } + + // If no end time, use start of next day + if (!endDate) { + endDate = startDate.add(1, 'day').startOf('day') + } + + const programLink = el.find('a').attr('href') + let description = null + + if (programLink) { + description = await getProgramDescription(programLink).catch(() => null) + } + programs.push({ - title: el.item.name, - start: dayjs(el.item.startDate), - stop: dayjs(el.item.endDate) + title, + description, + start: startDate, + stop: endDate }) - }) + } + return programs }, async channels() { const html = await axios - .get('https://www.movistarplus.es/programacion-tv') + .get('https://www.movistarplus.es/programacion-tv', { + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' + } + }) .then(r => r.data) .catch(console.log) @@ -46,15 +130,17 @@ module.exports = { } } -function parseItems(content) { - try { - const $ = cheerio.load(content) - let scheme = $('script:contains("@type": "ItemList")').html() - scheme = JSON.parse(scheme) - if (!scheme || !Array.isArray(scheme.itemListElement)) return [] +async function getProgramDescription(programUrl) { + const response = await axios.get(programUrl, { + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + Referer: 'https://www.movistarplus.es/programacion-tv/' + } + }) - return scheme.itemListElement - } catch { - return [] - } + const $ = cheerio.load(response.data) + const description = $('.show-content .text p').first().text().trim() || null + + return description } \ No newline at end of file diff --git a/fixes/orangetv.orange.es/orangetv.orange.es.channels.xml b/fixes/orangetv.orange.es/orangetv.orange.es.channels.xml deleted file mode 100644 index 8d666c2..0000000 --- a/fixes/orangetv.orange.es/orangetv.orange.es.channels.xml +++ /dev/null @@ -1,172 +0,0 @@ - - - LA 1 HD - La 2 HD - 24 Horas - TDP HD - Clan HD - Antena 3 HD - Cuatro HD - Telecinco HD - laSexta HD - SELEKT - STAR Channel - AMC - AMC Break - AMC CRIME - Warner TV - AXN - Comedy Central - Calle 13 - XTRM - SYFY - Cosmo - Enfamilia - Decasa - CanalCocina - Runtime Series - FDF - Neox - Energy - Atreseries - Divinity - Nova - Canal Hollywood - AXN Movies - Somos - TCM - Sundance TV - Dark - Runtime Cine y Series - Runtime Thriller/Terror - Runtime Acción - Runtime Comedia - Runtime Crimen - Runtime Romance - RunTime Clásicos - Cines Verdi TV - Cine Feel Good - Paramount Network - Dreamworks - Bom Cine - DKISS - Ten - Odisea - Mega - TR3CE - DMAX - La otra HD - Boing - Toon Goggles - Nick JR - Disney Junior - Nick - Be Mad TV - Canal Historia - National Geographic - NatGeo Wild - Discovery - NatureTime - 7RM HD - Levante TV - Canal Sur - CanalSur 2 - Andalucía TV - 7 TV Andalucia - TV3 HD - TV3CAT - 3/24 - TV Castellon - 8 Mediterraneo - A punt HD - IB3 HD - RTV Canaria HD - ETB1 HD - ETB2 HD - ETB3 - ETB4 - ETB Basque - TVG HD - TVG2 HD - TPA HD - Televigo - Galicia TV - TV Ceuta - TVR HD - Navarra TV HD - Navarra TV2 - Telemadrid HD - Real Madrid - SEVILLA FC Televisión - Betis TV - M+ Liga de Campeones - M+ Liga de Campeones 2 - M+ Liga de Campeones 3 - M+ Liga de Campeones 4 - M+ Liga de Campeones 5 - M+ Liga de Campeones 6 - M+ Liga de Campeones 7 - M+ Liga de Campeones 8 - M+ Liga de Campeones 9 - M+ Liga de Campeones 10 - M+ Liga de Campeones 11 - M+ Liga de Campeones 12 - M+ Liga de Campeones 13 - M+ Liga de Campeones 14 - M+ Liga de Campeones 15 - M+ Liga de Campeones 16 - M+ Liga de Campeones 17 - M LALIGA TV UHD - M+ LALIGA TV - M+ LALIGA TV 2 - M+ LALIGATV 3 - M+ LALIGATV 4 - LA LIGA TV - DAZN LALIGA - DAZN LALIGA 2 - LALIGATV HYPERMOTION - LALIGATV HYPERMOTION 2 - LALIGATV HYPERMOTION 3 - GOL PLAY - Mezzo HD - MTV 00s - HIT TV - MTV España - MTV Live - Sol Música - Classica HD - Festival 4K - Djazz - iConcerts - MezzoLiveHD - TRACE Latina - TRACE Urban - Caracol TV - EWTN - Gulli - France 2 - France 5 - Trace Sport Stars - TV5MONDE - Qwest TV - Eurosport 1 - Eurosport 2 - Myzen TV - FightSports - Fight Box HD - Gametoon - Euronews - El Toro TV - Motorvision - Esport3 - BBC World HD - CNN - Deutsche Welle - Al Jazeera - Pro TV - Ubeat - OUTtv - ¡BUENVIAJE! - Tennis Channel - Nautical Channel - \ No newline at end of file diff --git a/fixes/orangetv.orange.es/orangetv.orange.es.config.js b/fixes/orangetv.orange.es/orangetv.orange.es.config.js index 080c45c..f7e675a 100644 --- a/fixes/orangetv.orange.es/orangetv.orange.es.config.js +++ b/fixes/orangetv.orange.es/orangetv.orange.es.config.js @@ -1,3 +1,5 @@ +//https://github.com/iptv-org/epg/blob/master/sites/orangetv.orange.es/orangetv.orange.es.config.js + const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const doFetch = require('@ntlab/sfetch') @@ -18,9 +20,12 @@ module.exports = { request: { cache: { ttl: 24 * 60 * 60 * 1000 // 1 day + }, + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36' } }, - url({ date, segment = 1 }) { + url: function({ date, segment = 1 }) { return `${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_${segment}.json` }, async parser({ content, channel, date }) { diff --git a/fixes/pickx.be/pickx.be.config.js b/fixes/pickx.be/pickx.be.config.js index ab4757e..08e45fe 100644 --- a/fixes/pickx.be/pickx.be.config.js +++ b/fixes/pickx.be/pickx.be.config.js @@ -1,4 +1,4 @@ -// credit for this fix goes to davidclaeysquinones for his PR on https://github.com/iptv-org/epg/pull/2430, https://github.com/iptv-org/epg/pull/2520 and to BellezaEmporium for his PR on https://github.com/iptv-org/epg/pull/2480, https://github.com/iptv-org/epg/pull/2525 +//https://github.com/iptv-org/epg/blob/e4f92bb2a2768dcba3dbbd52b19d78d96bebc31e/sites/pickx.be/pickx.be.config.js const axios = require('axios') const dayjs = require('dayjs') @@ -36,7 +36,7 @@ module.exports = { ? item.program.translatedCategory[channel.lang] : item.program.category.split('.')[1], image: item.program.posterFileName - ? `https://experience-cache.proximustv.be/posterserver/poster/EPG/w-166_h-110/${item.program.posterFileName}` + ? `https://experience-cache.cdi.streaming.proximustv.be/posterserver/poster/EPG/${item.program.posterFileName}` : null, season: item.program.seasonNumber, episode: item.program.episodeNumber, diff --git a/fixes/telenet.tv/telenet.tv.config.js b/fixes/telenet.tv/telenet.tv.config.js index 1a6090e..cf9117a 100644 --- a/fixes/telenet.tv/telenet.tv.config.js +++ b/fixes/telenet.tv/telenet.tv.config.js @@ -1,11 +1,11 @@ -// credit for this fix goes to davidclaeysquinones for his PR on https://github.com/iptv-org/epg/pull/2429 +//https://github.com/iptv-org/epg/blob/e4f92bb2a2768dcba3dbbd52b19d78d96bebc31e/sites/telenet.tv/telenet.tv.config.js const axios = require('axios') const dayjs = require('dayjs') const API_STATIC_ENDPOINT = 'https://static.spark.telenet.tv/eng/web/epg-service-lite/be' const API_PROD_ENDPOINT = 'https://spark-prod-be.gnp.cloud.telenet.tv/eng/web/linear-service/v2' -const API_IMAGE_ENDPOINT = 'https://staticqbr-prod-be.gnp.cloud.telenet.tv/image-service'; +const API_IMAGE_ENDPOINT = 'https://staticqbr-prod-be.gnp.cloud.telenet.tv/image-service' module.exports = { site: 'telenet.tv', @@ -16,7 +16,7 @@ module.exports = { } }, url: function ({ date, channel }) { - return `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date.format('YYYYMMDDHHmmss')}` + return `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date.format('YYYYMMDD')}000000` }, async parser({ content, channel, date }) { let programs = [] @@ -24,25 +24,19 @@ module.exports = { if (!items.length) return programs const promises = [ axios.get( - `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date - .add(6, 'h') - .format('YYYYMMDDHHmmss')}`, + `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date.format('YYYYMMDD')}060000`, { responseType: 'arraybuffer' } ), axios.get( - `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date - .add(12, 'h') - .format('YYYYMMDDHHmmss')}`, + `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date.format('YYYYMMDD')}120000`, { responseType: 'arraybuffer' } ), axios.get( - `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date - .add(18, 'h') - .format('YYYYMMDDHHmmss')}`, + `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date.format('YYYYMMDD')}180000`, { responseType: 'arraybuffer' } @@ -65,6 +59,7 @@ module.exports = { const detail = await loadProgramDetails(item, channel) programs.push({ title: item.title, + subTitle: detail.episodeName, icon: parseIcon(item), description: detail.longDescription, category: detail.genres, @@ -96,7 +91,7 @@ module.exports = { async function loadProgramDetails(item, channel) { if (!item.id) return {} - const url = `${API_PROD_ENDPOINT}/replayEvent/${item.id}?returnLinearContent=true&language=${channel.lang}` + const url = `${API_PROD_ENDPOINT}/replayEvent/${item.id}?returnLinearContent=true&language=${channel.lang}` const data = await axios .get(url) .then(r => r.data) @@ -136,5 +131,5 @@ function parseEpisode(detail) { } function parseIcon(item) { - return `${API_IMAGE_ENDPOINT}/intent/${item.id}/posterTile`; + return `${API_IMAGE_ENDPOINT}/intent/${item.id}/posterTile` } \ No newline at end of file diff --git a/fixes/web.magentatv.de/web.magentatv.de.config.js b/fixes/web.magentatv.de/web.magentatv.de.config.js index 890f50a..0960028 100644 --- a/fixes/web.magentatv.de/web.magentatv.de.config.js +++ b/fixes/web.magentatv.de/web.magentatv.de.config.js @@ -1,16 +1,13 @@ -// credit for this fix goes to klausellus-wallace for his PR on https://github.com/iptv-org/epg/pull/2458 +// https://github.com/iptv-org/epg/blob/e4f92bb2a2768dcba3dbbd52b19d78d96bebc31e/sites/web.magentatv.de/web.magentatv.de.config.js const axios = require('axios') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') -const fetch = require('node-fetch') -const { upperCase } = require('lodash') let X_CSRFTOKEN -let COOKIE +let Cookie const cookiesToExtract = ['JSESSIONID', 'CSESSIONID', 'CSRFSESSION'] -const extractedCookies = {} dayjs.extend(utc) dayjs.extend(customParseFormat) @@ -21,10 +18,9 @@ module.exports = { url: 'https://api.prod.sngtv.magentatv.de/EPG/JSON/PlayBillList', request: { method: 'POST', - headers: function () { - return setHeaders() + async headers() { + return await setHeaders() }, - data({ channel, date }) { return { count: -1, @@ -32,7 +28,8 @@ module.exports = { offset: 0, properties: [ { - include: 'endtime,genres,id,name,starttime,channelid,pictures,introduce,subName,seasonNum,subNum,cast,country,producedate,externalIds', + include: + 'endtime,genres,id,name,starttime,channelid,pictures,introduce,subName,seasonNum,subNum,cast,country,producedate,externalIds', name: 'playbill' } ], @@ -43,8 +40,8 @@ module.exports = { } } }, - parser: function ({ content }) { - let programs = [] + parser({ content }) { + const programs = [] const items = parseItems(content) items.forEach(item => { programs.push({ @@ -60,7 +57,7 @@ module.exports = { directors: parseDirectors(item), producers: parseProducers(item), adapters: parseAdapters(item), - country: upperCase(item.country), + country: item.country?.toUpperCase(), date: item.producedate, urls: parseUrls(item) }) @@ -115,15 +112,15 @@ function parseCategory(item) { } function parseDirectors(item) { - if (!item.cast || !item.cast.director) return []; + if (!item.cast || !item.cast.director) return [] return item.cast.director .replace('und', ',') .split(',') - .map(i => i.trim()); + .map(i => i.trim()) } function parseProducers(item) { - if (!item.cast || !item.cast.producer) return []; + if (!item.cast || !item.cast.producer) return [] return item.cast.producer .replace('und', ',') .split(',') @@ -131,7 +128,7 @@ function parseProducers(item) { } function parseAdapters(item) { - if (!item.cast || !item.cast.adaptor) return []; + if (!item.cast || !item.cast.adaptor) return [] return item.cast.adaptor .replace('und', ',') .split(',') @@ -140,7 +137,7 @@ function parseAdapters(item) { function parseUrls(item) { // currently only a imdb id is returned by the api, thus we can construct the url here - if (!item.externalIds) return []; + if (!item.externalIds) return [] return JSON.parse(item.externalIds) .filter(externalId => externalId.type === 'imdb' && externalId.id) .map(externalId => ({ system: 'imdb', value: `https://www.imdb.com/title/${externalId.id}` })) @@ -167,66 +164,52 @@ function parseItems(content) { return data.playbilllist } -// Function to try to fetch COOKIE and X_CSRFTOKEN -function fetchCookieAndToken() { - return fetch( - 'https://api.prod.sngtv.magentatv.de/EPG/JSON/Authenticate?SID=firstup&T=Windows_chrome_118', - { - headers: { - accept: 'application/json, text/javascript, */*; q=0.01', - 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', - 'sec-fetch-dest': 'empty', - 'sec-fetch-mode': 'cors', - 'sec-fetch-site': 'same-origin', - 'x-requested-with': 'XMLHttpRequest', - Referer: 'https://web.magentatv.de/', - 'Referrer-Policy': 'strict-origin-when-cross-origin' +async function fetchCookieAndToken() { + // Only fetch the cookies and csrfToken if they are not already set + if (X_CSRFTOKEN && Cookie) { + return + } + + try { + const response = await axios.request({ + url: 'https://api.prod.sngtv.magentatv.de/EPG/JSON/Authenticate', + params: { + SID: 'firstup', + T: 'Windows_chrome_118' }, - body: '{"terminalid":"00:00:00:00:00:00","mac":"00:00:00:00:00:00","terminaltype":"WEBTV","utcEnable":1,"timezone":"Etc/GMT0","userType":3,"terminalvendor":"Unknown"}', - method: 'POST' + method: 'POST', + data: '{"terminalid":"00:00:00:00:00:00","mac":"00:00:00:00:00:00","terminaltype":"WEBTV","utcEnable":1,"timezone":"Etc/GMT0","userType":3,"terminalvendor":"Unknown"}', + }) + + // Extract the cookies specified in cookiesToExtract + const setCookieHeader = response.headers['set-cookie'] || [] + const extractedCookies = [] + cookiesToExtract.forEach(cookieName => { + const regex = new RegExp(`${cookieName}=(.+?)(;|$)`) + const match = setCookieHeader.find(header => regex.test(header)) + + if (match) { + const cookieString = regex.exec(match)[0] + extractedCookies.push(cookieString) + } + }) + + // check if we recieved a csrfToken only then store the values + if (!response.data.csrfToken) { + console.log('csrfToken not found in the response.') + return } - ) - .then(response => { - // Check if the response status is OK (2xx) - if (!response.ok) { - throw new Error('HTTP request failed') - } - // Extract the set-cookie header - const setCookieHeader = response.headers.raw()['set-cookie'] + X_CSRFTOKEN = response.data.csrfToken + Cookie = extractedCookies.join(' ') - // Extract the cookies specified in cookiesToExtract - cookiesToExtract.forEach(cookieName => { - const regex = new RegExp(`${cookieName}=(.+?)(;|$)`) - const match = setCookieHeader.find(header => regex.test(header)) - - if (match) { - const cookieValue = regex.exec(match)[1] - extractedCookies[cookieName] = cookieValue - } - }) - - return response.json() - }) - .then(data => { - if (data.csrfToken) { - X_CSRFTOKEN = data.csrfToken - COOKIE = `JSESSIONID=${extractedCookies.JSESSIONID}; CSESSIONID=${extractedCookies.CSESSIONID}; CSRFSESSION=${extractedCookies.CSRFSESSION}; JSESSIONID=${extractedCookies.JSESSIONID};` - } else { - console.log('csrfToken not found in the response.') - } - }) - .catch(error => { - console.error(error) - }) + } catch(error) { + console.error(error) + } } -function setHeaders() { - return fetchCookieAndToken().then(() => { - return { - X_CSRFTOKEN: X_CSRFTOKEN, - 'Content-Type': 'application/json', - Cookie: COOKIE - } - }) +async function setHeaders() { + await fetchCookieAndToken() + + return { X_CSRFTOKEN, Cookie } } \ No newline at end of file