Fixes linter errors

This commit is contained in:
freearhey
2023-10-15 14:08:23 +03:00
parent 57e508fc3b
commit 63c86a2b30
393 changed files with 28447 additions and 28443 deletions

View File

@@ -1,79 +1,79 @@
import { Collection } from '@freearhey/core'
type ApiChannelProps = {
id: string
name: string
alt_names: string[]
network: string
owners: string[]
country: string
subdivision: string
city: string
broadcast_area: string[]
languages: string[]
categories: string[]
is_nsfw: boolean
launched: string
closed: string
replaced_by: string
website: string
logo: string
}
export class ApiChannel {
id: string
name: string
altNames: Collection
network: string
owners: Collection
country: string
subdivision: string
city: string
broadcastArea: Collection
languages: Collection
categories: Collection
isNSFW: boolean
launched: string
closed: string
replacedBy: string
website: string
logo: string
constructor({
id,
name,
alt_names,
network,
owners,
country,
subdivision,
city,
broadcast_area,
languages,
categories,
is_nsfw,
launched,
closed,
replaced_by,
website,
logo
}: ApiChannelProps) {
this.id = id
this.name = name
this.altNames = new Collection(alt_names)
this.network = network
this.owners = new Collection(owners)
this.country = country
this.subdivision = subdivision
this.city = city
this.broadcastArea = new Collection(broadcast_area)
this.languages = new Collection(languages)
this.categories = new Collection(categories)
this.isNSFW = is_nsfw
this.launched = launched
this.closed = closed
this.replacedBy = replaced_by
this.website = website
this.logo = logo
}
}
import { Collection } from '@freearhey/core'
type ApiChannelProps = {
id: string
name: string
alt_names: string[]
network: string
owners: string[]
country: string
subdivision: string
city: string
broadcast_area: string[]
languages: string[]
categories: string[]
is_nsfw: boolean
launched: string
closed: string
replaced_by: string
website: string
logo: string
}
export class ApiChannel {
id: string
name: string
altNames: Collection
network: string
owners: Collection
country: string
subdivision: string
city: string
broadcastArea: Collection
languages: Collection
categories: Collection
isNSFW: boolean
launched: string
closed: string
replacedBy: string
website: string
logo: string
constructor({
id,
name,
alt_names,
network,
owners,
country,
subdivision,
city,
broadcast_area,
languages,
categories,
is_nsfw,
launched,
closed,
replaced_by,
website,
logo
}: ApiChannelProps) {
this.id = id
this.name = name
this.altNames = new Collection(alt_names)
this.network = network
this.owners = new Collection(owners)
this.country = country
this.subdivision = subdivision
this.city = city
this.broadcastArea = new Collection(broadcast_area)
this.languages = new Collection(languages)
this.categories = new Collection(categories)
this.isNSFW = is_nsfw
this.launched = launched
this.closed = closed
this.replacedBy = replaced_by
this.website = website
this.logo = logo
}
}

View File

