Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
FROM node:22-alpine
<<<<<<< HEAD

=======
ARG GIT_REPO=https://github.com/iptv-org/epg.git
ARG GIT_BRANCH=master
>>>>>>> parent of f0b037d7 (Change Git repo)
ARG WORKDIR=/epg
ENV CRON_SCHEDULE="0 0 * * *"
ENV RUN_AT_STARTUP=true

RUN apk update \
&& apk upgrade --available \
&& apk add curl git tzdata bash \
&& apk add tzdata bash \
&& npm install -g npm@latest \
&& npm install pm2 -g \
&& mkdir $(echo "${WORKDIR}") -p \
&& cd $WORKDIR \
&& git clone --depth 1 -b $(echo "${GIT_BRANCH} ${GIT_REPO}") . \
&& npm install \
&& mkdir /public
RUN apk del git curl \
&& rm -rf /var/cache/apk/*
COPY pm2.config.js $WORKDIR
&& mkdir $(echo "${WORKDIR}") -p
WORKDIR $WORKDIR
COPY package*.json ./
RUN npm install
COPY . .
RUN mkdir -p /public
EXPOSE 3000
CMD [ "pm2-runtime", "pm2.config.js" ]
141 changes: 94 additions & 47 deletions sites/movistarplus.es/movistarplus.es.config.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,113 @@
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')}`
},
async 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

const $ = cheerio.load(content)
const programElements = $('div[id^="ele-"]').get()

for (let i = 0; i < items.length; i++) {
const el = items[i]
let description = null
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))

if (programElements[i]) {
const programDiv = $(programElements[i])
const programLink = programDiv.find('a').attr('href')

if (programLink) {
const idMatch = programLink.match(/id=(\d+)/)
if (idMatch && idMatch[1]) {
description = await getProgramDescription(programLink).catch(() => null)
// 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,
description: description,
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)

Expand All @@ -65,33 +128,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 []

return scheme.itemListElement
} catch {
return []
}
}

async function getProgramDescription(programUrl) {
try {
const response = await axios.get(programUrl, {
headers: {
'Referer': 'https://www.movistarplus.es/programacion-tv/'
}
})
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/'
}
})

const $ = cheerio.load(response.data)
const description = $('.show-content .text p').first().text().trim() || null
const $ = cheerio.load(response.data)
const description = $('.show-content .text p').first().text().trim() || null

return description
} catch (error) {
console.error(`Error fetching description from ${programUrl}:`, error.message)
return null
}
return description
}