const playlistParser = require('iptv-playlist-parser') const utils = require('./utils') const categories = require('./categories') const path = require('path') const sfwCategories = categories.filter(c => !c.nsfw).map(c => c.name) const nsfwCategories = categories.filter(c => c.nsfw).map(c => c.name) const parser = {} parser.parseIndex = function () { const content = utils.readFile('index.m3u') const result = playlistParser.parse(content) return result.items } parser.parsePlaylist = function (filename) { const content = utils.readFile(filename) const result = playlistParser.parse(content) const name = path.parse(filename).name const country = utils.code2name(name) return new Playlist({ header: result.header, items: result.items, url: filename, country, name }) } class Playlist { constructor({ header, items, url, name, country }) { this.url = url this.name = name this.country = country this.header = header this.channels = items .map(item => new Channel({ data: item, header, sourceUrl: url })) .filter(channel => channel.url) } toString(options = {}) { const config = { raw: false, ...options } let parts = ['#EXTM3U'] for (let key in this.header.attrs) { let value = this.header.attrs[key] if (value) { parts.push(`${key}="${value}"`) } } let output = `${parts.join(' ')}\n` for (let channel of this.channels) { output += channel.toString(config.raw) } return output } } class Channel { constructor({ data, header, sourceUrl }) { this.parseData(data) this.filename = utils.getBasename(sourceUrl) if (!this.countries.length) { const countryName = utils.code2name(this.filename) this.countries = countryName ? [{ code: this.filename, name: countryName }] : [] this.tvg.country = this.countries.map(c => c.code.toUpperCase()).join(';') } } parseData(data) { const title = this.parseTitle(data.name) this.tvg = data.tvg this.http = data.http this.url = data.url this.logo = data.tvg.logo this.name = title.channelName this.status = title.streamStatus this.resolution = title.streamResolution this.countries = this.parseCountries(data.tvg.country) this.languages = this.parseLanguages(data.tvg.language) this.category = this.parseCategory(data.group.title) this.raw = data.raw } parseCountries(string) { let arr = string .split(';') .reduce((acc, curr) => { const codes = utils.region2codes(curr) if (codes.length) { for (let code of codes) { if (!acc.includes(code)) { acc.push(code) } } } else { acc.push(curr) } return acc }, []) .filter(code => code && utils.code2name(code)) return arr.map(code => { return { code: code.toLowerCase(), name: utils.code2name(code) } }) } parseLanguages(string) { return string .split(';') .map(name => { const code = name ? utils.language2code(name) : null if (!code) return null return { code, name } }) .filter(l => l) } parseCategory(string) { const category = categories.find(c => c.id === string.toLowerCase()) return category ? category.name : '' } parseTitle(title) { const channelName = title .trim() .split(' ') .map(s => s.trim()) .filter(s => { return !/\[|\]/i.test(s) && !/\((\d+)P\)/i.test(s) }) .join(' ') const streamStatusMatch = title.match(/\[(.*)\]/i) const streamStatus = streamStatusMatch ? streamStatusMatch[1] : null const streamResolutionMatch = title.match(/\((\d+)P\)/i) const streamResolutionHeight = streamResolutionMatch ? parseInt(streamResolutionMatch[1]) : null const streamResolution = { width: null, height: streamResolutionHeight } return { channelName, streamStatus, streamResolution } } get tvgCountry() { return this.tvg.country .split(';') .map(code => utils.code2name(code)) .join(';') } get tvgLanguage() { return this.tvg.language } get tvgUrl() { return this.tvg.id && this.tvg.url ? this.tvg.url : '' } get tvgId() { if (this.tvg.id) { return this.tvg.id } else if (this.filename !== 'unsorted') { const id = utils.name2id(this.tvgName) return id ? `${id}.${this.filename}` : '' } return '' } get tvgName() { if (this.tvg.name) { return this.tvg.name } else if (this.filename !== 'unsorted') { return this.name.replace(/\"/gi, '') } return '' } getInfo() { this.tvg.country = this.tvg.country.toUpperCase() let info = `-1 tvg-id="${this.tvgId}" tvg-name="${this.tvgName}" tvg-country="${this.tvg.country}" tvg-language="${this.tvg.language}" tvg-logo="${this.logo}"` info += ` group-title="${this.category}",${this.name}` if (this.resolution.height) { info += ` (${this.resolution.height}p)` } if (this.status) { info += ` [${this.status}]` } if (this.http['referrer']) { info += `\n#EXTVLCOPT:http-referrer=${this.http['referrer']}` } if (this.http['user-agent']) { info += `\n#EXTVLCOPT:http-user-agent=${this.http['user-agent']}` } return info } toString(raw = false) { if (raw) return this.raw + '\n' return '#EXTINF:' + this.getInfo() + '\n' + this.url + '\n' } toObject() { return { name: this.name, logo: this.logo || null, url: this.url, category: this.category || null, languages: this.languages, countries: this.countries, tvg: { id: this.tvgId || null, name: this.tvgName || null, url: this.tvgUrl || null } } } isSFW() { return sfwCategories.includes(this.category) } isNSFW() { return nsfwCategories.includes(this.category) } } module.exports = parser