@@ -1,59 +1,59 @@
import { Logger, Storage } from '@freearhey/core'
import axios, { AxiosInstance, AxiosResponse, AxiosProgressEvent } from 'axios'
import cliProgress, { MultiBar } from 'cli-progress'
import numeral from 'numeral'
export class ApiClient {
progressBar: MultiBar
client: AxiosInstance
storage: Storage
logger: Logger
constructor({ logger }: { logger: Logger }) {
this.logger = logger
this.client = axios.create({
responseType: 'stream'
})
this.storage = new Storage()
this.progressBar = new cliProgress.MultiBar({
stopOnComplete: true,
hideCursor: true,
forceRedraw: true,
barsize: 36,
format(options, params, payload) {
const filename = payload.filename.padEnd(18, ' ')
const barsize = options.barsize || 40
const percent = (params.progress * 100).toFixed(2)
const speed = payload.speed ? numeral(payload.speed).format('0.0 b') + '/s' : 'N/A'
const total = numeral(params.total).format('0.0 b')
const completeSize = Math.round(params.progress * barsize)
const incompleteSize = barsize - completeSize
const bar =
options.barCompleteString && options.barIncompleteString
? options.barCompleteString.substr(0, completeSize) +
options.barGlue +
options.barIncompleteString.substr(0, incompleteSize)
: '-'.repeat(barsize)
return `${filename} [${bar}] ${percent}% | ETA: ${params.eta}s | ${total} | ${speed}`
}
})
}
async download(filename: string) {
const stream = await this.storage.createStream(`/temp/data/${filename}`)
const bar = this.progressBar.create(0, 0, { filename })
this.client
.get(`https://iptv-org.github.io/api/${filename}`, {
onDownloadProgress({ total, loaded, rate }: AxiosProgressEvent) {
if (total) bar.setTotal(total)
bar.update(loaded, { speed: rate })
}
})
.then((response: AxiosResponse) => {
response.data.pipe(stream)
})
}
}
import { Logger, Storage } from '@freearhey/core'
import axios, { AxiosInstance, AxiosResponse, AxiosProgressEvent } from 'axios'
import cliProgress, { MultiBar } from 'cli-progress'
import numeral from 'numeral'
export class ApiClient {
progressBar: MultiBar
client: AxiosInstance
storage: Storage
logger: Logger
constructor({ logger }: { logger: Logger }) {
this.logger = logger
this.client = axios.create({
responseType: 'stream'
})
this.storage = new Storage()
this.progressBar = new cliProgress.MultiBar({
stopOnComplete: true,
hideCursor: true,
forceRedraw: true,
barsize: 36,
format(options, params, payload) {
const filename = payload.filename.padEnd(18, ' ')
const barsize = options.barsize || 40
const percent = (params.progress * 100).toFixed(2)
const speed = payload.speed ? numeral(payload.speed).format('0.0 b') + '/s' : 'N/A'
const total = numeral(params.total).format('0.0 b')
const completeSize = Math.round(params.progress * barsize)
const incompleteSize = barsize - completeSize
const bar =
options.barCompleteString && options.barIncompleteString
? options.barCompleteString.substr(0, completeSize) +
options.barGlue +
options.barIncompleteString.substr(0, incompleteSize)
: '-'.repeat(barsize)
return `${filename} [${bar}] ${percent}% | ETA: ${params.eta}s | ${total} | ${speed}`
}
})
}
async download(filename: string) {
const stream = await this.storage.createStream(`/temp/data/${filename}`)
const bar = this.progressBar.create(0, 0, { filename })
this.client
.get(`https://iptv-org.github.io/api/${filename}`, {
onDownloadProgress({ total, loaded, rate }: AxiosProgressEvent) {
if (total) bar.setTotal(total)
bar.update(loaded, { speed: rate })
}
})
.then((response: AxiosResponse) => {
response.data.pipe(stream)
})
}
}

View File

@@ -1,24 +1,24 @@
import { parseChannels } from 'epg-grabber'
import { Storage, Collection } from '@freearhey/core'
type ChannelsParserProps = {
storage: Storage
}
export class ChannelsParser {
storage: Storage
constructor({ storage }: ChannelsParserProps) {
this.storage = storage
}
async parse(filepath: string) {
let parsedChannels = new Collection()
const content = await this.storage.load(filepath)
const channels = parseChannels(content)
parsedChannels = parsedChannels.concat(new Collection(channels))
return parsedChannels
}
}
import { parseChannels } from 'epg-grabber'
import { Storage, Collection } from '@freearhey/core'
type ChannelsParserProps = {
storage: Storage
}
export class ChannelsParser {
storage: Storage
constructor({ storage }: ChannelsParserProps) {
this.storage = storage
}
async parse(filepath: string) {
let parsedChannels = new Collection()
const content = await this.storage.load(filepath)
const channels = parseChannels(content)
parsedChannels = parsedChannels.concat(new Collection(channels))
return parsedChannels
}
}

View File

@@ -1,21 +1,21 @@
import { SiteConfig } from 'epg-grabber'
import _ from 'lodash'
import { pathToFileURL } from 'url'
export class ConfigLoader {
async load(filepath: string): Promise<SiteConfig> {
const fileUrl = pathToFileURL(filepath).toString()
const config = (await import(fileUrl)).default
return _.merge(
{
delay: 0,
maxConnections: 1,
request: {
timeout: 30000
}
},
config
)
}
}
import { SiteConfig } from 'epg-grabber'
import _ from 'lodash'
import { pathToFileURL } from 'url'
export class ConfigLoader {
async load(filepath: string): Promise<SiteConfig> {
const fileUrl = pathToFileURL(filepath).toString()
const config = (await import(fileUrl)).default
return _.merge(
{
delay: 0,
maxConnections: 1,
request: {
timeout: 30000
}
},
config
)
}
}

