epg-grabber/src/utils.js

247 lines
7.5 KiB
JavaScript
Raw Normal View History

2021-03-13 13:11:33 +01:00
const fs = require('fs')
const path = require('path')
const axios = require('axios').default
2021-03-13 13:11:33 +01:00
const axiosCookieJarSupport = require('axios-cookiejar-support').default
const tough = require('tough-cookie')
const convert = require('xml-js')
const merge = require('lodash.merge')
2021-03-13 13:11:33 +01:00
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
axiosCookieJarSupport(axios)
const utils = {}
2021-08-21 18:11:01 +02:00
const defaultUserAgent =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 Edg/79.0.309.71'
2021-03-13 14:17:50 +01:00
2021-03-13 13:11:33 +01:00
utils.loadConfig = function (file) {
if (!file) throw new Error('Path to [site].config.js is missing')
console.log(`Loading '${file}'...`)
2021-03-13 14:17:50 +01:00
const configPath = path.resolve(file)
2021-03-13 13:11:33 +01:00
const config = require(configPath)
2021-03-13 14:42:18 +01:00
if (!config.site) throw new Error("The required 'site' property is missing")
if (!config.channels) throw new Error("The required 'channels' property is missing")
if (!config.url) throw new Error("The required 'url' property is missing")
if (typeof config.url !== 'function' && typeof config.url !== 'string')
throw new Error("The 'url' property should return the function or string")
2021-03-13 14:42:18 +01:00
if (!config.parser) throw new Error("The required 'parser' function is missing")
if (typeof config.parser !== 'function')
throw new Error("The 'parser' property should return the function")
2021-03-19 19:26:32 +01:00
if (config.logo && typeof config.logo !== 'function')
throw new Error("The 'logo' property should return the function")
2021-03-13 14:42:18 +01:00
2021-03-19 20:22:58 +01:00
config.channels = path.join(path.dirname(file), config.channels)
const defaultConfig = {
days: 1,
lang: 'en',
delay: 3000,
output: 'guide.xml',
request: {
method: 'GET',
2021-04-17 12:28:53 +02:00
maxContentLength: 5 * 1024 * 1024,
2021-03-21 19:16:27 +01:00
timeout: 5000,
withCredentials: true,
jar: new tough.CookieJar(),
responseType: 'arraybuffer'
}
}
return merge(defaultConfig, config)
2021-03-13 13:11:33 +01:00
}
2021-03-19 20:22:58 +01:00
utils.parseChannels = function (filename) {
if (!filename) throw new Error('Path to [site].channels.xml is missing')
console.log(`Loading '${filename}'...`)
2021-03-13 13:11:33 +01:00
2021-03-19 20:22:58 +01:00
const xml = fs.readFileSync(path.resolve(filename), { encoding: 'utf-8' })
2021-03-13 13:11:33 +01:00
const result = convert.xml2js(xml)
const site = result.elements.find(el => el.name === 'site')
const channels = site.elements.find(el => el.name === 'channels')
return channels.elements
.filter(el => el.name === 'channel')
.map(el => {
const channel = el.attributes
if (!el.elements) throw new Error(`Channel '${channel.xmltv_id}' has no valid name`)
2021-03-13 13:11:33 +01:00
channel.name = el.elements.find(el => el.type === 'text').text
channel.site = channel.site || site.attributes.site
return channel
})
}
utils.sleep = function (ms) {
return function (x) {
return new Promise(resolve => setTimeout(() => resolve(x), ms))
}
}
utils.escapeString = function (string, defaultValue = '') {
if (!string) return defaultValue
2021-03-20 15:03:17 +01:00
const regex = new RegExp(
'((?:[\0-\x08\x0B\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))|([\\x7F-\\x84]|[\\x86-\\x9F]|[\\uFDD0-\\uFDEF]|(?:\\uD83F[\\uDFFE\\uDFFF])|(?:\\uD87F[\\uDF' +
'FE\\uDFFF])|(?:\\uD8BF[\\uDFFE\\uDFFF])|(?:\\uD8FF[\\uDFFE\\uDFFF])|(?:\\uD93F[\\uDFFE\\uD' +
'FFF])|(?:\\uD97F[\\uDFFE\\uDFFF])|(?:\\uD9BF[\\uDFFE\\uDFFF])|(?:\\uD9FF[\\uDFFE\\uDFFF])' +
'|(?:\\uDA3F[\\uDFFE\\uDFFF])|(?:\\uDA7F[\\uDFFE\\uDFFF])|(?:\\uDABF[\\uDFFE\\uDFFF])|(?:\\' +
'uDAFF[\\uDFFE\\uDFFF])|(?:\\uDB3F[\\uDFFE\\uDFFF])|(?:\\uDB7F[\\uDFFE\\uDFFF])|(?:\\uDBBF' +
'[\\uDFFE\\uDFFF])|(?:\\uDBFF[\\uDFFE\\uDFFF])(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\' +
'uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|' +
'(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))',
'g'
)
string = String(string || '').replace(regex, '')
2021-03-13 13:11:33 +01:00
return string
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;')
.replace(/\n|\r/g, ' ')
.replace(/ +/g, ' ')
.trim()
}
utils.convertToXMLTV = function ({ config, channels, programs }) {
let output = `<?xml version="1.0" encoding="UTF-8" ?><tv>\r\n`
for (let channel of channels) {
const id = this.escapeString(channel['xmltv_id'])
const displayName = this.escapeString(channel.name)
2021-03-19 19:26:32 +01:00
output += `<channel id="${id}"><display-name>${displayName}</display-name>`
if (channel.logo) {
2021-03-28 03:00:21 +02:00
const logo = this.escapeString(channel.logo)
output += `<icon src="${logo}"/>`
2021-03-19 19:26:32 +01:00
}
output += `</channel>\r\n`
2021-03-13 13:11:33 +01:00
}
for (let program of programs) {
if (!program) continue
const channel = this.escapeString(program.channel)
const title = this.escapeString(program.title)
const description = this.escapeString(program.description)
const category = this.escapeString(program.category)
const start = program.start ? dayjs.utc(program.start).format('YYYYMMDDHHmmss ZZ') : ''
const stop = program.stop ? dayjs.utc(program.stop).format('YYYYMMDDHHmmss ZZ') : ''
const lang = program.lang || config.lang
const icon = this.escapeString(program.icon)
2021-03-13 13:11:33 +01:00
if (start && title) {
output += `<programme start="${start}"`
if (stop) {
output += ` stop="${stop}"`
}
output += ` channel="${channel}"><title lang="${lang}">${title}</title>`
if (description) {
output += `<desc lang="${lang}">${description}</desc>`
}
if (category) {
output += `<category lang="${lang}">${category}</category>`
}
if (icon) {
output += `<icon src="${icon}"/>`
}
2021-03-13 13:11:33 +01:00
output += '</programme>\r\n'
}
}
output += '</tv>'
return output
}
2021-03-20 11:55:09 +01:00
utils.parsePrograms = function ({ response, item, config }) {
const options = merge(item, config, {
2021-03-31 20:47:53 +02:00
content: response.data.toString(),
buffer: response.data
2021-03-20 11:55:09 +01:00
})
2021-04-17 12:44:42 +02:00
const programs = config.parser(options)
if (!Array.isArray(programs)) {
throw new Error('Parser should return an array')
}
return programs
2021-03-20 11:55:09 +01:00
.filter(i => i)
.map(p => {
p.channel = item.channel.xmltv_id
return p
})
}
2021-03-13 14:17:50 +01:00
utils.writeToFile = function (filename, data) {
const dir = path.resolve(path.dirname(filename))
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
2021-03-13 13:11:33 +01:00
}
2021-03-13 13:49:04 +01:00
fs.writeFileSync(path.resolve(filename), data)
2021-03-13 13:11:33 +01:00
}
2021-08-21 18:11:01 +02:00
utils.buildRequest = async function (item, config) {
2021-04-02 21:00:33 +02:00
const request = { ...config.request }
2021-08-21 18:11:01 +02:00
const headers = await utils.getRequestHeaders(item, config)
request.headers = { 'User-Agent': defaultUserAgent, ...headers }
request.url = await utils.getRequestUrl(item, config)
request.data = await utils.getRequestData(item, config)
2021-08-21 18:11:01 +02:00
return request
}
utils.fetchData = function (request) {
2021-04-02 21:00:33 +02:00
return axios(request)
2021-03-13 13:11:33 +01:00
}
2021-08-21 18:11:01 +02:00
utils.getRequestHeaders = async function (item, config) {
if (typeof config.request.headers === 'function') {
const headers = config.request.headers(item)
if (typeof headers === 'Promise') {
return await headers
}
return headers
}
return config.request.headers
}
utils.getRequestData = async function (item, config) {
if (typeof config.request.data === 'function') {
const data = config.request.data(item)
if (typeof data === 'Promise') {
return await data
}
return data
}
return config.request.data
}
utils.getRequestUrl = async function (item, config) {
if (typeof config.url === 'function') {
const url = config.url(item)
if (typeof url === 'Promise') {
return await url
}
return url
}
return config.url
}
2021-03-13 13:11:33 +01:00
utils.getUTCDate = function () {
return dayjs.utc()
}
module.exports = utils