mirror of
https://github.com/iptv-org/epg
synced 2026-09-06 12:00:09 -04:00
150 lines
4.3 KiB
JavaScript
150 lines
4.3 KiB
JavaScript
const fs = require('fs')
|
|
const path = require('path')
|
|
const https = require('https')
|
|
const axios = require('axios')
|
|
const dayjs = require('dayjs')
|
|
const utc = require('dayjs/plugin/utc')
|
|
|
|
dayjs.extend(utc)
|
|
|
|
const API_ENDPOINT = 'https://api.toonamiaftermath.com'
|
|
|
|
// Let's Encrypt YR1 and YR2 will be expired on Sep 2 23:59:59 2028 GMT
|
|
const certfile = path.join(__dirname, '__data__', 'certificate.pem')
|
|
const httpsAgent = new https.Agent({ ca: fs.readFileSync(certfile) })
|
|
const caches = {}
|
|
|
|
module.exports = {
|
|
site: 'toonamiaftermath.com',
|
|
days: 3,
|
|
request: {
|
|
cache: {
|
|
ttl: 24 * 60 * 60 * 1000 // 1 day
|
|
},
|
|
httpsAgent
|
|
},
|
|
async url({ channel, date }) {
|
|
return await getUrl(channel, date)
|
|
},
|
|
async parser({ channel, date, content }) {
|
|
const programs = []
|
|
const delay = parseInt(channel.site_id.split('+')[1]) || 0
|
|
const cdate = date.startOf('d')
|
|
const items = parseItems(content)
|
|
// fetch other playlist URLs if exist
|
|
if (items.length && Array.isArray(date.urls)) {
|
|
for (const url of date.urls) {
|
|
if (caches[url] !== undefined) {
|
|
items.push(...caches[url])
|
|
} else {
|
|
const result = await axios.get(url, { httpsAgent })
|
|
.then(res => res.data)
|
|
.catch(console.error)
|
|
const newItems = parseItems(result)
|
|
caches[url] = newItems
|
|
items.push(...newItems)
|
|
}
|
|
}
|
|
}
|
|
items.forEach(item => {
|
|
const start = getDate(item.startDate, delay)
|
|
const stop = getDate(item.endDate, delay)
|
|
if (isDate(start, cdate) || isDate(stop, cdate, true)) {
|
|
programs.push({
|
|
title: item.name,
|
|
description: item.info?.fullname,
|
|
sub_title: item.info?.episode,
|
|
image: item.info?.image,
|
|
start,
|
|
stop
|
|
})
|
|
}
|
|
})
|
|
|
|
return programs
|
|
},
|
|
async channels() {
|
|
const channels = []
|
|
const result = await axios.get('https://www.toonamiaftermath.com/bundle.js')
|
|
.then(res => res.data)
|
|
.catch(err => console.error(err.toString()))
|
|
|
|
const matches = result.match(/channels:(\[.*?\]),siteConfigs:/)
|
|
if (matches[1]) {
|
|
const vm = require('vm')
|
|
const items = vm.runInNewContext(`const o=${matches[1]}; o`)
|
|
if (Array.isArray(items)) {
|
|
channels.push(...items
|
|
.filter(item => item.scheduleName)
|
|
.map(item => ({
|
|
lang: 'en',
|
|
site_id: `${item.scheduleName}${item.streamDelay ? '+' + item.streamDelay : ''}`,
|
|
name: item.name
|
|
}))
|
|
)
|
|
}
|
|
}
|
|
|
|
return channels
|
|
}
|
|
}
|
|
|
|
async function getUrl(channel, date) {
|
|
const scheduleName = channel.site_id.split('+')[0]
|
|
const url = new URL(`${API_ENDPOINT}/playlists?scheduleName=${
|
|
scheduleName
|
|
}&startDate=${
|
|
date.startOf('d').toJSON()
|
|
}&thisWeek=true&weekStartDay=monday`)
|
|
const playlists = await axios.get(url.toString(), { httpsAgent })
|
|
.then(res => res.data)
|
|
.catch(err => console.error(err.toString()))
|
|
|
|
const urls = []
|
|
if (Array.isArray(playlists)) {
|
|
const dt = date.startOf('d')
|
|
playlists
|
|
.filter(p => isDate(getDate(p.startDate), dt) || isDate(getDate(p.endDate), dt, true))
|
|
.forEach(p => {
|
|
urls.push(`${API_ENDPOINT}/playlist?id=${p._id}&addInfo=true`)
|
|
})
|
|
}
|
|
if (!urls.length) {
|
|
throw new Error(`Unable to get schedule URL for ${scheduleName}!`)
|
|
}
|
|
const playlistUrl = urls.shift()
|
|
if (urls.length) {
|
|
// pass next playlist URLs in date object
|
|
date.urls = urls
|
|
}
|
|
|
|
return playlistUrl
|
|
}
|
|
|
|
function getDate(dt, delay = 0) {
|
|
return dayjs.utc(dt).add(delay, 'm')
|
|
}
|
|
|
|
function isDate(dt, ref, end) {
|
|
return dt.isSame(ref, 'd') && (end ? dt > ref : true)
|
|
}
|
|
|
|
function parseItems(content) {
|
|
if (!content) return []
|
|
if (typeof content === 'string' || Buffer.isBuffer(content)) {
|
|
content = JSON.parse(content)
|
|
}
|
|
if (!content || !content.playlist) return []
|
|
|
|
return content.playlist.blocks.reduce((acc, curr) => {
|
|
// short media type has no title, use block title instead
|
|
curr.mediaList.forEach(media => {
|
|
if (media.name === undefined) {
|
|
media.name = curr.name
|
|
}
|
|
})
|
|
|
|
return acc.concat(curr.mediaList)
|
|
}, [])
|
|
}
|