View File

@@ -1,13 +1,13 @@
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
const date = {}
date.getUTC = function (d = null) {
if (typeof d === 'string') return dayjs.utc(d).startOf('d')
return dayjs.utc().startOf('d')
}
module.exports = date
const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
const date = {}
date.getUTC = function (d = null) {
if (typeof d === 'string') return dayjs.utc(d).startOf('d')
return dayjs.utc().startOf('d')
}
module.exports = date

View File

@@ -1,75 +1,75 @@
import { EPGGrabber, GrabCallbackData, EPGGrabberMock, SiteConfig, Channel } from 'epg-grabber'
import { Logger, Collection } from '@freearhey/core'
import { Queue } from './'
import { GrabOptions } from '../commands/epg/grab'
import { TaskQueue, PromisyClass } from 'cwait'
type GrabberProps = {
logger: Logger
queue: Queue
options: GrabOptions
}
export class Grabber {
logger: Logger
queue: Queue
options: GrabOptions
constructor({ logger, queue, options }: GrabberProps) {
this.logger = logger
this.queue = queue
this.options = options
}
async grab(): Promise<{ channels: Collection; programs: Collection }> {
const taskQueue = new TaskQueue(Promise as PromisyClass, this.options.maxConnections)
const total = this.queue.size()
const channels = new Collection()
let programs = new Collection()
let i = 1
await Promise.all(
this.queue.items().map(
taskQueue.wrap(
async (queueItem: { channel: Channel; config: SiteConfig; date: string }) => {
const { channel, config, date } = queueItem
channels.add(channel)
if (this.options.timeout !== undefined) {
const timeout = parseInt(this.options.timeout)
config.request = { ...config.request, ...{ timeout } }
}
const grabber =
process.env.NODE_ENV === 'test' ? new EPGGrabberMock(config) : new EPGGrabber(config)
const _programs = await grabber.grab(
channel,
date,
(data: GrabCallbackData, error: Error | null) => {
const { programs, date } = data
this.logger.info(
` [${i}/${total}] ${channel.site} (${channel.lang}) - ${
channel.xmltv_id
} - ${date.format('MMM D, YYYY')} (${programs.length} programs)`
)
if (i < total) i++
if (error) {
this.logger.info(` ERR: ${error.message}`)
}
}
)
programs = programs.concat(new Collection(_programs))
}
)
)
)
return { channels, programs }
}
}
import { EPGGrabber, GrabCallbackData, EPGGrabberMock, SiteConfig, Channel } from 'epg-grabber'
import { Logger, Collection } from '@freearhey/core'
import { Queue } from './'
import { GrabOptions } from '../commands/epg/grab'
import { TaskQueue, PromisyClass } from 'cwait'
type GrabberProps = {
logger: Logger
queue: Queue
options: GrabOptions
}
export class Grabber {
logger: Logger
queue: Queue
options: GrabOptions
constructor({ logger, queue, options }: GrabberProps) {
this.logger = logger
this.queue = queue
this.options = options
}
async grab(): Promise<{ channels: Collection; programs: Collection }> {
const taskQueue = new TaskQueue(Promise as PromisyClass, this.options.maxConnections)
const total = this.queue.size()
const channels = new Collection()
let programs = new Collection()
let i = 1
await Promise.all(
this.queue.items().map(
taskQueue.wrap(
async (queueItem: { channel: Channel; config: SiteConfig; date: string }) => {
const { channel, config, date } = queueItem
channels.add(channel)
if (this.options.timeout !== undefined) {
const timeout = parseInt(this.options.timeout)
config.request = { ...config.request, ...{ timeout } }
}
const grabber =
process.env.NODE_ENV === 'test' ? new EPGGrabberMock(config) : new EPGGrabber(config)
const _programs = await grabber.grab(
channel,
date,
(data: GrabCallbackData, error: Error | null) => {
const { programs, date } = data
this.logger.info(
` [${i}/${total}] ${channel.site} (${channel.lang}) - ${
channel.xmltv_id
} - ${date.format('MMM D, YYYY')} (${programs.length} programs)`
)
if (i < total) i++
if (error) {
this.logger.info(` ERR: ${error.message}`)
}
}
)
programs = programs.concat(new Collection(_programs))
}
)
)
)
return { channels, programs }
}
}

View File

