mirror of
https://github.com/iptv-org/epg.git
synced 2025-05-09 16:40:07 -04:00
295 lines
8.6 KiB
JavaScript
295 lines
8.6 KiB
JavaScript
const dayjs = require('dayjs')
|
|
const doFetch = require('@ntlab/sfetch')
|
|
const debug = require('debug')('site:tv.yandex.ru')
|
|
|
|
doFetch.setDebugger(debug).setMaxWorker(10)
|
|
|
|
// enable to fetch guide description but its take a longer time
|
|
const detailedGuide = true
|
|
|
|
// update this data by heading to https://tv.yandex.ru and change the values accordingly
|
|
const cookies = {
|
|
i: 'eIUfSP+/mzQWXcH+Cuz8o1vY+D2K8fhBd6Sj0xvbPZeO4l3cY+BvMp8fFIuM17l6UE1Z5+R2a18lP00ex9iYVJ+VT+c=',
|
|
spravka:
|
|
'dD0xNzM0MjA0NjM4O2k9MTI1LjE2NC4xNDkuMjAwO0Q9QTVCQ0IyOTI5RDQxNkU5NkEyOTcwMTNDMzZGMDAzNjRDNTFFNDM4QkE2Q0IyOTJDRjhCOTZDRDIzODdBQzk2MzRFRDc5QTk2Qjc2OEI1MUY5MTM5M0QzNkY3OEQ2OUY3OTUwNkQ3RjBCOEJGOEJDMjAwMTQ0RDUwRkFCMDNEQzJFMDI2OEI5OTk5OUJBNEFERUYwOEQ1MjUwQTE0QTI3RDU1MEQwM0U0O3U9MTczNDIwNDYzODUyNDYyNzg1NDtoPTIxNTc0ZTc2MDQ1ZjcwMDBkYmY0NTVkM2Q2ZWMyM2Y1',
|
|
yandexuid: '1197179041732383499',
|
|
yashr: '4682342911732383504',
|
|
yuidss: '1197179041732383499',
|
|
user_display: 824
|
|
}
|
|
const headers = {
|
|
'User-Agent':
|
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 OPR/114.0.0.0'
|
|
}
|
|
const caches = {}
|
|
|
|
module.exports = {
|
|
site: 'tv.yandex.ru',
|
|
days: 2,
|
|
url({ date }) {
|
|
return getUrl(date)
|
|
},
|
|
request: {
|
|
cache: {
|
|
ttl: 3600000 // 1 hour
|
|
},
|
|
headers: getHeaders()
|
|
},
|
|
async parser({ content, date, channel }) {
|
|
const programs = []
|
|
const events = []
|
|
|
|
if (content && parseContent(content, date, true)) {
|
|
const cacheid = date.format('YYYY-MM-DD')
|
|
if (!caches[cacheid]) {
|
|
debug(`Please wait while fetching schedules for ${cacheid}`)
|
|
caches[cacheid] = await fetchSchedules({ date, content })
|
|
}
|
|
if (detailedGuide) {
|
|
await fetchPrograms({ schedules: caches[cacheid], date, channel })
|
|
}
|
|
caches[cacheid].forEach(schedule => {
|
|
schedule.events
|
|
.filter(
|
|
event => event.channelFamilyId == channel.site_id && date.isSame(event.start, 'day')
|
|
)
|
|
.forEach(event => {
|
|
if (events.indexOf(event.id) < 0) {
|
|
events.push(event.id)
|
|
programs.push({
|
|
title: event.title,
|
|
description: event.program.description,
|
|
category: event.program.type.name,
|
|
start: dayjs(event.start),
|
|
stop: dayjs(event.finish)
|
|
})
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
return programs
|
|
},
|
|
async channels() {
|
|
const channels = []
|
|
const included = []
|
|
const schedules = await fetchSchedules({ date: dayjs() })
|
|
schedules.forEach(schedule => {
|
|
if (schedule.channel && included.indexOf(schedule.channel.familyId) < 0) {
|
|
included.push(schedule.channel.familyId)
|
|
channels.push({
|
|
lang: 'ru',
|
|
site_id: schedule.channel.familyId.toString(),
|
|
name: schedule.channel.title
|
|
})
|
|
}
|
|
})
|
|
|
|
return channels
|
|
}
|
|
}
|
|
|
|
async function fetchSchedules({ date, content = null }) {
|
|
const schedules = []
|
|
const queues = []
|
|
const fetches = []
|
|
const url = getUrl(date)
|
|
|
|
let mainApi
|
|
// parse content as schedules and add to queue if more requests is needed
|
|
const f = (src, res, headers) => {
|
|
if (src) {
|
|
fetches.push(src)
|
|
}
|
|
if (headers) {
|
|
parseCookies(headers)
|
|
}
|
|
const [q, s] = parseContent(res, date)
|
|
if (!mainApi) {
|
|
mainApi = true
|
|
if (caches.region) {
|
|
queues.push(getQueue(getUrl(date, caches.region), src))
|
|
}
|
|
}
|
|
for (const url of q) {
|
|
if (fetches.indexOf(url) < 0) {
|
|
queues.push(getQueue(url, src))
|
|
}
|
|
}
|
|
schedules.push(...s)
|
|
}
|
|
// is main html already fetched?
|
|
if (content) {
|
|
f(url, content)
|
|
} else {
|
|
queues.push(getQueue(url, 'https://tv.yandex.ru/'))
|
|
}
|
|
// fetch all queues
|
|
await doFetch(queues, f)
|
|
|
|
return schedules
|
|
}
|
|
|
|
async function fetchPrograms({ schedules, date, channel }) {
|
|
const queues = []
|
|
schedules
|
|
.filter(schedule => schedule.channel.familyId == channel.site_id)
|
|
.forEach(schedule => {
|
|
queues.push(
|
|
...schedule.events
|
|
.filter(event => date.isSame(event.start, 'day'))
|
|
.map(event => getQueue(getUrl(null, caches.region, null, event), 'https://tv.yandex.ru/'))
|
|
)
|
|
})
|
|
await doFetch(queues, (queue, res, headers) => {
|
|
if (headers) {
|
|
parseCookies(headers)
|
|
}
|
|
// is it a program?
|
|
if (res?.program) {
|
|
let updated = false
|
|
schedules.forEach(schedule => {
|
|
schedule.events.forEach(event => {
|
|
if (event.channelFamilyId === res.channelFamilyId && event.id === res.id) {
|
|
Object.assign(event, res)
|
|
updated = true
|
|
return true
|
|
}
|
|
})
|
|
if (updated) {
|
|
return true
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
function parseContent(content, date, checkOnly = false) {
|
|
const queues = []
|
|
const schedules = []
|
|
let valid = false
|
|
if (content) {
|
|
if (Buffer.isBuffer(content)) {
|
|
content = content.toString()
|
|
}
|
|
// got captcha, its look like our cookies has expired
|
|
if (
|
|
content?.type === 'captcha' ||
|
|
(typeof content === 'string' && content.match(/SmartCaptcha/))
|
|
) {
|
|
throw new Error('Got captcha, please goto https://tv.yandex.ru and update cookies!')
|
|
}
|
|
if (typeof content === 'object') {
|
|
let items
|
|
if (content.schedule) {
|
|
// fetch next request based on schedule map
|
|
if (Array.isArray(content.schedule.scheduleMap)) {
|
|
queues.push(...content.schedule.scheduleMap.map(m => getUrl(date, caches.region, m)))
|
|
}
|
|
// find some schedules?
|
|
if (Array.isArray(content.schedule.schedules)) {
|
|
items = content.schedule.schedules
|
|
}
|
|
}
|
|
// find another schedules?
|
|
if (Array.isArray(content.schedules)) {
|
|
items = content.schedules
|
|
}
|
|
// add programs
|
|
if (items && items.length) {
|
|
schedules.push(...getSchedules(items))
|
|
}
|
|
} else {
|
|
// prepare headers for next http request
|
|
const [, region] = content.match(/region: '(\d+)'/i) || [null, null]
|
|
const [, initialSk] = content.match(/window.__INITIAL_SK__ = (.*);/i) || [null, null]
|
|
const [, sessionId] = content.match(/window.__USER_SESSION_ID__ = "(.*)";/i) || [null, null]
|
|
const tvSk = initialSk ? JSON.parse(initialSk) : {}
|
|
if (region) {
|
|
caches.region = region
|
|
}
|
|
if (tvSk.key) {
|
|
headers['X-Tv-Sk'] = tvSk.key
|
|
}
|
|
if (sessionId) {
|
|
headers['X-User-Session-Id'] = sessionId
|
|
}
|
|
if (checkOnly && region && tvSk.key && sessionId) {
|
|
valid = true
|
|
}
|
|
}
|
|
}
|
|
|
|
return checkOnly ? valid : [queues, schedules]
|
|
}
|
|
|
|
function parseCookies(headers) {
|
|
if (Array.isArray(headers['set-cookie'])) {
|
|
headers['set-cookie'].forEach(cookie => {
|
|
const [key, value] = cookie.split('; ')[0].split('=')
|
|
if (cookies[key] !== value) {
|
|
cookies[key] = value
|
|
debug(`Update cookie ${key}=${value}`)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
function getSchedules(schedules) {
|
|
return schedules.filter(schedule => schedule.events.length)
|
|
}
|
|
|
|
function getHeaders(data = {}) {
|
|
return Object.assign(
|
|
{},
|
|
headers,
|
|
{
|
|
Cookie: Object.keys(cookies)
|
|
.map(cookie => `${cookie}=${cookies[cookie]}`)
|
|
.join('; ')
|
|
},
|
|
data
|
|
)
|
|
}
|
|
|
|
function getUrl(date, region = null, page = null, event = null) {
|
|
let url = 'https://tv.yandex.ru/'
|
|
if (region) {
|
|
url += `api/${region}`
|
|
}
|
|
if (page && page.id !== undefined) {
|
|
url += `${url.endsWith('/') ? '' : '/'}main/chunk?page=${page.id}`
|
|
}
|
|
if (event && event.id !== undefined) {
|
|
url += `${url.endsWith('/') ? '' : '/'}event?eventId=${event.id}&programCoId=`
|
|
}
|
|
if (date) {
|
|
url += `${url.indexOf('?') < 0 ? '?' : '&'}date=${date.format('YYYY-MM-DD')}${
|
|
!page ? '&grid=all' : ''
|
|
}&period=all-day`
|
|
}
|
|
if (page && page.id !== undefined && page.offset !== undefined) {
|
|
url += `${url.indexOf('?') < 0 ? '?' : '&'}offset=${page.offset}`
|
|
}
|
|
if (page && page.id !== undefined && page.limit !== undefined) {
|
|
url += `${url.indexOf('?') < 0 ? '?' : '&'}limit=${page.limit}`
|
|
}
|
|
return url
|
|
}
|
|
|
|
function getQueue(url, referer) {
|
|
const data = {
|
|
Origin: 'https://tv.yandex.ru'
|
|
}
|
|
if (referer) {
|
|
data['Referer'] = referer
|
|
}
|
|
if (url.indexOf('api') > 0) {
|
|
data['X-Requested-With'] = 'XMLHttpRequest'
|
|
}
|
|
const headers = getHeaders(data)
|
|
return {
|
|
url,
|
|
params: { headers }
|
|
}
|
|
}
|