Merge pull request #3086 from iptv-org/update-tvguide.com

Update tvguide.com
This commit is contained in:
PopeyeTheSai10r
2026-04-20 12:03:25 -07:00
committed by GitHub
2 changed files with 23 additions and 68 deletions

View File

@@ -11,28 +11,11 @@ const providerId = '9100001138'
const maxDuration = 240 const maxDuration = 240
const segments = 1440 / maxDuration const segments = 1440 / maxDuration
const headers = { const headers = {
'referer': 'https://www.tvguide.com/', referer: 'https://www.tvguide.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36', 'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'
} }
const east_channels = [
'5StarMax', 'ABC Network Feed', 'ActionMax', 'A&E', 'AMC', 'Animal Planet', 'BBC America',
'BET', 'BET Her', 'Bravo', 'Cartoon Network', 'CBS National', 'Cinemax', 'CMT', 'Comedy Central',
'Discovery', 'Disney', 'Disney Junior', 'Disney XD', 'E!', 'Flix', 'Food Network', 'FOX', 'Freeform',
'Fuse HD', 'FX', 'FXX', 'FYI', 'Game Show Network', 'Hallmark', 'Hallmark Mystery', 'HBO 2',
'HBO Comedy', 'HBO', 'HBO Family', 'HBO Signature', 'HBO Zone', 'HGTV', 'History', 'IFC',
'Investigation Discovery', 'ION', 'Lifetime', 'LMN', 'LOGO', 'MAGNOLIA Network', 'MGM+ Hits HD',
'MoreMax', 'MovieMax', 'MTV2', 'MTV', 'National Geographic', 'National Geographic Wild', 'NBC National',
'Nickelodeon', 'Nick Jr.', 'Nicktoons', 'OuterMax', 'OWN', 'Oxygen', 'Paramount Network', 'PBS HD',
'Pop Network', 'SHOWTIME 2', 'Paramount+ with Showtime', 'SHOWTIME EXTREME', 'SHOWTIME FAMILY ZONE',
'SHOWTIME NEXT', 'SHOWTIME SHOWCASE', 'SHOWTIME WOMEN', 'SHOxBET', 'Smithsonian', 'STARZ Cinema',
'STARZ Comedy', 'STARZ', 'STARZ Edge', 'STARZ ENCORE Action', 'STARZ ENCORE Black',
'STARZ ENCORE Classic', 'STARZ ENCORE', 'STARZ ENCORE Family', 'STARZ ENCORE Suspense',
'STARZ ENCORE Westerns', 'STARZ InBlack', 'STARZ Kids & Family', 'Sundance TV', 'Syfy', 'tbs',
'Turner Classic Movies', 'TeenNick', 'Telemundo', 'The Movie', 'The Movie Xtra', 'ThrillerMax', 'TLC',
'TNT', 'Travel', 'truTV', 'TV Land', 'Universal Kids', 'USA', 'VH1', 'WE tv', 'Univision'
]
module.exports = { module.exports = {
site: 'tvguide.com', site: 'tvguide.com',
days: 2, days: 2,
@@ -48,21 +31,16 @@ module.exports = {
}, },
async url({ date, segment = 1 }) { async url({ date, segment = 1 }) {
const params = [] const params = []
if (module.exports.apiKey === undefined) {
module.exports.apiKey = await module.exports.fetchApiKey()
debug('Got api key', module.exports.apiKey)
}
if (date) { if (date) {
if (segment > 1) { if (segment > 1) {
date = date.add((segment - 1) * maxDuration, 'm') date = date.add((segment - 1) * maxDuration, 'm')
} }
params.push(`start=${date.unix()}`, `duration=${maxDuration}`) params.push(`start=${date.unix()}`, `duration=${maxDuration}`)
} }
params.push(`apiKey=${module.exports.apiKey}`)
return date ? return date
`https://backend.tvguide.com/tvschedules/tvguide/${providerId}/web?${params.join('&')}` : ? `https://backend.tvguide.com/tvschedules/tvguide/${providerId}/web?${params.join('&')}`
`https://backend.tvguide.com/tvschedules/tvguide/serviceprovider/${providerId}/sources/web?${params.join('&')}` : `https://backend.tvguide.com/tvschedules/tvguide/serviceprovider/${providerId}/sources/web?${params.join('&')}`
}, },
async parser({ content, date, channel, fetchSegments = true }) { async parser({ content, date, channel, fetchSegments = true }) {
const programs = [] const programs = []
@@ -75,9 +53,11 @@ module.exports = {
data.data.items data.data.items
.filter(i => i.channel.sourceId.toString() === channel.site_id) .filter(i => i.channel.sourceId.toString() === channel.site_id)
.forEach(i => { .forEach(i => {
result.push(...i.programSchedules.map(p => { result.push(
return { i: p, url: p.programDetails } ...i.programSchedules.map(p => {
})) return { i: p, url: p.programDetails }
})
)
}) })
} }
@@ -109,7 +89,7 @@ module.exports = {
rating: item.rating ? { system: 'MPA', value: item.rating } : null, rating: item.rating ? { system: 'MPA', value: item.rating } : null,
categories: Array.isArray(item.genres) ? item.genres.map(g => g.name) : null, categories: Array.isArray(item.genres) ? item.genres.map(g => g.name) : null,
start: dayjs.unix(item.startTime || queue.i.startTime), start: dayjs.unix(item.startTime || queue.i.startTime),
stop: dayjs.unix(item.endTime || queue.i.endTime), stop: dayjs.unix(item.endTime || queue.i.endTime)
}) })
} catch (err) { } catch (err) {
debug(`Failed to fetch program details ${queue.url}: ${err.message}`) debug(`Failed to fetch program details ${queue.url}: ${err.message}`)
@@ -120,33 +100,18 @@ module.exports = {
async channels() { async channels() {
const channels = [] const channels = []
try { try {
const data = await axios const data = await axios.get(await this.url({}), { headers }).then(r => r.data)
.get(await this.url({}), { headers })
.then(r => r.data)
data.data.items.forEach(item => { data.data.items.forEach(item => {
const finalName = item.fullName.replace(/Channel|Schedule/g, '').trim() const name = item.fullName.replace(/Channel|Schedule/g, '').trim()
const isEast = east_channels.some(name => name.toLowerCase().includes(finalName.toLowerCase()))
channels.push({ channels.push({
lang: 'en', lang: 'en',
site_id: item.sourceId, site_id: item.sourceId,
xmltv_id: finalName.replaceAll(/[ '&]/g, '') + '.us' + (isEast ? '@East' : ''), name
name: finalName
}) })
}) })
} catch (err) { } catch (err) {
console.error('Failed to fetch channels:', err.message) console.error('Failed to fetch channels:', err.message)
} }
return channels return channels
},
async fetchApiKey() {
try {
const data = await axios
.get('https://www.tvguide.com/listings/')
.then(r => r.data)
return data ? data.match(/apiKey=([a-zA-Z0-9]+)&/)[1] : null
} catch (err) {
console.error('Failed to fetch API key:', err.message)
return null
}
} }
} }

View File

@@ -17,29 +17,19 @@ const channel = {
} }
it('can generate valid url', async () => { it('can generate valid url', async () => {
axios.get.mockImplementation(url => {
if (url === 'https://www.tvguide.com/listings/') {
return Promise.resolve({
data: fs.readFileSync(path.join(__dirname, '__data__', 'content.html'), 'utf8')
})
}
throw new Error(`Unexpected URL: ${url}`)
})
const result = await url({ date }) const result = await url({ date })
expect(result).toBe( expect(result).toBe(
'https://backend.tvguide.com/tvschedules/tvguide/9100001138/web?start=1753747200&duration=240&apiKey=DI9elXhZ3bU6ujsA2gXEKOANyncXGUGc' 'https://backend.tvguide.com/tvschedules/tvguide/9100001138/web?start=1753747200&duration=240'
) )
}) })
it('can parse response', async () => { it('can parse response', async () => {
const content = JSON.parse(fs.readFileSync(path.join(__dirname, '__data__', 'content.json'), 'utf-8')) const content = JSON.parse(
fs.readFileSync(path.join(__dirname, '__data__', 'content.json'), 'utf-8')
)
axios.get.mockImplementation(url => { axios.get.mockImplementation(url => {
if ( if (url === 'https://backend.tvguide.com/tvschedules/tvguide/programdetails/9000058285/web') {
url ===
'https://backend.tvguide.com/tvschedules/tvguide/programdetails/9000058285/web'
) {
return Promise.resolve({ return Promise.resolve({
data: JSON.parse(fs.readFileSync(path.join(__dirname, '__data__', 'program.json'))) data: JSON.parse(fs.readFileSync(path.join(__dirname, '__data__', 'program.json')))
}) })
@@ -61,10 +51,10 @@ it('can parse response', async () => {
title: 'Secrets of the Zoo: North Carolina', title: 'Secrets of the Zoo: North Carolina',
sub_title: 'Chimp Off the Old Block', sub_title: 'Chimp Off the Old Block',
description: description:
'Chimps living at the North Carolina Zoo, a zoo located in the center of North Carolina that serves as the world\'s largest natural habitat zoo, as well as one of two state-supported zoos, are cared for', "Chimps living at the North Carolina Zoo, a zoo located in the center of North Carolina that serves as the world's largest natural habitat zoo, as well as one of two state-supported zoos, are cared for",
categories: ['Reality'], categories: ['Reality'],
season: 1, season: 1,
episode: 1, episode: 1
}) })
}) })