diff --git a/SITES.md b/SITES.md
index f651524ed..7a6282bc7 100644
--- a/SITES.md
+++ b/SITES.md
@@ -14,6 +14,7 @@
| antennaeurope.gr | 1 | 1 | π’ | |
| antennapacific.gr | 1 | 1 | π’ | |
| antennasatellite.gr | 1 | 1 | π’ | |
+ | app.tvufop.com.br | 1 | 1 | π’ | |
| arianatelevision.com | 1 | 1 | π’ | |
| arirang.com | 3 | 3 | π’ | |
| artonline.tv | 5 | 5 | π’ | |
@@ -33,7 +34,7 @@
| cosmotetv.gr | 109 | 0 | π’ | |
| ctc.ru | 1 | 1 | π’ | |
| cubmu.com | 140 | 104 | π’ | |
- | cyta.com.cy | 119 | 0 | π’ | https://github.com/iptv-org/epg/issues/3037 |
+ | cyta.com.cy | 119 | 0 | π΄ | https://github.com/iptv-org/epg/issues/3037 |
| dens.tv | 54 | 48 | π’ | |
| derana.lk | 1 | 1 | π’ | |
| digea.gr | 88 | 0 | π’ | |
@@ -124,7 +125,7 @@
| passie.nl | 1 | 1 | π’ | |
| pbsguam.org | 1 | 1 | π’ | |
| pickx.be | 404 | 375 | π’ | |
- | player.ee.co.uk | 241 | 198 | π’ | |
+ | player.ee.co.uk | 241 | 198 | π’ | https://github.com/iptv-org/epg/issues/3050 |
| playtv.unifi.com.my | 66 | 61 | π’ | |
| plex.tv | 1287 | 512 | π’ | |
| pluto.tv | 3041 | 415 | π’ | |
@@ -153,7 +154,7 @@
| singtel.com | 155 | 113 | π’ | |
| sjonvarp.is | 13 | 13 | π’ | |
| sky.co.nz | 111 | 93 | π’ | |
- | sky.com | 542 | 489 | π’ | |
+ | sky.com | 542 | 489 | π‘ | https://github.com/iptv-org/epg/issues/2763 |
| sky.de | 74 | 74 | π’ | |
| skylife.co.kr | 257 | 0 | π’ | |
| skyperfectv.co.jp | 137 | 129 | π’ | |
@@ -199,7 +200,7 @@
| tvarenasport.hr | 10 | 10 | π’ | |
| tvcesoir.fr | 135 | 132 | π’ | |
| tvcubana.icrt.cu | 10 | 10 | π’ | |
- | tvgids.nl | 115 | 85 | π’ | https://github.com/iptv-org/epg/issues/3048 |
+ | tvgids.nl | 115 | 101 | π’ | |
| tvguide.com | 153 | 153 | π’ | https://github.com/iptv-org/epg/issues/2967 |
| tvguide.myjcom.jp | 134 | 128 | π’ | |
| tvhebdo.com | 317 | 213 | π’ | |
@@ -214,7 +215,7 @@
| tvmustra.hu | 189 | 0 | π’ | |
| tvpassport.com | 19287 | 2496 | π’ | |
| tvplus.com.tr | 150 | 144 | π’ | https://github.com/iptv-org/epg/issues/2983 |
- | tvprofil.com | 9091 | 408 | π’ | https://github.com/iptv-org/epg/issues/3032 |
+ | tvprofil.com | 9091 | 408 | π΄ | https://github.com/iptv-org/epg/issues/3032 |
| tvtv.us | 2299 | 2230 | π’ | |
| v3.myafn.dodmedia.osd.mil | 8 | 8 | π’ | |
| vantagetv.ee | 3 | 1 | π’ | |
diff --git a/sites/app.tvufop.com.br/app.tvufop.com.br.channels.xml b/sites/app.tvufop.com.br/app.tvufop.com.br.channels.xml
new file mode 100644
index 000000000..cc2338e50
--- /dev/null
+++ b/sites/app.tvufop.com.br/app.tvufop.com.br.channels.xml
@@ -0,0 +1,7 @@
+
+
+
+
+ TV UFOP
+
+
diff --git a/sites/app.tvufop.com.br/app.tvufop.com.br.config.js b/sites/app.tvufop.com.br/app.tvufop.com.br.config.js
new file mode 100644
index 000000000..a113981b7
--- /dev/null
+++ b/sites/app.tvufop.com.br/app.tvufop.com.br.config.js
@@ -0,0 +1,167 @@
+const { File } = require('node:buffer')
+
+
+if (typeof global.File === 'undefined') {
+
+ global.File = File
+
+}
+
+
+const cheerio = require('cheerio')
+
+
+module.exports = {
+
+ site: 'app.tvufop.com.br',
+
+ days: 7,
+
+ url() {
+
+ return 'https://app.tvufop.com.br/epg/epg_tvufop_web.xml'
+
+ },
+
+ parser({ content, channel, date }) {
+
+ const $ = cheerio.load(content || '', { xmlMode: true, decodeEntities: false })
+
+ const programs = []
+
+
+ const dayStart = date.startOf('d').toDate()
+
+ const dayEnd = date.add(1, 'd').startOf('d').toDate()
+
+
+ $(`programme[channel="${channel.site_id}"]`).each((_, el) => {
+
+ const $el = $(el)
+
+
+ const start = parseXmltvDate($el.attr('start'))
+
+ const stop = parseXmltvDate($el.attr('stop'))
+
+
+ if (!start || !stop) return
+
+ if (start >= dayEnd || stop <= dayStart) return
+
+
+ const title = textOf($el, 'title')
+
+ if (!title) return
+
+
+ const item = {
+
+ title,
+
+ start,
+
+ stop
+
+ }
+
+
+ const description = textOf($el, 'desc')
+
+ if (description) item.description = description
+
+
+ const icon = $el.find('icon').attr('src')
+
+ if (icon) item.icon = icon
+
+
+ const rating = $el.find('rating > value').first().text().trim()
+
+ if (rating) item.rating = rating
+
+
+ programs.push(item)
+
+ })
+
+
+ return programs
+
+ }
+
+}
+
+
+function textOf($el, tagName) {
+
+ return $el.find(tagName).first().text().trim()
+
+}
+
+
+function parseXmltvDate(value) {
+
+ if (!value) return null
+
+
+ const m = value.trim().match(
+
+ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\s+([+-])(\d{2})(\d{2})$/
+
+ )
+
+
+ if (!m) return null
+
+
+ const [
+
+ ,
+
+ year,
+
+ month,
+
+ day,
+
+ hour,
+
+ minute,
+
+ second,
+
+ sign,
+
+ tzHour,
+
+ tzMinute
+
+ ] = m
+
+
+ const utcMs = Date.UTC(
+
+ Number(year),
+
+ Number(month) - 1,
+
+ Number(day),
+
+ Number(hour),
+
+ Number(minute),
+
+ Number(second)
+
+ )
+
+
+ const offsetMinutes =
+
+ (Number(tzHour) * 60 + Number(tzMinute)) * (sign === '+' ? 1 : -1)
+
+
+ return new Date(utcMs - offsetMinutes * 60 * 1000)
+
+}
diff --git a/sites/app.tvufop.com.br/app.tvufop.com.br.test.js b/sites/app.tvufop.com.br/app.tvufop.com.br.test.js
new file mode 100644
index 000000000..3111ae9f4
--- /dev/null
+++ b/sites/app.tvufop.com.br/app.tvufop.com.br.test.js
@@ -0,0 +1,97 @@
+
+const { parser, url } = require('./app.tvufop.com.br.config.js')
+
+const dayjs = require('dayjs')
+
+const utc = require('dayjs/plugin/utc')
+
+const customParseFormat = require('dayjs/plugin/customParseFormat')
+
+
+
+dayjs.extend(utc)
+
+dayjs.extend(customParseFormat)
+
+
+
+const date = dayjs.utc('2026-03-28', 'YYYY-MM-DD').startOf('d')
+
+const channel = {
+
+ site_id: 'TVUFOP.br@HD',
+
+ xmltv_id: 'TVUFOP.br@HD',
+
+ lang: 'pt'
+
+}
+
+
+
+it('can generate valid url', () => {
+
+ expect(url({ channel, date })).toBe('https://app.tvufop.com.br/epg/epg_tvufop_web.xml')
+
+})
+
+
+
+it('can parse response', () => {
+
+ const content = `
+
+
+
+
+
+ TV UFOP
+
+
+
+
+
+ (FUTURA) CANAL DA HISTΓRIA - CARMEN MIRANDA
+
+ Clara e Neto usam uma mΓ‘quina do tempo.
+
+
+
+ Livre
+
+
+
+ `
+
+
+
+ const results = parser({ content, channel, date })
+
+
+
+ expect(results.length).toBe(1)
+
+ expect(results[0]).toMatchObject({
+
+ title: '(FUTURA) CANAL DA HISTΓRIA - CARMEN MIRANDA',
+
+ description: 'Clara e Neto usam uma mΓ‘quina do tempo.',
+
+ icon: 'https://app.tvufop.com.br/epg/CANAL.jpg',
+
+ rating: 'Livre'
+
+ })
+
+})
+
+
+
+it('can handle empty guide', () => {
+
+ const results = parser({ content: '', channel, date })
+
+ expect(results).toMatchObject([])
+
+})
+
diff --git a/sites/app.tvufop.com.br/readme.md b/sites/app.tvufop.com.br/readme.md
new file mode 100644
index 000000000..31886f122
--- /dev/null
+++ b/sites/app.tvufop.com.br/readme.md
@@ -0,0 +1,17 @@
+# app.tvufop.com.br
+
+XMLTV source for TV UFOP:
+
+https://app.tvufop.com.br/epg/epg_tvufop_web.xml
+
+### Download the guide
+
+Run:
+
+npm run grab --- --site=app.tvufop.com.br
+
+### Test
+
+Run:
+
+npm test --- app.tvufop.com.br