@@ -1,55 +1,55 @@
import { Collection, Logger, DateTime, Storage, Zip } from '@freearhey/core'
import { Channel } from 'epg-grabber'
import { XMLTV } from '../core'
import { CURR_DATE } from '../constants'
type GuideProps = {
channels: Collection
programs: Collection
logger: Logger
filepath: string
gzip: boolean
}
export class Guide {
channels: Collection
programs: Collection
logger: Logger
storage: Storage
filepath: string
gzip: boolean
constructor({ channels, programs, logger, filepath, gzip }: GuideProps) {
this.channels = channels
this.programs = programs
this.logger = logger
this.storage = new Storage()
this.filepath = filepath
this.gzip = gzip || false
}
async save() {
const channels = this.channels.uniqBy(
(channel: Channel) => `${channel.xmltv_id}:${channel.site}`
)
const programs = this.programs
const xmltv = new XMLTV({
channels,
programs,
date: new DateTime(CURR_DATE, { zone: 'UTC' })
})
const xmlFilepath = this.filepath
this.logger.info(` saving to "${xmlFilepath}"...`)
await this.storage.save(xmlFilepath, xmltv.toString())
if (this.gzip) {
const zip = new Zip()
const compressed = await zip.compress(xmltv.toString())
const gzFilepath = `${this.filepath}.gz`
this.logger.info(` saving to "${gzFilepath}"...`)
await this.storage.save(gzFilepath, compressed)
}
}
}
import { Collection, Logger, DateTime, Storage, Zip } from '@freearhey/core'
import { Channel } from 'epg-grabber'
import { XMLTV } from '../core'
import { CURR_DATE } from '../constants'
type GuideProps = {
channels: Collection
programs: Collection
logger: Logger
filepath: string
gzip: boolean
}
export class Guide {
channels: Collection
programs: Collection
logger: Logger
storage: Storage
filepath: string
gzip: boolean
constructor({ channels, programs, logger, filepath, gzip }: GuideProps) {
this.channels = channels
this.programs = programs
this.logger = logger
this.storage = new Storage()
this.filepath = filepath
this.gzip = gzip || false
}
async save() {
const channels = this.channels.uniqBy(
(channel: Channel) => `${channel.xmltv_id}:${channel.site}`
)
const programs = this.programs
const xmltv = new XMLTV({
channels,
programs,
date: new DateTime(CURR_DATE, { zone: 'UTC' })
})
const xmlFilepath = this.filepath
this.logger.info(` saving to "${xmlFilepath}"...`)
await this.storage.save(xmlFilepath, xmltv.toString())
if (this.gzip) {
const zip = new Zip()
const compressed = await zip.compress(xmltv.toString())
const gzFilepath = `${this.filepath}.gz`
this.logger.info(` saving to "${gzFilepath}"...`)
await this.storage.save(gzFilepath, compressed)
}
}
}

View File

