mirror of
https://github.com/iptv-org/epg
synced 2026-07-11 01:27:10 -04:00
2adc10b567
Signed-off-by: Toha <tohenk@yahoo.com>
234 lines
6.6 KiB
JavaScript
234 lines
6.6 KiB
JavaScript
const axios = require('axios')
|
|
const dayjs = require('dayjs')
|
|
const utc = require('dayjs/plugin/utc')
|
|
const timezone = require('dayjs/plugin/timezone')
|
|
const crypto = require('crypto')
|
|
|
|
dayjs.extend(utc)
|
|
dayjs.extend(timezone)
|
|
|
|
/**
|
|
* OSN.COM note:
|
|
* - API allows only at max 5 channels, otherwise returns 406
|
|
*/
|
|
|
|
// https://www.osn.com/_next/static/chunks/87cf1e375968671c.js
|
|
|
|
const cipherName = 'aes-256-cbc'
|
|
const cipherKey = Buffer.from('65a04b9b5591f27c837fac433274b494403e0eec5b43698060c28e1162d4460f', 'hex')
|
|
const cipherIv = Buffer.from('b7cc0a48d6d023bc1a2a670953ec5622', 'hex')
|
|
|
|
const tz = dayjs.tz.guess()
|
|
const oneDayMs = 864e5
|
|
const headers = {}
|
|
const caches = {}
|
|
let allChannels
|
|
|
|
module.exports = {
|
|
site: 'osn.com',
|
|
days: 2,
|
|
async url({ date }) {
|
|
return (await getUrlData(date)).url
|
|
},
|
|
request: {
|
|
cache: {
|
|
ttl: 24 * 60 * 60 * 1000 // 1 day
|
|
},
|
|
async headers({ date }) {
|
|
return await getHeaders(date)
|
|
}
|
|
},
|
|
async parser({ content, channel, date }) {
|
|
const programs = []
|
|
content = getDecryptedData(content)
|
|
if (Array.isArray(content?.entries)) {
|
|
// get remaining segments entries
|
|
const cacheId = date.format('YYYYMMDD')
|
|
if (caches[cacheId] === undefined) {
|
|
caches[cacheId] = []
|
|
const segments = await getUrlData(date, false)
|
|
while (segments.length) {
|
|
const segment = segments.shift()
|
|
const result = await axios
|
|
.get(segment.url, { headers: await getHeaders(segment.data) })
|
|
.then(res => getDecryptedData(res.data))
|
|
.catch(err => console.error(`${segment.url}: ${err.message}!`))
|
|
if (Array.isArray(result?.entries)) {
|
|
caches[cacheId].push(...result.entries)
|
|
}
|
|
}
|
|
// add 5s delay to avoid 406
|
|
await new Promise(resolve => setTimeout(resolve, 5000))
|
|
}
|
|
// add entries from cache
|
|
if (Array.isArray(caches[cacheId])) {
|
|
content.entries.push(...caches[cacheId])
|
|
}
|
|
content.entries
|
|
.filter(entry => entry.guid == channel.site_id)
|
|
.forEach(entry => {
|
|
if (Array.isArray(entry.listings)) {
|
|
const listings = entry.listings
|
|
.map(listing => {
|
|
const res = {
|
|
title: dotProp(listing, `program.titleLocalized.${channel.lang}`),
|
|
subTitle: dotProp(listing, `program.osnprogram$seriesTitleLocalized.${channel.lang}`),
|
|
description: dotProp(listing, `program.descriptionLocalized.${channel.lang}`),
|
|
start: dayjs.tz(listing.startTime, tz),
|
|
stop: dayjs.tz(listing.endTime, tz),
|
|
categories: (dotProp(listing, 'program.tags') || [])
|
|
.map(tag => dotProp(tag, `titleLocalized.${channel.lang}`)),
|
|
image: dotProp(listing, `program.thumbnails.landscapeTitleImage-${channel.lang}.url`),
|
|
season: dotProp(listing, 'program.tvSeasonNumber'),
|
|
episode: dotProp(listing, 'program.tvSeasonEpisodeNumber'),
|
|
date: dotProp(listing, 'program.year')
|
|
}
|
|
if (res.subTitle) {
|
|
const subTitle = res.title
|
|
res.title = res.subTitle
|
|
res.subTitle = subTitle
|
|
}
|
|
if (res.date) {
|
|
res.date = `${res.date}`
|
|
}
|
|
return res
|
|
})
|
|
programs.push(...listings)
|
|
}
|
|
})
|
|
}
|
|
|
|
return programs
|
|
},
|
|
async channels({ lang = 'ar' }) {
|
|
const channels = []
|
|
const items = await fetchChannels()
|
|
if (Array.isArray(items)) {
|
|
channels.push(...items.map(item => ({
|
|
lang,
|
|
site_id: item.guid,
|
|
name: item.title
|
|
})))
|
|
}
|
|
|
|
return channels
|
|
}
|
|
}
|
|
|
|
async function fetchChannels() {
|
|
if (allChannels === undefined) {
|
|
const result = await axios
|
|
.get('https://www.osn.com/apidata/channels?platform=Android', { headers: await getHeaders() })
|
|
.then(res => getDecryptedData(res.data))
|
|
.catch(console.error)
|
|
|
|
if (Array.isArray(result)) {
|
|
allChannels = result
|
|
}
|
|
}
|
|
|
|
return allChannels
|
|
}
|
|
|
|
async function getHeaders(data) {
|
|
if (dayjs.isDayjs(data)) {
|
|
data = (await getUrlData(data)).data
|
|
}
|
|
const res = { ...headers }
|
|
if (data) {
|
|
res['X-Encrypted-Data'] = getEncryptedData(data)
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
async function getUrlData(date, first = true) {
|
|
const parts = []
|
|
const startOfDay = date.tz(tz).startOf('d')
|
|
const endOfDay = startOfDay.add(oneDayMs, 'ms')
|
|
const startStop = {
|
|
startTime: startOfDay.valueOf(),
|
|
endTime: endOfDay.valueOf()
|
|
}
|
|
await fetchChannels()
|
|
const segments = await getUrlSegments()
|
|
let i = 0
|
|
for (const v of segments) {
|
|
const data = {
|
|
url: `https://www.osn.com/apidata/tv-schedule-timeline?t=batch${++i}-time${
|
|
startStop.startTime
|
|
}-${
|
|
startStop.endTime
|
|
}-boxAndroid`,
|
|
data: {
|
|
channelGuid: v.join('|'),
|
|
...startStop
|
|
}
|
|
}
|
|
if (first) {
|
|
return data
|
|
} else if (i === 1) {
|
|
continue
|
|
}
|
|
parts.push(data)
|
|
}
|
|
|
|
return parts
|
|
}
|
|
|
|
async function getUrlSegments() {
|
|
await fetchChannels()
|
|
const segments = []
|
|
const _channels = [...allChannels.map(item => item.guid)]
|
|
while (_channels.length) {
|
|
segments.push(_channels.splice(0, 5))
|
|
}
|
|
return segments
|
|
}
|
|
|
|
function getEncryptedData(data) {
|
|
return encrypt(JSON.stringify(data))
|
|
}
|
|
|
|
function getDecryptedData(data) {
|
|
if (typeof data === 'string' || Buffer.isBuffer(data)) {
|
|
data = JSON.parse(data)
|
|
}
|
|
if (data?.encrypted) {
|
|
data = JSON.parse(decrypt(data.encrypted))
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
function encrypt(data, encoding = 'utf8') {
|
|
const cipher = crypto.createCipheriv(cipherName, cipherKey, cipherIv)
|
|
const encrypted = cipher.update(data, encoding, 'base64')
|
|
|
|
return (encrypted + cipher.final('base64'))
|
|
}
|
|
|
|
function decrypt(data, encoding = 'utf8') {
|
|
const decipher = crypto.createDecipheriv(cipherName, cipherKey, cipherIv)
|
|
const decrypted = decipher.update(data, 'base64', encoding)
|
|
|
|
return (decrypted + decipher.final(encoding))
|
|
}
|
|
|
|
function dotProp(o, prop) {
|
|
if (typeof o === 'object') {
|
|
const props = prop.split('.')
|
|
while (props.length) {
|
|
const k = props.shift()
|
|
if (o[k] !== undefined) {
|
|
o = o[k]
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
if (props.length === 0) {
|
|
return o
|
|
}
|
|
}
|
|
}
|