iptv-playlist-parser
Version:
A basic IPTV playlist parser
154 lines (129 loc) • 4.26 kB
JavaScript
const { isURL: isValidUrl } = require('validator')
const isValidPath = require('is-valid-path')
const Parser = {}
Parser.parse = content => {
let playlist = {
header: {},
items: []
}
let lines = content.split('\n').map(parseLine)
let firstLine = lines.find(l => l.index === 0)
if (!firstLine || !/^#EXTM3U/.test(firstLine.raw)) throw new Error('Playlist is not valid')
playlist.header = parseHeader(firstLine)
let i = 0
const items = {}
for (let line of lines) {
if (line.index === 0) continue
const string = line.raw.toString().trim()
if (string.startsWith('#EXTINF:')) {
const EXTINF = string
items[i] = {
name: EXTINF.getName(),
tvg: {
id: EXTINF.getAttribute('tvg-id'),
name: EXTINF.getAttribute('tvg-name'),
logo: EXTINF.getAttribute('tvg-logo'),
url: EXTINF.getAttribute('tvg-url'),
rec: EXTINF.getAttribute('tvg-rec'),
shift: EXTINF.getAttribute('tvg-shift')
},
group: {
title: EXTINF.getAttribute('group-title')
},
http: {
referrer: '',
'user-agent': EXTINF.getAttribute('user-agent')
},
url: undefined,
raw: line.raw,
line: line.index + 1,
catchup: {
type: EXTINF.getAttribute('catchup'),
days: EXTINF.getAttribute('catchup-days'),
source: EXTINF.getAttribute('catchup-source')
},
timeshift: EXTINF.getAttribute('timeshift'),
lang: EXTINF.getAttribute('lang')
}
} else if (string.startsWith('#EXTVLCOPT:')) {
if (!items[i]) continue
const EXTVLCOPT = string
items[i].http.referrer = EXTVLCOPT.getOption('http-referrer') || items[i].http.referrer
items[i].http['user-agent'] =
EXTVLCOPT.getOption('http-user-agent') || items[i].http['user-agent']
items[i].raw += `\r\n${line.raw}`
} else if (string.startsWith('#EXTGRP:')) {
if (!items[i]) continue
const EXTGRP = string
items[i].group.title = EXTGRP.getValue() || items[i].group.title
items[i].raw += `\r\n${line.raw}`
} else {
if (!items[i]) continue
const url = string.getURL()
const user_agent = string.getParameter('user-agent')
const referrer = string.getParameter('referer')
if (url && (isValidPath(url) || isValidUrl(url))) {
items[i].url = url
items[i].http['user-agent'] = user_agent || items[i].http['user-agent']
items[i].http.referrer = referrer || items[i].http.referrer
items[i].raw += `\r\n${line.raw}`
i++
} else {
if (!items[i]) continue
items[i].raw += `\r\n${line.raw}`
}
}
}
playlist.items = Object.values(items)
return playlist
}
function parseLine(line, index) {
return {
index,
raw: line
}
}
function parseHeader(line) {
const supportedAttrs = ['x-tvg-url', 'url-tvg']
let attrs = {}
for (let attrName of supportedAttrs) {
const tvgUrl = line.raw.getAttribute(attrName)
if (tvgUrl) {
attrs[attrName] = tvgUrl
}
}
return {
attrs,
raw: line.raw
}
}
String.prototype.getName = function () {
let info = this.replace(/\="(.*?)"/g, '')
let parts = info.split(/,(.*)/)
return parts[1] || ''
}
String.prototype.getAttribute = function (name) {
let regex = new RegExp(name + '="(.*?)"', 'gi')
let match = regex.exec(this)
return match && match[1] ? match[1] : ''
}
String.prototype.getOption = function (name) {
let regex = new RegExp(':' + name + '=(.*)', 'gi')
let match = regex.exec(this)
return match && match[1] && typeof match[1] === 'string' ? match[1].replace(/\"/g, '') : ''
}
String.prototype.getValue = function (name) {
let regex = new RegExp(':(.*)', 'gi')
let match = regex.exec(this)
return match && match[1] && typeof match[1] === 'string' ? match[1].replace(/\"/g, '') : ''
}
String.prototype.getURL = function () {
return this.split('|')[0] || ''
}
String.prototype.getParameter = function (name) {
const params = this.replace(/^(.*)\|/, '')
const regex = new RegExp(name + '=(\\w[^&]*)', 'gi')
const match = regex.exec(params)
return match && match[1] ? match[1] : ''
}
module.exports = Parser