@@ -1,61 +1,61 @@
import { Collection, Logger, Storage, StringTemplate } from '@freearhey/core'
import { OptionValues } from 'commander'
import { Channel, Program } from 'epg-grabber'
import { Guide } from '.'
type GuideManagerProps = {
options: OptionValues
logger: Logger
channels: Collection
programs: Collection
}
export class GuideManager {
options: OptionValues
storage: Storage
logger: Logger
channels: Collection
programs: Collection
constructor({ channels, programs, logger, options }: GuideManagerProps) {
this.options = options
this.logger = logger
this.channels = channels
this.programs = programs
this.storage = new Storage()
}
async createGuides() {
const pathTemplate = new StringTemplate(this.options.output)
const groupedChannels = this.channels
.orderBy([(channel: Channel) => channel.xmltv_id])
.uniqBy((channel: Channel) => `${channel.xmltv_id}:${channel.site}:${channel.lang}`)
.groupBy((channel: Channel) => {
return pathTemplate.format({ lang: channel.lang || 'en', site: channel.site || '' })
})
const groupedPrograms = this.programs
.orderBy([(program: Program) => program.channel, (program: Program) => program.start])
.groupBy((program: Program) => {
const lang =
program.titles && program.titles.length && program.titles[0].lang
? program.titles[0].lang
: 'en'
return pathTemplate.format({ lang, site: program.site || '' })
})
for (const groupKey of groupedPrograms.keys()) {
const guide = new Guide({
filepath: groupKey,
gzip: this.options.gzip,
channels: new Collection(groupedChannels.get(groupKey)),
programs: new Collection(groupedPrograms.get(groupKey)),
logger: this.logger
})
await guide.save()
}
}
}
import { Collection, Logger, Storage, StringTemplate } from '@freearhey/core'
import { OptionValues } from 'commander'
import { Channel, Program } from 'epg-grabber'
import { Guide } from '.'
type GuideManagerProps = {
options: OptionValues
logger: Logger
channels: Collection
programs: Collection
}
export class GuideManager {
options: OptionValues
storage: Storage
logger: Logger
channels: Collection
programs: Collection
constructor({ channels, programs, logger, options }: GuideManagerProps) {
this.options = options
this.logger = logger
this.channels = channels
this.programs = programs
this.storage = new Storage()
}
async createGuides() {
const pathTemplate = new StringTemplate(this.options.output)
const groupedChannels = this.channels
.orderBy([(channel: Channel) => channel.xmltv_id])
.uniqBy((channel: Channel) => `${channel.xmltv_id}:${channel.site}:${channel.lang}`)
.groupBy((channel: Channel) => {
return pathTemplate.format({ lang: channel.lang || 'en', site: channel.site || '' })
})
const groupedPrograms = this.programs
.orderBy([(program: Program) => program.channel, (program: Program) => program.start])
.groupBy((program: Program) => {
const lang =
program.titles && program.titles.length && program.titles[0].lang
? program.titles[0].lang
: 'en'
return pathTemplate.format({ lang, site: program.site || '' })
})
for (const groupKey of groupedPrograms.keys()) {
const guide = new Guide({
filepath: groupKey,
gzip: this.options.gzip,
channels: new Collection(groupedChannels.get(groupKey)),
programs: new Collection(groupedPrograms.get(groupKey)),
logger: this.logger
})
await guide.save()
}
}
}

View File

@@ -1,12 +1,12 @@
export * from './xml'
export * from './channelsParser'
export * from './xmltv'
export * from './configLoader'
export * from './grabber'
export * from './job'
export * from './queue'
export * from './guideManager'
export * from './guide'
export * from './apiChannel'
export * from './apiClient'
export * from './queueCreator'
export * from './xml'
export * from './channelsParser'
export * from './xmltv'
export * from './configLoader'
export * from './grabber'
export * from './job'
export * from './queue'
export * from './guideManager'
export * from './guide'
export * from './apiChannel'
export * from './apiClient'
export * from './queueCreator'

View File

@@ -1,34 +1,34 @@
import { Logger } from '@freearhey/core'
import { Queue, Grabber, GuideManager } from '.'
import { GrabOptions } from '../commands/epg/grab'
type JobProps = {
options: GrabOptions
logger: Logger
queue: Queue
}
export class Job {
options: GrabOptions
logger: Logger
grabber: Grabber
constructor({ queue, logger, options }: JobProps) {
this.options = options
this.logger = logger
this.grabber = new Grabber({ logger, queue, options })
}
async run() {
const { channels, programs } = await this.grabber.grab()
const manager = new GuideManager({
channels,
programs,
options: this.options,
logger: this.logger
})
await manager.createGuides()
}
}
import { Logger } from '@freearhey/core'
import { Queue, Grabber, GuideManager } from '.'
import { GrabOptions } from '../commands/epg/grab'
type JobProps = {
options: GrabOptions
logger: Logger
queue: Queue
}
export class Job {
options: GrabOptions
logger: Logger
grabber: Grabber
constructor({ queue, logger, options }: JobProps) {
this.options = options
this.logger = logger
this.grabber = new Grabber({ logger, queue, options })
}
async run() {
const { channels, programs } = await this.grabber.grab()
const manager = new GuideManager({
channels,
programs,
options: this.options,
logger: this.logger
})
await manager.createGuides()
}
}

View File

