Create scripts/core
This commit is contained in:
parent
213da67762
commit
c6c3154522
|
@ -0,0 +1,19 @@
|
||||||
|
const IPTVChecker = require('iptv-checker')
|
||||||
|
|
||||||
|
const checker = {}
|
||||||
|
|
||||||
|
checker.check = async function (item, config) {
|
||||||
|
const ic = new IPTVChecker(config)
|
||||||
|
const result = await ic.checkStream({ url: item.url, http: item.http })
|
||||||
|
|
||||||
|
return {
|
||||||
|
_id: item._id,
|
||||||
|
url: item.url,
|
||||||
|
http: item.http,
|
||||||
|
error: !result.status.ok ? result.status.reason : null,
|
||||||
|
streams: result.status.ok ? result.status.metadata.streams : [],
|
||||||
|
requests: result.status.ok ? result.status.metadata.requests : []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = checker
|
|
@ -0,0 +1,61 @@
|
||||||
|
const Database = require('nedb-promises')
|
||||||
|
const file = require('./file')
|
||||||
|
|
||||||
|
const DB_FILEPATH = process.env.DB_FILEPATH || './scripts/channels.db'
|
||||||
|
|
||||||
|
const nedb = Database.create({
|
||||||
|
filename: file.resolve(DB_FILEPATH),
|
||||||
|
autoload: true,
|
||||||
|
onload(err) {
|
||||||
|
if (err) console.error(err)
|
||||||
|
},
|
||||||
|
compareStrings: (a, b) => {
|
||||||
|
a = a.replace(/\s/g, '_')
|
||||||
|
b = b.replace(/\s/g, '_')
|
||||||
|
|
||||||
|
return a.localeCompare(b, undefined, {
|
||||||
|
sensitivity: 'accent',
|
||||||
|
numeric: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const db = {}
|
||||||
|
|
||||||
|
db.removeIndex = function (field) {
|
||||||
|
return nedb.removeIndex(field)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.addIndex = function (options) {
|
||||||
|
return nedb.ensureIndex(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.compact = function () {
|
||||||
|
return nedb.persistence.compactDatafile()
|
||||||
|
}
|
||||||
|
|
||||||
|
db.reset = function () {
|
||||||
|
return file.clear(DB_FILEPATH)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.count = function (query) {
|
||||||
|
return nedb.count(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.insert = function (doc) {
|
||||||
|
return nedb.insert(doc)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.update = function (query, update) {
|
||||||
|
return nedb.update(query, update)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.find = function (query) {
|
||||||
|
return nedb.find(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.remove = function (query, options) {
|
||||||
|
return nedb.remove(query, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = db
|
|
@ -0,0 +1,67 @@
|
||||||
|
const path = require('path')
|
||||||
|
const glob = require('glob')
|
||||||
|
const fs = require('mz/fs')
|
||||||
|
|
||||||
|
const file = {}
|
||||||
|
|
||||||
|
file.list = function (pattern) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
glob(pattern, function (err, files) {
|
||||||
|
resolve(files)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
file.getFilename = function (filepath) {
|
||||||
|
return path.parse(filepath).name
|
||||||
|
}
|
||||||
|
|
||||||
|
file.createDir = async function (dir) {
|
||||||
|
if (await file.exists(dir)) return
|
||||||
|
|
||||||
|
return fs.mkdir(dir, { recursive: true }).catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
file.exists = function (filepath) {
|
||||||
|
return fs.exists(path.resolve(filepath))
|
||||||
|
}
|
||||||
|
|
||||||
|
file.read = function (filepath) {
|
||||||
|
return fs.readFile(path.resolve(filepath), { encoding: 'utf8' }).catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
file.append = function (filepath, data) {
|
||||||
|
return fs.appendFile(path.resolve(filepath), data).catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
file.create = function (filepath, data = '') {
|
||||||
|
filepath = path.resolve(filepath)
|
||||||
|
const dir = path.dirname(filepath)
|
||||||
|
|
||||||
|
return file
|
||||||
|
.createDir(dir)
|
||||||
|
.then(() => fs.writeFile(filepath, data, { encoding: 'utf8', flag: 'w' }))
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
file.write = function (filepath, data = '') {
|
||||||
|
return fs.writeFile(path.resolve(filepath), data).catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
file.clear = function (filepath) {
|
||||||
|
return file.write(filepath, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
file.resolve = function (filepath) {
|
||||||
|
return path.resolve(filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
file.dirname = function (filepath) {
|
||||||
|
return path.dirname(filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
file.basename = function (filepath) {
|
||||||
|
return path.basename(filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = file
|
|
@ -0,0 +1,114 @@
|
||||||
|
const { create: createPlaylist } = require('./playlist')
|
||||||
|
const store = require('./store')
|
||||||
|
const file = require('./file')
|
||||||
|
const logger = require('./logger')
|
||||||
|
const db = require('./db')
|
||||||
|
const _ = require('lodash')
|
||||||
|
|
||||||
|
const generator = {}
|
||||||
|
|
||||||
|
generator.generate = async function (filepath, query = {}, options = {}) {
|
||||||
|
options = {
|
||||||
|
...{
|
||||||
|
format: 'm3u',
|
||||||
|
saveEmpty: false,
|
||||||
|
includeNSFW: false,
|
||||||
|
includeGuides: true,
|
||||||
|
includeBroken: false,
|
||||||
|
onLoad: r => r,
|
||||||
|
uniqBy: item => item.id || _.uniqueId(),
|
||||||
|
sortBy: null
|
||||||
|
},
|
||||||
|
...options
|
||||||
|
}
|
||||||
|
|
||||||
|
query['is_nsfw'] = options.includeNSFW ? { $in: [true, false] } : false
|
||||||
|
query['is_broken'] = options.includeBroken ? { $in: [true, false] } : false
|
||||||
|
|
||||||
|
let items = await db
|
||||||
|
.find(query)
|
||||||
|
.sort({ name: 1, 'status.level': 1, 'resolution.height': -1, url: 1 })
|
||||||
|
|
||||||
|
items = _.uniqBy(items, 'url')
|
||||||
|
if (!options.saveEmpty && !items.length) return { filepath, query, options, count: 0 }
|
||||||
|
if (options.uniqBy) items = _.uniqBy(items, options.uniqBy)
|
||||||
|
|
||||||
|
items = options.onLoad(items)
|
||||||
|
|
||||||
|
if (options.sortBy) items = _.sortBy(items, options.sortBy)
|
||||||
|
|
||||||
|
switch (options.format) {
|
||||||
|
case 'json':
|
||||||
|
await saveAsJSON(filepath, items, options)
|
||||||
|
break
|
||||||
|
case 'm3u':
|
||||||
|
default:
|
||||||
|
await saveAsM3U(filepath, items, options)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return { filepath, query, options, count: items.length }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveAsM3U(filepath, items, options) {
|
||||||
|
const playlist = await createPlaylist(filepath)
|
||||||
|
|
||||||
|
const header = {}
|
||||||
|
if (options.includeGuides) {
|
||||||
|
let guides = items.map(item => item.guides)
|
||||||
|
guides = _.uniq(_.flatten(guides)).sort().join(',')
|
||||||
|
|
||||||
|
header['x-tvg-url'] = guides
|
||||||
|
}
|
||||||
|
|
||||||
|
await playlist.header(header)
|
||||||
|
for (const item of items) {
|
||||||
|
const stream = store.create(item)
|
||||||
|
await playlist.link(
|
||||||
|
stream.get('url'),
|
||||||
|
stream.get('title'),
|
||||||
|
{
|
||||||
|
'tvg-id': stream.get('tvg_id'),
|
||||||
|
'tvg-country': stream.get('tvg_country'),
|
||||||
|
'tvg-language': stream.get('tvg_language'),
|
||||||
|
'tvg-logo': stream.get('tvg_logo'),
|
||||||
|
// 'tvg-url': stream.get('tvg_url') || undefined,
|
||||||
|
'user-agent': stream.get('http.user-agent') || undefined,
|
||||||
|
'group-title': stream.get('group_title')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'http-referrer': stream.get('http.referrer') || undefined,
|
||||||
|
'http-user-agent': stream.get('http.user-agent') || undefined
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveAsJSON(filepath, items, options) {
|
||||||
|
const output = items.map(item => {
|
||||||
|
const stream = store.create(item)
|
||||||
|
const categories = stream.get('categories').map(c => ({ name: c.name, slug: c.slug }))
|
||||||
|
const countries = stream.get('countries').map(c => ({ name: c.name, code: c.code }))
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: stream.get('name'),
|
||||||
|
logo: stream.get('logo'),
|
||||||
|
url: stream.get('url'),
|
||||||
|
categories,
|
||||||
|
countries,
|
||||||
|
languages: stream.get('languages'),
|
||||||
|
tvg: {
|
||||||
|
id: stream.get('tvg_id'),
|
||||||
|
name: stream.get('name'),
|
||||||
|
url: stream.get('tvg_url')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await file.create(filepath, JSON.stringify(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
generator.saveAsM3U = saveAsM3U
|
||||||
|
generator.saveAsJSON = saveAsJSON
|
||||||
|
|
||||||
|
module.exports = generator
|
|
@ -0,0 +1,10 @@
|
||||||
|
exports.db = require('./db')
|
||||||
|
exports.logger = require('./logger')
|
||||||
|
exports.file = require('./file')
|
||||||
|
exports.timer = require('./timer')
|
||||||
|
exports.parser = require('./parser')
|
||||||
|
exports.checker = require('./checker')
|
||||||
|
exports.generator = require('./generator')
|
||||||
|
exports.playlist = require('./playlist')
|
||||||
|
exports.store = require('./store')
|
||||||
|
exports.markdown = require('./markdown')
|
|
@ -0,0 +1,42 @@
|
||||||
|
const { createLogger, format, transports, addColors } = require('winston')
|
||||||
|
const { combine, timestamp, printf } = format
|
||||||
|
|
||||||
|
const consoleFormat = ({ level, message, timestamp }) => {
|
||||||
|
if (typeof message === 'object') return JSON.stringify(message)
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
levels: {
|
||||||
|
error: 0,
|
||||||
|
warn: 1,
|
||||||
|
info: 2,
|
||||||
|
failed: 3,
|
||||||
|
success: 4,
|
||||||
|
http: 5,
|
||||||
|
verbose: 6,
|
||||||
|
debug: 7,
|
||||||
|
silly: 8
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
info: 'white',
|
||||||
|
success: 'green',
|
||||||
|
failed: 'red'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = [
|
||||||
|
new transports.Console({
|
||||||
|
format: format.combine(format.printf(consoleFormat))
|
||||||
|
})
|
||||||
|
]
|
||||||
|
|
||||||
|
const logger = createLogger({
|
||||||
|
transports: t,
|
||||||
|
levels: config.levels,
|
||||||
|
level: 'verbose'
|
||||||
|
})
|
||||||
|
|
||||||
|
addColors(config.colors)
|
||||||
|
|
||||||
|
module.exports = logger
|
|
@ -0,0 +1,39 @@
|
||||||
|
const markdownInclude = require('markdown-include')
|
||||||
|
const file = require('./file')
|
||||||
|
|
||||||
|
const markdown = {}
|
||||||
|
|
||||||
|
markdown.createTable = function (data, cols) {
|
||||||
|
let output = '<table>\n'
|
||||||
|
|
||||||
|
output += ' <thead>\n <tr>'
|
||||||
|
for (let column of cols) {
|
||||||
|
output += `<th align="${column.align}">${column.name}</th>`
|
||||||
|
}
|
||||||
|
output += '</tr>\n </thead>\n'
|
||||||
|
|
||||||
|
output += ' <tbody>\n'
|
||||||
|
for (let item of data) {
|
||||||
|
output += ' <tr>'
|
||||||
|
let i = 0
|
||||||
|
for (let prop in item) {
|
||||||
|
const column = cols[i]
|
||||||
|
let nowrap = column.nowrap
|
||||||
|
let align = column.align
|
||||||
|
output += `<td align="${align}"${nowrap ? ' nowrap' : ''}>${item[prop]}</td>`
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
output += '</tr>\n'
|
||||||
|
}
|
||||||
|
output += ' </tbody>\n'
|
||||||
|
|
||||||
|
output += '</table>'
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
markdown.compile = function (filepath) {
|
||||||
|
markdownInclude.compileFiles(file.resolve(filepath))
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = markdown
|
|
@ -0,0 +1,31 @@
|
||||||
|
const ipp = require('iptv-playlist-parser')
|
||||||
|
const logger = require('./logger')
|
||||||
|
const file = require('./file')
|
||||||
|
|
||||||
|
const parser = {}
|
||||||
|
|
||||||
|
parser.parsePlaylist = async function (filepath) {
|
||||||
|
const content = await file.read(filepath)
|
||||||
|
const playlist = ipp.parse(content)
|
||||||
|
|
||||||
|
return playlist.items
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.parseLogs = async function (filepath) {
|
||||||
|
const content = await file.read(filepath)
|
||||||
|
if (!content) return []
|
||||||
|
const lines = content.split('\n')
|
||||||
|
|
||||||
|
return lines.map(line => (line ? JSON.parse(line) : null)).filter(l => l)
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.parseNumber = function (string) {
|
||||||
|
const parsed = parseInt(string)
|
||||||
|
if (isNaN(parsed)) {
|
||||||
|
logger.error('Not a number')
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = parser
|
|
@ -0,0 +1,49 @@
|
||||||
|
const file = require('./file')
|
||||||
|
|
||||||
|
const playlist = {}
|
||||||
|
|
||||||
|
playlist.create = async function (filepath) {
|
||||||
|
playlist.filepath = filepath
|
||||||
|
const dir = file.dirname(filepath)
|
||||||
|
file.createDir(dir)
|
||||||
|
await file.create(filepath, '')
|
||||||
|
|
||||||
|
return playlist
|
||||||
|
}
|
||||||
|
|
||||||
|
playlist.header = async function (attrs) {
|
||||||
|
let header = `#EXTM3U`
|
||||||
|
for (const name in attrs) {
|
||||||
|
const value = attrs[name]
|
||||||
|
header += ` ${name}="${value}"`
|
||||||
|
}
|
||||||
|
header += `\n`
|
||||||
|
|
||||||
|
await file.append(playlist.filepath, header)
|
||||||
|
|
||||||
|
return playlist
|
||||||
|
}
|
||||||
|
|
||||||
|
playlist.link = async function (url, title, attrs, vlcOpts) {
|
||||||
|
let link = `#EXTINF:-1`
|
||||||
|
for (const name in attrs) {
|
||||||
|
const value = attrs[name]
|
||||||
|
if (value !== undefined) {
|
||||||
|
link += ` ${name}="${value}"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
link += `,${title}\n`
|
||||||
|
for (const name in vlcOpts) {
|
||||||
|
const value = vlcOpts[name]
|
||||||
|
if (value !== undefined) {
|
||||||
|
link += `#EXTVLCOPT:${name}=${value}\n`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
link += `${url}\n`
|
||||||
|
|
||||||
|
await file.append(playlist.filepath, link)
|
||||||
|
|
||||||
|
return playlist
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = playlist
|
|
@ -0,0 +1,56 @@
|
||||||
|
const _ = require('lodash')
|
||||||
|
const logger = require('./logger')
|
||||||
|
const setters = require('../store/setters')
|
||||||
|
const getters = require('../store/getters')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
create(state = {}) {
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
changed: false,
|
||||||
|
set: function (prop, value) {
|
||||||
|
const prevState = JSON.stringify(this.state)
|
||||||
|
|
||||||
|
const setter = setters[prop]
|
||||||
|
if (typeof setter === 'function') {
|
||||||
|
try {
|
||||||
|
this.state[prop] = setter.bind()(value)
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`store/setters/${prop}.js: ${error.message}`)
|
||||||
|
}
|
||||||
|
} else if (typeof value === 'object') {
|
||||||
|
this.state[prop] = value[prop]
|
||||||
|
} else {
|
||||||
|
this.state[prop] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
const newState = JSON.stringify(this.state)
|
||||||
|
if (prevState !== newState) {
|
||||||
|
this.changed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
get: function (prop) {
|
||||||
|
const getter = getters[prop]
|
||||||
|
if (typeof getter === 'function') {
|
||||||
|
try {
|
||||||
|
return getter.bind(this.state)()
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`store/getters/${prop}.js: ${error.message}`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return prop.split('.').reduce((o, i) => (o ? o[i] : undefined), this.state)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
has: function (prop) {
|
||||||
|
const value = this.get(prop)
|
||||||
|
|
||||||
|
return !_.isEmpty(value)
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return this.state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
const { performance } = require('perf_hooks')
|
||||||
|
const dayjs = require('dayjs')
|
||||||
|
const duration = require('dayjs/plugin/duration')
|
||||||
|
const relativeTime = require('dayjs/plugin/relativeTime')
|
||||||
|
|
||||||
|
dayjs.extend(relativeTime)
|
||||||
|
dayjs.extend(duration)
|
||||||
|
|
||||||
|
const timer = {}
|
||||||
|
|
||||||
|
let t0 = 0
|
||||||
|
|
||||||
|
timer.start = function () {
|
||||||
|
t0 = performance.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
timer.format = function (f) {
|
||||||
|
let t1 = performance.now()
|
||||||
|
|
||||||
|
return dayjs.duration(t1 - t0).format(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
timer.humanize = function (suffix = true) {
|
||||||
|
let t1 = performance.now()
|
||||||
|
|
||||||
|
return dayjs.duration(t1 - t0).humanize(suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = timer
|
Loading…
Reference in New Issue