@@ -1,45 +1,45 @@
import { Dictionary } from '@freearhey/core'
import { SiteConfig, Channel } from 'epg-grabber'
export type QueueItem = {
channel: Channel
date: string
config: SiteConfig
error: string | null
}
export class Queue {
_data: Dictionary
constructor() {
this._data = new Dictionary()
}
missing(key: string): boolean {
return this._data.missing(key)
}
add(
key: string,
{ channel, config, date }: { channel: Channel; date: string | null; config: SiteConfig }
) {
this._data.set(key, {
channel,
date,
config,
error: null
})
}
size(): number {
return Object.values(this._data.data()).length
}
items(): QueueItem[] {
return Object.values(this._data.data()) as QueueItem[]
}
isEmpty(): boolean {
return this.size() === 0
}
}
import { Dictionary } from '@freearhey/core'
import { SiteConfig, Channel } from 'epg-grabber'
export type QueueItem = {
channel: Channel
date: string
config: SiteConfig
error: string | null
}
export class Queue {
_data: Dictionary
constructor() {
this._data = new Dictionary()
}
missing(key: string): boolean {
return this._data.missing(key)
}
add(
key: string,
{ channel, config, date }: { channel: Channel; date: string | null; config: SiteConfig }
) {
this._data.set(key, {
channel,
date,
config,
error: null
})
}
size(): number {
return Object.values(this._data.data()).length
}
items(): QueueItem[] {
return Object.values(this._data.data()) as QueueItem[]
}
isEmpty(): boolean {
return this.size() === 0
}
}

View File

@@ -1,71 +1,71 @@
import { Storage, Collection, DateTime, Logger } from '@freearhey/core'
import { ChannelsParser, ConfigLoader, ApiChannel, Queue } from './'
import { SITES_DIR, DATA_DIR, CURR_DATE } from '../constants'
import { SiteConfig } from 'epg-grabber'
import path from 'path'
import { GrabOptions } from '../commands/epg/grab'
type QueueCreatorProps = {
logger: Logger
options: GrabOptions
parsedChannels: Collection
}
export class QueueCreator {
configLoader: ConfigLoader
logger: Logger
sitesStorage: Storage
dataStorage: Storage
parser: ChannelsParser
parsedChannels: Collection
options: GrabOptions
date: DateTime
constructor({ parsedChannels, logger, options }: QueueCreatorProps) {
this.parsedChannels = parsedChannels
this.logger = logger
this.sitesStorage = new Storage()
this.dataStorage = new Storage(DATA_DIR)
this.parser = new ChannelsParser({ storage: new Storage() })
this.date = new DateTime(CURR_DATE)
this.options = options
this.configLoader = new ConfigLoader()
}
async create(): Promise<Queue> {
const channelsContent = await this.dataStorage.json('channels.json')
const channels = new Collection(channelsContent).map(data => new ApiChannel(data))
const queue = new Queue()
for (const channel of this.parsedChannels.all()) {
if (!channel.site || !channel.xmltv_id) continue
if (this.options.lang && channel.lang !== this.options.lang) continue
const configPath = path.resolve(SITES_DIR, `${channel.site}/${channel.site}.config.js`)
const config: SiteConfig = await this.configLoader.load(configPath)
const found: ApiChannel = channels.first(
(_channel: ApiChannel) => _channel.id === channel.xmltv_id
)
if (found) {
channel.logo = found.logo
}
const days = this.options.days || config.days || 1
const dates = Array.from({ length: days }, (_, day) => this.date.add(day, 'd'))
dates.forEach((date: DateTime) => {
const dateString = date.toJSON()
const key = `${channel.site}:${channel.lang}:${channel.xmltv_id}:${dateString}`
if (queue.missing(key)) {
queue.add(key, {
channel,
date: dateString,
config
})
}
})
}
return queue
}
}
import { Storage, Collection, DateTime, Logger } from '@freearhey/core'
import { ChannelsParser, ConfigLoader, ApiChannel, Queue } from './'
import { SITES_DIR, DATA_DIR, CURR_DATE } from '../constants'
import { SiteConfig } from 'epg-grabber'
import path from 'path'
import { GrabOptions } from '../commands/epg/grab'
type QueueCreatorProps = {
logger: Logger
options: GrabOptions
parsedChannels: Collection
}
export class QueueCreator {
configLoader: ConfigLoader
logger: Logger
sitesStorage: Storage
dataStorage: Storage
parser: ChannelsParser
parsedChannels: Collection
options: GrabOptions
date: DateTime
constructor({ parsedChannels, logger, options }: QueueCreatorProps) {
this.parsedChannels = parsedChannels
this.logger = logger
this.sitesStorage = new Storage()
this.dataStorage = new Storage(DATA_DIR)
this.parser = new ChannelsParser({ storage: new Storage() })
this.date = new DateTime(CURR_DATE)
this.options = options
this.configLoader = new ConfigLoader()
}
async create(): Promise<Queue> {
const channelsContent = await this.dataStorage.json('channels.json')
const channels = new Collection(channelsContent).map(data => new ApiChannel(data))
const queue = new Queue()
for (const channel of this.parsedChannels.all()) {
if (!channel.site || !channel.xmltv_id) continue
if (this.options.lang && channel.lang !== this.options.lang) continue
const configPath = path.resolve(SITES_DIR, `${channel.site}/${channel.site}.config.js`)
const config: SiteConfig = await this.configLoader.load(configPath)
const found: ApiChannel = channels.first(
(_channel: ApiChannel) => _channel.id === channel.xmltv_id
)
if (found) {
channel.logo = found.logo
}
const days = this.options.days || config.days || 1
const dates = Array.from({ length: days }, (_, day) => this.date.add(day, 'd'))
dates.forEach((date: DateTime) => {
const dateString = date.toJSON()
const key = `${channel.site}:${channel.lang}:${channel.xmltv_id}:${dateString}`
if (queue.missing(key)) {
queue.add(key, {
channel,
date: dateString,
config
})
}
})
}
return queue
}
}

View File

@@ -1,56 +1,56 @@
import { Collection } from '@freearhey/core'
import { Channel } from 'epg-grabber'
export class XML {
items: Collection
constructor(items: Collection) {
this.items = items
}
toString() {
let output = '<?xml version="1.0" encoding="UTF-8"?>\r\n<channels>\r\n'
this.items.forEach((channel: Channel) => {
const logo = channel.logo ? ` logo="${channel.logo}"` : ''
const xmltv_id = channel.xmltv_id || ''
const lang = channel.lang || ''
const site_id = channel.site_id || ''
output += ` <channel site="${channel.site}" lang="${lang}" xmltv_id="${escapeString(
xmltv_id
)}" site_id="${site_id}"${logo}>${escapeString(channel.name)}</channel>\r\n`
})
output += '</channels>\r\n'
return output
}
}
function escapeString(value: string, defaultValue: string = '') {
if (!value) return defaultValue
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'
)
value = String(value || '').replace(regex, '')
return value
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;')
.replace(/\n|\r/g, ' ')
.replace(/ +/g, ' ')
.trim()
}
import { Collection } from '@freearhey/core'
import { Channel } from 'epg-grabber'
export class XML {
items: Collection
constructor(items: Collection) {
this.items = items
}
toString() {
let output = '<?xml version="1.0" encoding="UTF-8"?>\r\n<channels>\r\n'
this.items.forEach((channel: Channel) => {
const logo = channel.logo ? ` logo="${channel.logo}"` : ''
const xmltv_id = channel.xmltv_id || ''
const lang = channel.lang || ''
const site_id = channel.site_id || ''
output += ` <channel site="${channel.site}" lang="${lang}" xmltv_id="${escapeString(
xmltv_id
)}" site_id="${site_id}"${logo}>${escapeString(channel.name)}</channel>\r\n`
})
output += '</channels>\r\n'
return output
}
}
function escapeString(value: string, defaultValue: string = '') {
if (!value) return defaultValue
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'
)
value = String(value || '').replace(regex, '')
return value
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;')
.replace(/\n|\r/g, ' ')
.replace(/ +/g, ' ')
.trim()
}

View File

@@ -1,28 +1,28 @@
import { DateTime, Collection } from '@freearhey/core'
import { generateXMLTV } from 'epg-grabber'
type XMLTVProps = {
channels: Collection
programs: Collection
date: DateTime
}
export class XMLTV {
channels: Collection
programs: Collection
date: DateTime
constructor({ channels, programs, date }: XMLTVProps) {
this.channels = channels
this.programs = programs
this.date = date
}
toString() {
return generateXMLTV({
channels: this.channels.all(),
programs: this.programs.all(),
date: this.date.toJSON()
})
}
}
import { DateTime, Collection } from '@freearhey/core'
import { generateXMLTV } from 'epg-grabber'
type XMLTVProps = {
channels: Collection
programs: Collection
date: DateTime
}
export class XMLTV {
channels: Collection
programs: Collection
date: DateTime
constructor({ channels, programs, date }: XMLTVProps) {
this.channels = channels
this.programs = programs
this.date = date
}
toString() {
return generateXMLTV({
channels: this.channels.all(),
programs: this.programs.all(),
date: this.date.toJSON()
})
}
}