mirror of
https://github.com/iptv-org/epg
synced 2025-12-18 11:27:06 -05:00
Update scripts
This commit is contained in:
@@ -1,17 +1,9 @@
|
|||||||
import { Logger, Storage, Collection } from '@freearhey/core'
|
import { Logger, Collection, Storage } from '@freearhey/core'
|
||||||
import { ChannelsParser } from '../../core'
|
|
||||||
import path from 'path'
|
|
||||||
import { SITES_DIR, API_DIR } from '../../constants'
|
import { SITES_DIR, API_DIR } from '../../constants'
|
||||||
|
import { GuideChannel } from '../../models'
|
||||||
|
import { ChannelsParser } from '../../core'
|
||||||
import epgGrabber from 'epg-grabber'
|
import epgGrabber from 'epg-grabber'
|
||||||
|
import path from 'path'
|
||||||
type OutputItem = {
|
|
||||||
channel: string | null
|
|
||||||
feed: string | null
|
|
||||||
site: string
|
|
||||||
site_id: string
|
|
||||||
site_name: string
|
|
||||||
lang: string
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const logger = new Logger()
|
const logger = new Logger()
|
||||||
@@ -20,31 +12,24 @@ async function main() {
|
|||||||
|
|
||||||
logger.info('loading channels...')
|
logger.info('loading channels...')
|
||||||
const sitesStorage = new Storage(SITES_DIR)
|
const sitesStorage = new Storage(SITES_DIR)
|
||||||
const parser = new ChannelsParser({ storage: sitesStorage })
|
const parser = new ChannelsParser({
|
||||||
|
storage: sitesStorage
|
||||||
|
})
|
||||||
|
|
||||||
let files: string[] = []
|
const files: string[] = await sitesStorage.list('**/*.channels.xml')
|
||||||
files = await sitesStorage.list('**/*.channels.xml')
|
|
||||||
|
|
||||||
let parsedChannels = new Collection()
|
const channels = new Collection()
|
||||||
for (const filepath of files) {
|
for (const filepath of files) {
|
||||||
parsedChannels = parsedChannels.concat(await parser.parse(filepath))
|
const channelList = await parser.parse(filepath)
|
||||||
|
|
||||||
|
channelList.channels.forEach((data: epgGrabber.Channel) => {
|
||||||
|
channels.add(new GuideChannel(data))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(` found ${parsedChannels.count()} channel(s)`)
|
logger.info(`found ${channels.count()} channel(s)`)
|
||||||
|
|
||||||
const output = parsedChannels.map((channel: epgGrabber.Channel): OutputItem => {
|
const output = channels.map((channel: GuideChannel) => channel.toJSON())
|
||||||
const xmltv_id = channel.xmltv_id || ''
|
|
||||||
const [channelId, feedId] = xmltv_id.split('@')
|
|
||||||
|
|
||||||
return {
|
|
||||||
channel: channelId || null,
|
|
||||||
feed: feedId || null,
|
|
||||||
site: channel.site || '',
|
|
||||||
site_id: channel.site_id || '',
|
|
||||||
site_name: channel.name,
|
|
||||||
lang: channel.lang || ''
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const apiStorage = new Storage(API_DIR)
|
const apiStorage = new Storage(API_DIR)
|
||||||
const outputFilename = 'guides.json'
|
const outputFilename = 'guides.json'
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ async function main() {
|
|||||||
loader.download('feeds.json'),
|
loader.download('feeds.json'),
|
||||||
loader.download('timezones.json'),
|
loader.download('timezones.json'),
|
||||||
loader.download('guides.json'),
|
loader.download('guides.json'),
|
||||||
loader.download('streams.json')
|
loader.download('streams.json'),
|
||||||
|
loader.download('logos.json')
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { Storage, Collection, Logger, Dictionary } from '@freearhey/core'
|
import { Storage, Collection, Logger, Dictionary } from '@freearhey/core'
|
||||||
|
import type { DataProcessorData } from '../../types/dataProcessor'
|
||||||
|
import type { DataLoaderData } from '../../types/dataLoader'
|
||||||
|
import { ChannelSearchableData } from '../../types/channel'
|
||||||
|
import { Channel, ChannelList, Feed } from '../../models'
|
||||||
|
import { DataProcessor, DataLoader } from '../../core'
|
||||||
import { select, input } from '@inquirer/prompts'
|
import { select, input } from '@inquirer/prompts'
|
||||||
import { ChannelsParser, XML } from '../../core'
|
import { ChannelsParser } from '../../core'
|
||||||
import { Channel, Feed } from '../../models'
|
|
||||||
import { DATA_DIR } from '../../constants'
|
import { DATA_DIR } from '../../constants'
|
||||||
import nodeCleanup from 'node-cleanup'
|
import nodeCleanup from 'node-cleanup'
|
||||||
|
import sjs from '@freearhey/search-js'
|
||||||
|
import epgGrabber from 'epg-grabber'
|
||||||
import { Command } from 'commander'
|
import { Command } from 'commander'
|
||||||
import readline from 'readline'
|
import readline from 'readline'
|
||||||
import sjs from '@freearhey/search-js'
|
|
||||||
import { DataProcessor, DataLoader } from '../../core'
|
|
||||||
import type { DataLoaderData } from '../../types/dataLoader'
|
|
||||||
import type { DataProcessorData } from '../../types/dataProcessor'
|
|
||||||
import epgGrabber from 'epg-grabber'
|
|
||||||
import { ChannelSearchableData } from '../../types/channel'
|
|
||||||
|
|
||||||
type ChoiceValue = { type: string; value?: Feed | Channel }
|
type ChoiceValue = { type: string; value?: Feed | Channel }
|
||||||
type Choice = { name: string; short?: string; value: ChoiceValue; default?: boolean }
|
type Choice = { name: string; short?: string; value: ChoiceValue; default?: boolean }
|
||||||
@@ -34,11 +34,11 @@ program.argument('<filepath>', 'Path to *.channels.xml file to edit').parse(proc
|
|||||||
const filepath = program.args[0]
|
const filepath = program.args[0]
|
||||||
const logger = new Logger()
|
const logger = new Logger()
|
||||||
const storage = new Storage()
|
const storage = new Storage()
|
||||||
let parsedChannels = new Collection()
|
let channelList = new ChannelList({ channels: [] })
|
||||||
|
|
||||||
main(filepath)
|
main(filepath)
|
||||||
nodeCleanup(() => {
|
nodeCleanup(() => {
|
||||||
save(filepath)
|
save(filepath, channelList)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default async function main(filepath: string) {
|
export default async function main(filepath: string) {
|
||||||
@@ -51,18 +51,18 @@ export default async function main(filepath: string) {
|
|||||||
const dataStorage = new Storage(DATA_DIR)
|
const dataStorage = new Storage(DATA_DIR)
|
||||||
const loader = new DataLoader({ storage: dataStorage })
|
const loader = new DataLoader({ storage: dataStorage })
|
||||||
const data: DataLoaderData = await loader.load()
|
const data: DataLoaderData = await loader.load()
|
||||||
const { feedsGroupedByChannelId, channels, channelsKeyById }: DataProcessorData =
|
const { channels, channelsKeyById, feedsGroupedByChannelId }: DataProcessorData =
|
||||||
processor.process(data)
|
processor.process(data)
|
||||||
|
|
||||||
logger.info('loading channels...')
|
logger.info('loading channels...')
|
||||||
const parser = new ChannelsParser({ storage })
|
const parser = new ChannelsParser({ storage })
|
||||||
parsedChannels = await parser.parse(filepath)
|
channelList = await parser.parse(filepath)
|
||||||
const parsedChannelsWithoutId = parsedChannels.filter(
|
const parsedChannelsWithoutId = channelList.channels.filter(
|
||||||
(channel: epgGrabber.Channel) => !channel.xmltv_id
|
(channel: epgGrabber.Channel) => !channel.xmltv_id
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`found ${parsedChannels.count()} channels (including ${parsedChannelsWithoutId.count()} without ID)`
|
`found ${channelList.channels.count()} channels (including ${parsedChannelsWithoutId.count()} without ID)`
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info('creating search index...')
|
logger.info('creating search index...')
|
||||||
@@ -73,10 +73,10 @@ export default async function main(filepath: string) {
|
|||||||
|
|
||||||
logger.info('starting...\n')
|
logger.info('starting...\n')
|
||||||
|
|
||||||
for (const parsedChannel of parsedChannelsWithoutId.all()) {
|
for (const channel of parsedChannelsWithoutId.all()) {
|
||||||
try {
|
try {
|
||||||
parsedChannel.xmltv_id = await selectChannel(
|
channel.xmltv_id = await selectChannel(
|
||||||
parsedChannel,
|
channel,
|
||||||
searchIndex,
|
searchIndex,
|
||||||
feedsGroupedByChannelId,
|
feedsGroupedByChannelId,
|
||||||
channelsKeyById
|
channelsKeyById
|
||||||
@@ -124,8 +124,8 @@ async function selectChannel(
|
|||||||
case 'channel': {
|
case 'channel': {
|
||||||
const selectedChannel = selected.value
|
const selectedChannel = selected.value
|
||||||
if (!selectedChannel) return ''
|
if (!selectedChannel) return ''
|
||||||
const selectedFeedId = await selectFeed(selectedChannel.id, feedsGroupedByChannelId)
|
const selectedFeedId = await selectFeed(selectedChannel.id || '', feedsGroupedByChannelId)
|
||||||
if (selectedFeedId === '-') return selectedChannel.id
|
if (selectedFeedId === '-') return selectedChannel.id || ''
|
||||||
return [selectedChannel.id, selectedFeedId].join('@')
|
return [selectedChannel.id, selectedFeedId].join('@')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,7 +153,7 @@ async function selectFeed(channelId: string, feedsGroupedByChannelId: Dictionary
|
|||||||
case 'feed':
|
case 'feed':
|
||||||
const selectedFeed = selected.value
|
const selectedFeed = selected.value
|
||||||
if (!selectedFeed) return ''
|
if (!selectedFeed) return ''
|
||||||
return selectedFeed.id
|
return selectedFeed.id || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
return ''
|
return ''
|
||||||
@@ -205,10 +205,9 @@ function getFeedChoises(feeds: Collection): Choice[] {
|
|||||||
return choises
|
return choises
|
||||||
}
|
}
|
||||||
|
|
||||||
function save(filepath: string) {
|
function save(filepath: string, channelList: ChannelList) {
|
||||||
if (!storage.existsSync(filepath)) return
|
if (!storage.existsSync(filepath)) return
|
||||||
const xml = new XML(parsedChannels)
|
storage.saveSync(filepath, channelList.toString())
|
||||||
storage.saveSync(filepath, xml.toString())
|
|
||||||
logger.info(`\nFile '${filepath}' successfully saved`)
|
logger.info(`\nFile '${filepath}' successfully saved`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Logger, File, Collection, Storage } from '@freearhey/core'
|
import { Logger, File, Storage } from '@freearhey/core'
|
||||||
import { ChannelsParser, XML } from '../../core'
|
import { ChannelsParser } from '../../core'
|
||||||
import { Channel } from 'epg-grabber'
|
import { ChannelList } from '../../models'
|
||||||
import { Command } from 'commander'
|
|
||||||
import { pathToFileURL } from 'node:url'
|
import { pathToFileURL } from 'node:url'
|
||||||
|
import epgGrabber from 'epg-grabber'
|
||||||
|
import { Command } from 'commander'
|
||||||
|
|
||||||
const program = new Command()
|
const program = new Command()
|
||||||
program
|
program
|
||||||
@@ -21,17 +22,25 @@ type ParseOptions = {
|
|||||||
const options: ParseOptions = program.opts()
|
const options: ParseOptions = program.opts()
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
function isPromise(promise: object[] | Promise<object[]>) {
|
||||||
|
return (
|
||||||
|
!!promise &&
|
||||||
|
typeof promise === 'object' &&
|
||||||
|
typeof (promise as Promise<object[]>).then === 'function'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const storage = new Storage()
|
const storage = new Storage()
|
||||||
const parser = new ChannelsParser({ storage })
|
|
||||||
const logger = new Logger()
|
const logger = new Logger()
|
||||||
|
const parser = new ChannelsParser({ storage })
|
||||||
const file = new File(options.config)
|
const file = new File(options.config)
|
||||||
const dir = file.dirname()
|
const dir = file.dirname()
|
||||||
const config = (await import(pathToFileURL(options.config).toString())).default
|
const config = (await import(pathToFileURL(options.config).toString())).default
|
||||||
const outputFilepath = options.output || `${dir}/${config.site}.channels.xml`
|
const outputFilepath = options.output || `${dir}/${config.site}.channels.xml`
|
||||||
|
|
||||||
let channels = new Collection()
|
let channelList = new ChannelList({ channels: [] })
|
||||||
if (await storage.exists(outputFilepath)) {
|
if (await storage.exists(outputFilepath)) {
|
||||||
channels = await parser.parse(outputFilepath)
|
channelList = await parser.parse(outputFilepath)
|
||||||
}
|
}
|
||||||
|
|
||||||
const args: {
|
const args: {
|
||||||
@@ -49,45 +58,31 @@ async function main() {
|
|||||||
if (isPromise(parsedChannels)) {
|
if (isPromise(parsedChannels)) {
|
||||||
parsedChannels = await parsedChannels
|
parsedChannels = await parsedChannels
|
||||||
}
|
}
|
||||||
parsedChannels = parsedChannels.map((channel: Channel) => {
|
parsedChannels = parsedChannels.map((channel: epgGrabber.Channel) => {
|
||||||
channel.site = config.site
|
channel.site = config.site
|
||||||
|
|
||||||
return channel
|
return channel
|
||||||
})
|
})
|
||||||
|
|
||||||
let output = new Collection()
|
const newChannelList = new ChannelList({ channels: [] })
|
||||||
parsedChannels.forEach((channel: Channel) => {
|
parsedChannels.forEach((channel: epgGrabber.Channel) => {
|
||||||
const found: Channel | undefined = channels.first(
|
if (!channel.site_id) return
|
||||||
(_channel: Channel) => _channel.site_id == channel.site_id
|
|
||||||
)
|
const found: epgGrabber.Channel | undefined = channelList.get(channel.site_id)
|
||||||
|
|
||||||
if (found) {
|
if (found) {
|
||||||
channel.xmltv_id = found.xmltv_id
|
channel.xmltv_id = found.xmltv_id
|
||||||
channel.lang = found.lang
|
channel.lang = found.lang
|
||||||
}
|
}
|
||||||
|
|
||||||
output.add(channel)
|
newChannelList.add(channel)
|
||||||
})
|
})
|
||||||
|
|
||||||
output = output.orderBy([
|
newChannelList.sort()
|
||||||
(channel: Channel) => channel.lang || '_',
|
|
||||||
(channel: Channel) => (channel.xmltv_id ? channel.xmltv_id.toLowerCase() : '0'),
|
|
||||||
(channel: Channel) => channel.site_id
|
|
||||||
])
|
|
||||||
|
|
||||||
const xml = new XML(output)
|
await storage.save(outputFilepath, newChannelList.toString())
|
||||||
|
|
||||||
await storage.save(outputFilepath, xml.toString())
|
|
||||||
|
|
||||||
logger.info(`File '${outputFilepath}' successfully saved`)
|
logger.info(`File '${outputFilepath}' successfully saved`)
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
function isPromise(promise: object[] | Promise<object[]>) {
|
|
||||||
return (
|
|
||||||
!!promise &&
|
|
||||||
typeof promise === 'object' &&
|
|
||||||
typeof (promise as Promise<object[]>).then === 'function'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { Storage, Collection, Dictionary, File } from '@freearhey/core'
|
import { ChannelsParser, DataLoader, DataProcessor } from '../../core'
|
||||||
import { ChannelsParser } from '../../core'
|
import { DataProcessorData } from '../../types/dataProcessor'
|
||||||
import { Channel, Feed } from '../../models'
|
import { Storage, Dictionary, File } from '@freearhey/core'
|
||||||
|
import { DataLoaderData } from '../../types/dataLoader'
|
||||||
|
import { ChannelList } from '../../models'
|
||||||
|
import { DATA_DIR } from '../../constants'
|
||||||
|
import epgGrabber from 'epg-grabber'
|
||||||
import { program } from 'commander'
|
import { program } from 'commander'
|
||||||
import chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
import langs from 'langs'
|
import langs from 'langs'
|
||||||
import { DATA_DIR } from '../../constants'
|
|
||||||
import epgGrabber from 'epg-grabber'
|
|
||||||
|
|
||||||
program.argument('[filepath]', 'Path to *.channels.xml files to validate').parse(process.argv)
|
program.argument('[filepath]', 'Path to *.channels.xml files to validate').parse(process.argv)
|
||||||
|
|
||||||
@@ -19,15 +21,14 @@ type ValidationError = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const parser = new ChannelsParser({ storage: new Storage() })
|
const processor = new DataProcessor()
|
||||||
|
|
||||||
const dataStorage = new Storage(DATA_DIR)
|
const dataStorage = new Storage(DATA_DIR)
|
||||||
const channelsData = await dataStorage.json('channels.json')
|
const loader = new DataLoader({ storage: dataStorage })
|
||||||
const channels = new Collection(channelsData).map(data => new Channel(data))
|
const data: DataLoaderData = await loader.load()
|
||||||
const channelsKeyById = channels.keyBy((channel: Channel) => channel.id)
|
const { channelsKeyById, feedsKeyByStreamId }: DataProcessorData = processor.process(data)
|
||||||
const feedsData = await dataStorage.json('feeds.json')
|
const parser = new ChannelsParser({
|
||||||
const feeds = new Collection(feedsData).map(data => new Feed(data))
|
storage: new Storage()
|
||||||
const feedsKeyByStreamId = feeds.keyBy((feed: Feed) => feed.getStreamId())
|
})
|
||||||
|
|
||||||
let totalFiles = 0
|
let totalFiles = 0
|
||||||
let totalErrors = 0
|
let totalErrors = 0
|
||||||
@@ -38,11 +39,11 @@ async function main() {
|
|||||||
const file = new File(filepath)
|
const file = new File(filepath)
|
||||||
if (file.extension() !== 'xml') continue
|
if (file.extension() !== 'xml') continue
|
||||||
|
|
||||||
const parsedChannels = await parser.parse(filepath)
|
const channelList: ChannelList = await parser.parse(filepath)
|
||||||
|
|
||||||
const bufferBySiteId = new Dictionary()
|
const bufferBySiteId = new Dictionary()
|
||||||
const errors: ValidationError[] = []
|
const errors: ValidationError[] = []
|
||||||
parsedChannels.forEach((channel: epgGrabber.Channel) => {
|
channelList.channels.forEach((channel: epgGrabber.Channel) => {
|
||||||
const bufferId: string = channel.site_id
|
const bufferId: string = channel.site_id
|
||||||
if (bufferBySiteId.missing(bufferId)) {
|
if (bufferBySiteId.missing(bufferId)) {
|
||||||
bufferBySiteId.set(bufferId, true)
|
bufferBySiteId.set(bufferId, true)
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Logger, Timer, Storage, Collection } from '@freearhey/core'
|
import { Logger, Timer, Storage, Collection } from '@freearhey/core'
|
||||||
import { Option, program } from 'commander'
|
|
||||||
import { QueueCreator, Job, ChannelsParser } from '../../core'
|
import { QueueCreator, Job, ChannelsParser } from '../../core'
|
||||||
|
import { Option, program } from 'commander'
|
||||||
|
import { SITES_DIR } from '../../constants'
|
||||||
import { Channel } from 'epg-grabber'
|
import { Channel } from 'epg-grabber'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { SITES_DIR } from '../../constants'
|
import { ChannelList } from '../../models'
|
||||||
|
|
||||||
program
|
program
|
||||||
.addOption(new Option('-s, --site <name>', 'Name of the site to parse'))
|
.addOption(new Option('-s, --site <name>', 'Name of the site to parse'))
|
||||||
@@ -31,7 +32,7 @@ program
|
|||||||
'--days <days>',
|
'--days <days>',
|
||||||
'Override the number of days for which the program will be loaded (defaults to the value from the site config)'
|
'Override the number of days for which the program will be loaded (defaults to the value from the site config)'
|
||||||
)
|
)
|
||||||
.argParser(value => (value !== undefined ? parseInt(value) : undefined))
|
.argParser(value => parseInt(value))
|
||||||
.env('DAYS')
|
.env('DAYS')
|
||||||
)
|
)
|
||||||
.addOption(
|
.addOption(
|
||||||
@@ -87,31 +88,35 @@ async function main() {
|
|||||||
files = await storage.list(options.channels)
|
files = await storage.list(options.channels)
|
||||||
}
|
}
|
||||||
|
|
||||||
let parsedChannels = new Collection()
|
let channels = new Collection()
|
||||||
for (const filepath of files) {
|
for (const filepath of files) {
|
||||||
parsedChannels = parsedChannels.concat(await parser.parse(filepath))
|
const channelList: ChannelList = await parser.parse(filepath)
|
||||||
|
|
||||||
|
channels = channels.concat(channelList.channels)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.lang) {
|
if (options.lang) {
|
||||||
parsedChannels = parsedChannels.filter((channel: Channel) => {
|
channels = channels.filter((channel: Channel) => {
|
||||||
if (!options.lang || !channel.lang) return true
|
if (!options.lang || !channel.lang) return true
|
||||||
|
|
||||||
return options.lang.includes(channel.lang)
|
return options.lang.includes(channel.lang)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
logger.info(` found ${parsedChannels.count()} channel(s)`)
|
|
||||||
|
logger.info(` found ${channels.count()} channel(s)`)
|
||||||
|
|
||||||
logger.info('run:')
|
logger.info('run:')
|
||||||
runJob({ logger, parsedChannels })
|
runJob({ logger, channels })
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
async function runJob({ logger, parsedChannels }: { logger: Logger; parsedChannels: Collection }) {
|
async function runJob({ logger, channels }: { logger: Logger; channels: Collection }) {
|
||||||
const timer = new Timer()
|
const timer = new Timer()
|
||||||
timer.start()
|
timer.start()
|
||||||
|
|
||||||
const queueCreator = new QueueCreator({
|
const queueCreator = new QueueCreator({
|
||||||
parsedChannels,
|
channels,
|
||||||
logger,
|
logger,
|
||||||
options
|
options
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
import { IssueLoader, HTMLTable, ChannelsParser } from '../../core'
|
import { IssueLoader, HTMLTable, ChannelsParser } from '../../core'
|
||||||
import { Logger, Storage, Collection } from '@freearhey/core'
|
import { Logger, Storage, Collection } from '@freearhey/core'
|
||||||
|
import { ChannelList, Issue, Site } from '../../models'
|
||||||
import { SITES_DIR, ROOT_DIR } from '../../constants'
|
import { SITES_DIR, ROOT_DIR } from '../../constants'
|
||||||
import { Issue, Site } from '../../models'
|
|
||||||
import { Channel } from 'epg-grabber'
|
import { Channel } from 'epg-grabber'
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const logger = new Logger({ disabled: true })
|
const logger = new Logger({ level: -999 })
|
||||||
const loader = new IssueLoader()
|
const issueLoader = new IssueLoader()
|
||||||
const sitesStorage = new Storage(SITES_DIR)
|
const sitesStorage = new Storage(SITES_DIR)
|
||||||
const channelsParser = new ChannelsParser({ storage: sitesStorage })
|
|
||||||
const sites = new Collection()
|
const sites = new Collection()
|
||||||
|
|
||||||
|
logger.info('loading channels...')
|
||||||
|
const channelsParser = new ChannelsParser({
|
||||||
|
storage: sitesStorage
|
||||||
|
})
|
||||||
|
|
||||||
logger.info('loading list of sites')
|
logger.info('loading list of sites')
|
||||||
const folders = await sitesStorage.list('*/')
|
const folders = await sitesStorage.list('*/')
|
||||||
|
|
||||||
logger.info('loading issues...')
|
logger.info('loading issues...')
|
||||||
const issues = await loader.load()
|
const issues = await issueLoader.load()
|
||||||
|
|
||||||
logger.info('putting the data together...')
|
logger.info('putting the data together...')
|
||||||
const brokenGuideReports = issues.filter(issue =>
|
const brokenGuideReports = issues.filter(issue =>
|
||||||
@@ -33,19 +37,21 @@ async function main() {
|
|||||||
|
|
||||||
const files = await sitesStorage.list(`${domain}/*.channels.xml`)
|
const files = await sitesStorage.list(`${domain}/*.channels.xml`)
|
||||||
for (const filepath of files) {
|
for (const filepath of files) {
|
||||||
const channels = await channelsParser.parse(filepath)
|
const channelList: ChannelList = await channelsParser.parse(filepath)
|
||||||
|
|
||||||
site.totalChannels += channels.count()
|
site.totalChannels += channelList.channels.count()
|
||||||
site.markedChannels += channels.filter((channel: Channel) => channel.xmltv_id).count()
|
site.markedChannels += channelList.channels
|
||||||
|
.filter((channel: Channel) => channel.xmltv_id)
|
||||||
|
.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
sites.add(site)
|
sites.add(site)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('creating sites table...')
|
logger.info('creating sites table...')
|
||||||
const data = new Collection()
|
const tableData = new Collection()
|
||||||
sites.forEach((site: Site) => {
|
sites.forEach((site: Site) => {
|
||||||
data.add([
|
tableData.add([
|
||||||
{ value: `<a href="sites/${site.domain}">${site.domain}</a>` },
|
{ value: `<a href="sites/${site.domain}">${site.domain}</a>` },
|
||||||
{ value: site.totalChannels, align: 'right' },
|
{ value: site.totalChannels, align: 'right' },
|
||||||
{ value: site.markedChannels, align: 'right' },
|
{ value: site.markedChannels, align: 'right' },
|
||||||
@@ -55,7 +61,7 @@ async function main() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
logger.info('updating sites.md...')
|
logger.info('updating sites.md...')
|
||||||
const table = new HTMLTable(data.all(), [
|
const table = new HTMLTable(tableData.all(), [
|
||||||
{ name: 'Site', align: 'left' },
|
{ name: 'Site', align: 'left' },
|
||||||
{ name: 'Channels<br>(total / with xmltv-id)', colspan: 2, align: 'left' },
|
{ name: 'Channels<br>(total / with xmltv-id)', colspan: 2, align: 'left' },
|
||||||
{ name: 'Status', align: 'left' },
|
{ name: 'Status', align: 'left' },
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { parseChannels } from 'epg-grabber'
|
import { parseChannels } from 'epg-grabber'
|
||||||
import { Storage, Collection } from '@freearhey/core'
|
import { Storage } from '@freearhey/core'
|
||||||
|
import { ChannelList } from '../models'
|
||||||
|
|
||||||
type ChannelsParserProps = {
|
type ChannelsParserProps = {
|
||||||
storage: Storage
|
storage: Storage
|
||||||
@@ -12,13 +13,10 @@ export class ChannelsParser {
|
|||||||
this.storage = storage
|
this.storage = storage
|
||||||
}
|
}
|
||||||
|
|
||||||
async parse(filepath: string) {
|
async parse(filepath: string): Promise<ChannelList> {
|
||||||
let parsedChannels = new Collection()
|
|
||||||
|
|
||||||
const content = await this.storage.load(filepath)
|
const content = await this.storage.load(filepath)
|
||||||
const channels = parseChannels(content)
|
const parsed = parseChannels(content)
|
||||||
parsedChannels = parsedChannels.concat(new Collection(channels))
|
|
||||||
|
|
||||||
return parsedChannels
|
return new ChannelList({ channels: parsed })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ export class DataLoader {
|
|||||||
feeds,
|
feeds,
|
||||||
timezones,
|
timezones,
|
||||||
guides,
|
guides,
|
||||||
streams
|
streams,
|
||||||
|
logos
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
this.storage.json('countries.json'),
|
this.storage.json('countries.json'),
|
||||||
this.storage.json('regions.json'),
|
this.storage.json('regions.json'),
|
||||||
@@ -61,7 +62,8 @@ export class DataLoader {
|
|||||||
this.storage.json('feeds.json'),
|
this.storage.json('feeds.json'),
|
||||||
this.storage.json('timezones.json'),
|
this.storage.json('timezones.json'),
|
||||||
this.storage.json('guides.json'),
|
this.storage.json('guides.json'),
|
||||||
this.storage.json('streams.json')
|
this.storage.json('streams.json'),
|
||||||
|
this.storage.json('logos.json')
|
||||||
])
|
])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -75,7 +77,8 @@ export class DataLoader {
|
|||||||
feeds,
|
feeds,
|
||||||
timezones,
|
timezones,
|
||||||
guides,
|
guides,
|
||||||
streams
|
streams,
|
||||||
|
logos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { Channel, Feed, GuideChannel, Logo, Stream } from '../models'
|
||||||
import { DataLoaderData } from '../types/dataLoader'
|
import { DataLoaderData } from '../types/dataLoader'
|
||||||
import { Collection } from '@freearhey/core'
|
import { Collection } from '@freearhey/core'
|
||||||
import { Channel, Feed, Guide, Stream } from '../models'
|
|
||||||
|
|
||||||
export class DataProcessor {
|
export class DataProcessor {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
@@ -9,31 +9,48 @@ export class DataProcessor {
|
|||||||
let channels = new Collection(data.channels).map(data => new Channel(data))
|
let channels = new Collection(data.channels).map(data => new Channel(data))
|
||||||
const channelsKeyById = channels.keyBy((channel: Channel) => channel.id)
|
const channelsKeyById = channels.keyBy((channel: Channel) => channel.id)
|
||||||
|
|
||||||
const guides = new Collection(data.guides).map(data => new Guide(data))
|
const guideChannels = new Collection(data.guides).map(data => new GuideChannel(data))
|
||||||
const guidesGroupedByStreamId = guides.groupBy((guide: Guide) => guide.getStreamId())
|
const guideChannelsGroupedByStreamId = guideChannels.groupBy((channel: GuideChannel) =>
|
||||||
|
channel.getStreamId()
|
||||||
|
)
|
||||||
|
|
||||||
const streams = new Collection(data.streams).map(data => new Stream(data))
|
const streams = new Collection(data.streams).map(data => new Stream(data))
|
||||||
const streamsGroupedById = streams.groupBy((stream: Stream) => stream.getId())
|
const streamsGroupedById = streams.groupBy((stream: Stream) => stream.getId())
|
||||||
|
|
||||||
const feeds = new Collection(data.feeds).map(data =>
|
let feeds = new Collection(data.feeds).map(data =>
|
||||||
new Feed(data)
|
new Feed(data)
|
||||||
.withGuides(guidesGroupedByStreamId)
|
.withGuideChannels(guideChannelsGroupedByStreamId)
|
||||||
.withStreams(streamsGroupedById)
|
.withStreams(streamsGroupedById)
|
||||||
.withChannel(channelsKeyById)
|
.withChannel(channelsKeyById)
|
||||||
)
|
)
|
||||||
|
const feedsKeyByStreamId = feeds.keyBy((feed: Feed) => feed.getStreamId())
|
||||||
|
|
||||||
|
const logos = new Collection(data.logos).map(data =>
|
||||||
|
new Logo(data).withFeed(feedsKeyByStreamId)
|
||||||
|
)
|
||||||
|
const logosGroupedByChannelId = logos.groupBy((logo: Logo) => logo.channelId)
|
||||||
|
const logosGroupedByStreamId = logos.groupBy((logo: Logo) => logo.getStreamId())
|
||||||
|
|
||||||
|
feeds = feeds.map((feed: Feed) => feed.withLogos(logosGroupedByStreamId))
|
||||||
const feedsGroupedByChannelId = feeds.groupBy((feed: Feed) => feed.channelId)
|
const feedsGroupedByChannelId = feeds.groupBy((feed: Feed) => feed.channelId)
|
||||||
|
|
||||||
channels = channels.map((channel: Channel) => channel.withFeeds(feedsGroupedByChannelId))
|
channels = channels.map((channel: Channel) =>
|
||||||
|
channel.withFeeds(feedsGroupedByChannelId).withLogos(logosGroupedByChannelId)
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
guideChannelsGroupedByStreamId,
|
||||||
feedsGroupedByChannelId,
|
feedsGroupedByChannelId,
|
||||||
guidesGroupedByStreamId,
|
logosGroupedByChannelId,
|
||||||
|
logosGroupedByStreamId,
|
||||||
streamsGroupedById,
|
streamsGroupedById,
|
||||||
|
feedsKeyByStreamId,
|
||||||
channelsKeyById,
|
channelsKeyById,
|
||||||
|
guideChannels,
|
||||||
channels,
|
channels,
|
||||||
streams,
|
streams,
|
||||||
guides,
|
feeds,
|
||||||
feeds
|
logos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import { Collection, Logger, DateTime, Storage, Zip } from '@freearhey/core'
|
|
||||||
import { Channel } from 'epg-grabber'
|
|
||||||
import { XMLTV } from '../core'
|
|
||||||
import path from 'path'
|
|
||||||
|
|
||||||
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(path.dirname(filepath))
|
|
||||||
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 currDate = new DateTime(process.env.CURR_DATE || new Date().toISOString(), {
|
|
||||||
timezone: 'UTC'
|
|
||||||
})
|
|
||||||
const xmltv = new XMLTV({
|
|
||||||
channels,
|
|
||||||
programs,
|
|
||||||
date: currDate
|
|
||||||
})
|
|
||||||
|
|
||||||
const xmlFilepath = this.filepath
|
|
||||||
const xmlFilename = path.basename(xmlFilepath)
|
|
||||||
this.logger.info(` saving to "${xmlFilepath}"...`)
|
|
||||||
await this.storage.save(xmlFilename, xmltv.toString())
|
|
||||||
|
|
||||||
if (this.gzip) {
|
|
||||||
const zip = new Zip()
|
|
||||||
const compressed = zip.compress(xmltv.toString())
|
|
||||||
const gzFilepath = `${this.filepath}.gz`
|
|
||||||
const gzFilename = path.basename(gzFilepath)
|
|
||||||
this.logger.info(` saving to "${gzFilepath}"...`)
|
|
||||||
await this.storage.save(gzFilename, compressed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
import { Collection, Logger, Storage, StringTemplate } from '@freearhey/core'
|
import { Collection, Logger, Zip, Storage, StringTemplate } from '@freearhey/core'
|
||||||
|
import epgGrabber from 'epg-grabber'
|
||||||
import { OptionValues } from 'commander'
|
import { OptionValues } from 'commander'
|
||||||
import { Channel, Program } from 'epg-grabber'
|
import { Channel, Feed, Guide } from '../models'
|
||||||
import { Guide } from '.'
|
import path from 'path'
|
||||||
|
import { DataLoader, DataProcessor } from '.'
|
||||||
|
import { DataLoaderData } from '../types/dataLoader'
|
||||||
|
import { DataProcessorData } from '../types/dataProcessor'
|
||||||
|
import { DATA_DIR } from '../constants'
|
||||||
|
|
||||||
type GuideManagerProps = {
|
type GuideManagerProps = {
|
||||||
options: OptionValues
|
options: OptionValues
|
||||||
@@ -12,7 +17,6 @@ type GuideManagerProps = {
|
|||||||
|
|
||||||
export class GuideManager {
|
export class GuideManager {
|
||||||
options: OptionValues
|
options: OptionValues
|
||||||
storage: Storage
|
|
||||||
logger: Logger
|
logger: Logger
|
||||||
channels: Collection
|
channels: Collection
|
||||||
programs: Collection
|
programs: Collection
|
||||||
@@ -22,22 +26,51 @@ export class GuideManager {
|
|||||||
this.logger = logger
|
this.logger = logger
|
||||||
this.channels = channels
|
this.channels = channels
|
||||||
this.programs = programs
|
this.programs = programs
|
||||||
this.storage = new Storage()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async createGuides() {
|
async createGuides() {
|
||||||
const pathTemplate = new StringTemplate(this.options.output)
|
const pathTemplate = new StringTemplate(this.options.output)
|
||||||
|
|
||||||
|
const processor = new DataProcessor()
|
||||||
|
const dataStorage = new Storage(DATA_DIR)
|
||||||
|
const loader = new DataLoader({ storage: dataStorage })
|
||||||
|
const data: DataLoaderData = await loader.load()
|
||||||
|
const { feedsKeyByStreamId, channelsKeyById }: DataProcessorData = processor.process(data)
|
||||||
|
|
||||||
const groupedChannels = this.channels
|
const groupedChannels = this.channels
|
||||||
.orderBy([(channel: Channel) => channel.index, (channel: Channel) => channel.xmltv_id])
|
.map((channel: epgGrabber.Channel) => {
|
||||||
.uniqBy((channel: Channel) => `${channel.xmltv_id}:${channel.site}:${channel.lang}`)
|
if (channel.xmltv_id && !channel.icon) {
|
||||||
.groupBy((channel: Channel) => {
|
const foundFeed: Feed = feedsKeyByStreamId.get(channel.xmltv_id)
|
||||||
|
if (foundFeed && foundFeed.hasLogo()) {
|
||||||
|
channel.icon = foundFeed.getLogoUrl()
|
||||||
|
} else {
|
||||||
|
const [channelId] = channel.xmltv_id.split('@')
|
||||||
|
const foundChannel: Channel = channelsKeyById.get(channelId)
|
||||||
|
if (foundChannel && foundChannel.hasLogo()) {
|
||||||
|
channel.icon = foundChannel.getLogoUrl()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return channel
|
||||||
|
})
|
||||||
|
.orderBy([
|
||||||
|
(channel: epgGrabber.Channel) => channel.index,
|
||||||
|
(channel: epgGrabber.Channel) => channel.xmltv_id
|
||||||
|
])
|
||||||
|
.uniqBy(
|
||||||
|
(channel: epgGrabber.Channel) => `${channel.xmltv_id}:${channel.site}:${channel.lang}`
|
||||||
|
)
|
||||||
|
.groupBy((channel: epgGrabber.Channel) => {
|
||||||
return pathTemplate.format({ lang: channel.lang || 'en', site: channel.site || '' })
|
return pathTemplate.format({ lang: channel.lang || 'en', site: channel.site || '' })
|
||||||
})
|
})
|
||||||
|
|
||||||
const groupedPrograms = this.programs
|
const groupedPrograms = this.programs
|
||||||
.orderBy([(program: Program) => program.channel, (program: Program) => program.start])
|
.orderBy([
|
||||||
.groupBy((program: Program) => {
|
(program: epgGrabber.Program) => program.channel,
|
||||||
|
(program: epgGrabber.Program) => program.start
|
||||||
|
])
|
||||||
|
.groupBy((program: epgGrabber.Program) => {
|
||||||
const lang =
|
const lang =
|
||||||
program.titles && program.titles.length && program.titles[0].lang
|
program.titles && program.titles.length && program.titles[0].lang
|
||||||
? program.titles[0].lang
|
? program.titles[0].lang
|
||||||
@@ -51,11 +84,28 @@ export class GuideManager {
|
|||||||
filepath: groupKey,
|
filepath: groupKey,
|
||||||
gzip: this.options.gzip,
|
gzip: this.options.gzip,
|
||||||
channels: new Collection(groupedChannels.get(groupKey)),
|
channels: new Collection(groupedChannels.get(groupKey)),
|
||||||
programs: new Collection(groupedPrograms.get(groupKey)),
|
programs: new Collection(groupedPrograms.get(groupKey))
|
||||||
logger: this.logger
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await guide.save()
|
await this.save(guide)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async save(guide: Guide) {
|
||||||
|
const storage = new Storage(path.dirname(guide.filepath))
|
||||||
|
const xmlFilepath = guide.filepath
|
||||||
|
const xmlFilename = path.basename(xmlFilepath)
|
||||||
|
this.logger.info(` saving to "${xmlFilepath}"...`)
|
||||||
|
const xmltv = guide.toString()
|
||||||
|
await storage.save(xmlFilename, xmltv)
|
||||||
|
|
||||||
|
if (guide.gzip) {
|
||||||
|
const zip = new Zip()
|
||||||
|
const compressed = zip.compress(xmltv)
|
||||||
|
const gzFilepath = `${guide.filepath}.gz`
|
||||||
|
const gzFilename = path.basename(gzFilepath)
|
||||||
|
this.logger.info(` saving to "${gzFilepath}"...`)
|
||||||
|
await storage.save(gzFilename, compressed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ export * from './configLoader'
|
|||||||
export * from './dataLoader'
|
export * from './dataLoader'
|
||||||
export * from './dataProcessor'
|
export * from './dataProcessor'
|
||||||
export * from './grabber'
|
export * from './grabber'
|
||||||
export * from './guide'
|
|
||||||
export * from './guideManager'
|
export * from './guideManager'
|
||||||
export * from './htmlTable'
|
export * from './htmlTable'
|
||||||
export * from './issueLoader'
|
export * from './issueLoader'
|
||||||
@@ -13,5 +12,3 @@ export * from './job'
|
|||||||
export * from './proxyParser'
|
export * from './proxyParser'
|
||||||
export * from './queue'
|
export * from './queue'
|
||||||
export * from './queueCreator'
|
export * from './queueCreator'
|
||||||
export * from './xml'
|
|
||||||
export * from './xmltv'
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export class IssueLoader {
|
|||||||
repo: REPO,
|
repo: REPO,
|
||||||
per_page: 100,
|
per_page: 100,
|
||||||
labels,
|
labels,
|
||||||
|
state: 'open',
|
||||||
headers: {
|
headers: {
|
||||||
'X-GitHub-Api-Version': '2022-11-28'
|
'X-GitHub-Api-Version': '2022-11-28'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { Storage, Collection, DateTime, Logger } from '@freearhey/core'
|
import { Storage, Collection, DateTime, Logger } from '@freearhey/core'
|
||||||
import { ChannelsParser, ConfigLoader, Queue } from './'
|
|
||||||
import { SITES_DIR, DATA_DIR } from '../constants'
|
import { SITES_DIR, DATA_DIR } from '../constants'
|
||||||
|
import { GrabOptions } from '../commands/epg/grab'
|
||||||
|
import { ConfigLoader, Queue } from './'
|
||||||
import { SiteConfig } from 'epg-grabber'
|
import { SiteConfig } from 'epg-grabber'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { GrabOptions } from '../commands/epg/grab'
|
|
||||||
import { Channel } from '../models'
|
|
||||||
|
|
||||||
type QueueCreatorProps = {
|
type QueueCreatorProps = {
|
||||||
logger: Logger
|
logger: Logger
|
||||||
options: GrabOptions
|
options: GrabOptions
|
||||||
parsedChannels: Collection
|
channels: Collection
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QueueCreator {
|
export class QueueCreator {
|
||||||
@@ -17,44 +16,29 @@ export class QueueCreator {
|
|||||||
logger: Logger
|
logger: Logger
|
||||||
sitesStorage: Storage
|
sitesStorage: Storage
|
||||||
dataStorage: Storage
|
dataStorage: Storage
|
||||||
parser: ChannelsParser
|
channels: Collection
|
||||||
parsedChannels: Collection
|
|
||||||
options: GrabOptions
|
options: GrabOptions
|
||||||
|
|
||||||
constructor({ parsedChannels, logger, options }: QueueCreatorProps) {
|
constructor({ channels, logger, options }: QueueCreatorProps) {
|
||||||
this.parsedChannels = parsedChannels
|
this.channels = channels
|
||||||
this.logger = logger
|
this.logger = logger
|
||||||
this.sitesStorage = new Storage()
|
this.sitesStorage = new Storage()
|
||||||
this.dataStorage = new Storage(DATA_DIR)
|
this.dataStorage = new Storage(DATA_DIR)
|
||||||
this.parser = new ChannelsParser({ storage: new Storage() })
|
|
||||||
this.options = options
|
this.options = options
|
||||||
this.configLoader = new ConfigLoader()
|
this.configLoader = new ConfigLoader()
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(): Promise<Queue> {
|
async create(): Promise<Queue> {
|
||||||
const channelsContent = await this.dataStorage.json('channels.json')
|
|
||||||
const channels = new Collection(channelsContent).map(data => new Channel(data))
|
|
||||||
|
|
||||||
let index = 0
|
let index = 0
|
||||||
const queue = new Queue()
|
const queue = new Queue()
|
||||||
for (const channel of this.parsedChannels.all()) {
|
for (const channel of this.channels.all()) {
|
||||||
channel.index = index++
|
channel.index = index++
|
||||||
if (!channel.site || !channel.site_id || !channel.name) continue
|
if (!channel.site || !channel.site_id || !channel.name) continue
|
||||||
|
|
||||||
const configPath = path.resolve(SITES_DIR, `${channel.site}/${channel.site}.config.js`)
|
const configPath = path.resolve(SITES_DIR, `${channel.site}/${channel.site}.config.js`)
|
||||||
const config: SiteConfig = await this.configLoader.load(configPath)
|
const config: SiteConfig = await this.configLoader.load(configPath)
|
||||||
|
|
||||||
if (channel.xmltv_id) {
|
if (!channel.xmltv_id) {
|
||||||
if (!channel.icon) {
|
|
||||||
const found: Channel = channels.first(
|
|
||||||
(_channel: Channel) => _channel.id === channel.xmltv_id
|
|
||||||
)
|
|
||||||
|
|
||||||
if (found) {
|
|
||||||
channel.icon = found.logo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
channel.xmltv_id = channel.site_id
|
channel.xmltv_id = channel.site_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
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, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''')
|
|
||||||
.replace(/\n|\r/g, ' ')
|
|
||||||
.replace(/ +/g, ' ')
|
|
||||||
.trim()
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
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()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +1,28 @@
|
|||||||
import { ChannelData, ChannelSearchableData } from '../types/channel'
|
import { ChannelData, ChannelSearchableData } from '../types/channel'
|
||||||
import { Collection, Dictionary } from '@freearhey/core'
|
import { Collection, Dictionary } from '@freearhey/core'
|
||||||
import { Stream, Guide, Feed } from './'
|
import { Stream, Feed, Logo, GuideChannel } from './'
|
||||||
|
|
||||||
export class Channel {
|
export class Channel {
|
||||||
id: string
|
id?: string
|
||||||
name: string
|
name?: string
|
||||||
altNames?: Collection
|
altNames?: Collection
|
||||||
network?: string
|
network?: string
|
||||||
owners?: Collection
|
owners?: Collection
|
||||||
countryCode: string
|
countryCode?: string
|
||||||
subdivisionCode?: string
|
subdivisionCode?: string
|
||||||
cityName?: string
|
cityName?: string
|
||||||
categoryIds?: Collection
|
categoryIds?: Collection
|
||||||
isNSFW: boolean
|
isNSFW: boolean = false
|
||||||
launched?: string
|
launched?: string
|
||||||
closed?: string
|
closed?: string
|
||||||
replacedBy?: string
|
replacedBy?: string
|
||||||
website?: string
|
website?: string
|
||||||
logo?: string
|
|
||||||
feeds?: Collection
|
feeds?: Collection
|
||||||
|
logos: Collection = new Collection()
|
||||||
|
|
||||||
|
constructor(data?: ChannelData) {
|
||||||
|
if (!data) return
|
||||||
|
|
||||||
constructor(data: ChannelData) {
|
|
||||||
this.id = data.id
|
this.id = data.id
|
||||||
this.name = data.name
|
this.name = data.name
|
||||||
this.altNames = new Collection(data.alt_names)
|
this.altNames = new Collection(data.alt_names)
|
||||||
@@ -35,11 +37,16 @@ export class Channel {
|
|||||||
this.closed = data.closed || undefined
|
this.closed = data.closed || undefined
|
||||||
this.replacedBy = data.replaced_by || undefined
|
this.replacedBy = data.replaced_by || undefined
|
||||||
this.website = data.website || undefined
|
this.website = data.website || undefined
|
||||||
this.logo = data.logo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
withFeeds(feedsGroupedByChannelId: Dictionary): this {
|
withFeeds(feedsGroupedByChannelId: Dictionary): this {
|
||||||
this.feeds = new Collection(feedsGroupedByChannelId.get(this.id))
|
if (this.id) this.feeds = new Collection(feedsGroupedByChannelId.get(this.id))
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
withLogos(logosGroupedByChannelId: Dictionary): this {
|
||||||
|
if (this.id) this.logos = new Collection(logosGroupedByChannelId.get(this.id))
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
@@ -50,19 +57,19 @@ export class Channel {
|
|||||||
return this.feeds
|
return this.feeds
|
||||||
}
|
}
|
||||||
|
|
||||||
getGuides(): Collection {
|
getGuideChannels(): Collection {
|
||||||
let guides = new Collection()
|
let channels = new Collection()
|
||||||
|
|
||||||
this.getFeeds().forEach((feed: Feed) => {
|
this.getFeeds().forEach((feed: Feed) => {
|
||||||
guides = guides.concat(feed.getGuides())
|
channels = channels.concat(feed.getGuideChannels())
|
||||||
})
|
})
|
||||||
|
|
||||||
return guides
|
return channels
|
||||||
}
|
}
|
||||||
|
|
||||||
getGuideNames(): Collection {
|
getGuideChannelNames(): Collection {
|
||||||
return this.getGuides()
|
return this.getGuideChannels()
|
||||||
.map((guide: Guide) => guide.siteName)
|
.map((channel: GuideChannel) => channel.siteName)
|
||||||
.uniq()
|
.uniq()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,12 +107,56 @@ export class Channel {
|
|||||||
return this.altNames || new Collection()
|
return this.altNames || new Collection()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLogos(): Collection {
|
||||||
|
function feed(logo: Logo): number {
|
||||||
|
if (!logo.feed) return 1
|
||||||
|
if (logo.feed.isMain) return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function format(logo: Logo): number {
|
||||||
|
const levelByFormat: { [key: string]: number } = {
|
||||||
|
SVG: 0,
|
||||||
|
PNG: 3,
|
||||||
|
APNG: 1,
|
||||||
|
WebP: 1,
|
||||||
|
AVIF: 1,
|
||||||
|
JPEG: 2,
|
||||||
|
GIF: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return logo.format ? levelByFormat[logo.format] : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function size(logo: Logo): number {
|
||||||
|
return Math.abs(512 - logo.width) + Math.abs(512 - logo.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.logos.orderBy([feed, format, size], ['desc', 'desc', 'asc'], false)
|
||||||
|
}
|
||||||
|
|
||||||
|
getLogo(): Logo | undefined {
|
||||||
|
return this.getLogos().first()
|
||||||
|
}
|
||||||
|
|
||||||
|
hasLogo(): boolean {
|
||||||
|
return this.getLogos().notEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
getLogoUrl(): string {
|
||||||
|
const logo = this.getLogo()
|
||||||
|
if (!logo) return ''
|
||||||
|
|
||||||
|
return logo.url || ''
|
||||||
|
}
|
||||||
|
|
||||||
getSearchable(): ChannelSearchableData {
|
getSearchable(): ChannelSearchableData {
|
||||||
return {
|
return {
|
||||||
id: this.getId(),
|
id: this.getId(),
|
||||||
name: this.getName(),
|
name: this.getName(),
|
||||||
altNames: this.getAltNames().all(),
|
altNames: this.getAltNames().all(),
|
||||||
guideNames: this.getGuideNames().all(),
|
guideNames: this.getGuideChannelNames().all(),
|
||||||
streamNames: this.getStreamNames().all(),
|
streamNames: this.getStreamNames().all(),
|
||||||
feedFullNames: this.getFeedFullNames().all()
|
feedFullNames: this.getFeedFullNames().all()
|
||||||
}
|
}
|
||||||
|
|||||||
77
scripts/models/channelList.ts
Normal file
77
scripts/models/channelList.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { Collection } from '@freearhey/core'
|
||||||
|
import epgGrabber from 'epg-grabber'
|
||||||
|
|
||||||
|
export class ChannelList {
|
||||||
|
channels: Collection = new Collection()
|
||||||
|
|
||||||
|
constructor(data: { channels: epgGrabber.Channel[] }) {
|
||||||
|
this.channels = new Collection(data.channels)
|
||||||
|
}
|
||||||
|
|
||||||
|
add(channel: epgGrabber.Channel): this {
|
||||||
|
this.channels.add(channel)
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
get(siteId: string): epgGrabber.Channel | undefined {
|
||||||
|
return this.channels.find((channel: epgGrabber.Channel) => channel.site_id == siteId)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort(): this {
|
||||||
|
this.channels = this.channels.orderBy([
|
||||||
|
(channel: epgGrabber.Channel) => channel.lang || '_',
|
||||||
|
(channel: epgGrabber.Channel) => (channel.xmltv_id ? channel.xmltv_id.toLowerCase() : '0'),
|
||||||
|
(channel: epgGrabber.Channel) => channel.site_id
|
||||||
|
])
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
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, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''')
|
||||||
|
.replace(/\n|\r/g, ' ')
|
||||||
|
.replace(/ +/g, ' ')
|
||||||
|
.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = '<?xml version="1.0" encoding="UTF-8"?>\r\n<channels>\r\n'
|
||||||
|
|
||||||
|
this.channels.forEach((channel: epgGrabber.Channel) => {
|
||||||
|
const logo = channel.logo ? ` logo="${channel.logo}"` : ''
|
||||||
|
const xmltv_id = channel.xmltv_id ? escapeString(channel.xmltv_id) : ''
|
||||||
|
const lang = channel.lang || ''
|
||||||
|
const site_id = channel.site_id || ''
|
||||||
|
const site = channel.site || ''
|
||||||
|
const displayName = channel.name ? escapeString(channel.name) : ''
|
||||||
|
|
||||||
|
output += ` <channel site="${site}" lang="${lang}" xmltv_id="${xmltv_id}" site_id="${site_id}"${logo}>${displayName}</channel>\r\n`
|
||||||
|
})
|
||||||
|
|
||||||
|
output += '</channels>\r\n'
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Collection, Dictionary } from '@freearhey/core'
|
import { Collection, Dictionary } from '@freearhey/core'
|
||||||
import { FeedData } from '../types/feed'
|
import { FeedData } from '../types/feed'
|
||||||
import { Channel } from './channel'
|
import { Logo, Channel } from '.'
|
||||||
|
|
||||||
export class Feed {
|
export class Feed {
|
||||||
channelId: string
|
channelId: string
|
||||||
@@ -12,8 +12,9 @@ export class Feed {
|
|||||||
languageCodes: Collection
|
languageCodes: Collection
|
||||||
timezoneIds: Collection
|
timezoneIds: Collection
|
||||||
videoFormat: string
|
videoFormat: string
|
||||||
guides?: Collection
|
guideChannels?: Collection
|
||||||
streams?: Collection
|
streams?: Collection
|
||||||
|
logos: Collection = new Collection()
|
||||||
|
|
||||||
constructor(data: FeedData) {
|
constructor(data: FeedData) {
|
||||||
this.channelId = data.channel
|
this.channelId = data.channel
|
||||||
@@ -42,20 +43,30 @@ export class Feed {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
withGuides(guidesGroupedByStreamId: Dictionary): this {
|
withGuideChannels(guideChannelsGroupedByStreamId: Dictionary): this {
|
||||||
this.guides = new Collection(guidesGroupedByStreamId.get(`${this.channelId}@${this.id}`))
|
this.guideChannels = new Collection(
|
||||||
|
guideChannelsGroupedByStreamId.get(`${this.channelId}@${this.id}`)
|
||||||
|
)
|
||||||
|
|
||||||
if (this.isMain) {
|
if (this.isMain) {
|
||||||
this.guides = this.guides.concat(new Collection(guidesGroupedByStreamId.get(this.channelId)))
|
this.guideChannels = this.guideChannels.concat(
|
||||||
|
new Collection(guideChannelsGroupedByStreamId.get(this.channelId))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
getGuides(): Collection {
|
withLogos(logosGroupedByStreamId: Dictionary): this {
|
||||||
if (!this.guides) return new Collection()
|
this.logos = new Collection(logosGroupedByStreamId.get(this.getStreamId()))
|
||||||
|
|
||||||
return this.guides
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
getGuideChannels(): Collection {
|
||||||
|
if (!this.guideChannels) return new Collection()
|
||||||
|
|
||||||
|
return this.guideChannels
|
||||||
}
|
}
|
||||||
|
|
||||||
getStreams(): Collection {
|
getStreams(): Collection {
|
||||||
@@ -73,4 +84,41 @@ export class Feed {
|
|||||||
getStreamId(): string {
|
getStreamId(): string {
|
||||||
return `${this.channelId}@${this.id}`
|
return `${this.channelId}@${this.id}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLogos(): Collection {
|
||||||
|
function format(logo: Logo): number {
|
||||||
|
const levelByFormat: { [key: string]: number } = {
|
||||||
|
SVG: 0,
|
||||||
|
PNG: 3,
|
||||||
|
APNG: 1,
|
||||||
|
WebP: 1,
|
||||||
|
AVIF: 1,
|
||||||
|
JPEG: 2,
|
||||||
|
GIF: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return logo.format ? levelByFormat[logo.format] : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function size(logo: Logo): number {
|
||||||
|
return Math.abs(512 - logo.width) + Math.abs(512 - logo.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.logos.orderBy([format, size], ['desc', 'asc'], false)
|
||||||
|
}
|
||||||
|
|
||||||
|
getLogo(): Logo | undefined {
|
||||||
|
return this.getLogos().first()
|
||||||
|
}
|
||||||
|
|
||||||
|
hasLogo(): boolean {
|
||||||
|
return this.getLogos().notEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
getLogoUrl(): string {
|
||||||
|
const logo = this.getLogo()
|
||||||
|
if (!logo) return ''
|
||||||
|
|
||||||
|
return logo.url || ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,35 @@
|
|||||||
import type { GuideData } from '../types/guide'
|
import { Collection, DateTime } from '@freearhey/core'
|
||||||
import { uniqueId } from 'lodash'
|
import { generateXMLTV } from 'epg-grabber'
|
||||||
|
|
||||||
|
type GuideData = {
|
||||||
|
channels: Collection
|
||||||
|
programs: Collection
|
||||||
|
filepath: string
|
||||||
|
gzip: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export class Guide {
|
export class Guide {
|
||||||
channelId?: string
|
channels: Collection
|
||||||
feedId?: string
|
programs: Collection
|
||||||
siteDomain?: string
|
filepath: string
|
||||||
siteId?: string
|
gzip: boolean
|
||||||
siteName?: string
|
|
||||||
languageCode?: string
|
|
||||||
|
|
||||||
constructor(data?: GuideData) {
|
constructor({ channels, programs, filepath, gzip }: GuideData) {
|
||||||
if (!data) return
|
this.channels = channels
|
||||||
|
this.programs = programs
|
||||||
this.channelId = data.channel
|
this.filepath = filepath
|
||||||
this.feedId = data.feed
|
this.gzip = gzip || false
|
||||||
this.siteDomain = data.site
|
|
||||||
this.siteId = data.site_id
|
|
||||||
this.siteName = data.site_name
|
|
||||||
this.languageCode = data.lang
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getUUID(): string {
|
toString() {
|
||||||
if (!this.getStreamId() || !this.siteId) return uniqueId()
|
const currDate = new DateTime(process.env.CURR_DATE || new Date().toISOString(), {
|
||||||
|
timezone: 'UTC'
|
||||||
|
})
|
||||||
|
|
||||||
return this.getStreamId() + this.siteId
|
return generateXMLTV({
|
||||||
}
|
channels: this.channels.all(),
|
||||||
|
programs: this.programs.all(),
|
||||||
getStreamId(): string | undefined {
|
date: currDate.toJSON()
|
||||||
if (!this.channelId) return undefined
|
})
|
||||||
if (!this.feedId) return this.channelId
|
|
||||||
|
|
||||||
return `${this.channelId}@${this.feedId}`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
59
scripts/models/guideChannel.ts
Normal file
59
scripts/models/guideChannel.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { Dictionary } from '@freearhey/core'
|
||||||
|
import epgGrabber from 'epg-grabber'
|
||||||
|
import { Feed, Channel } from '.'
|
||||||
|
|
||||||
|
export class GuideChannel {
|
||||||
|
channelId?: string
|
||||||
|
channel?: Channel
|
||||||
|
feedId?: string
|
||||||
|
feed?: Feed
|
||||||
|
xmltvId?: string
|
||||||
|
languageCode?: string
|
||||||
|
siteId?: string
|
||||||
|
logoUrl?: string
|
||||||
|
siteDomain?: string
|
||||||
|
siteName?: string
|
||||||
|
|
||||||
|
constructor(data: epgGrabber.Channel) {
|
||||||
|
const [channelId, feedId] = data.xmltv_id ? data.xmltv_id.split('@') : [undefined, undefined]
|
||||||
|
|
||||||
|
this.channelId = channelId
|
||||||
|
this.feedId = feedId
|
||||||
|
this.xmltvId = data.xmltv_id
|
||||||
|
this.languageCode = data.lang
|
||||||
|
this.siteId = data.site_id
|
||||||
|
this.logoUrl = data.logo
|
||||||
|
this.siteDomain = data.site
|
||||||
|
this.siteName = data.name
|
||||||
|
}
|
||||||
|
|
||||||
|
withChannel(channelsKeyById: Dictionary): this {
|
||||||
|
if (this.channelId) this.channel = channelsKeyById.get(this.channelId)
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
withFeed(feedsKeyByStreamId: Dictionary): this {
|
||||||
|
if (this.feedId) this.feed = feedsKeyByStreamId.get(this.getStreamId())
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
getStreamId(): string {
|
||||||
|
if (!this.channelId) return ''
|
||||||
|
if (!this.feedId) return this.channelId
|
||||||
|
|
||||||
|
return `${this.channelId}@${this.feedId}`
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
channel: this.channelId || null,
|
||||||
|
feed: this.feedId || null,
|
||||||
|
site: this.siteDomain || '',
|
||||||
|
site_id: this.siteId || '',
|
||||||
|
site_name: this.siteName || '',
|
||||||
|
lang: this.languageCode || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
export * from './issue'
|
|
||||||
export * from './site'
|
|
||||||
export * from './channel'
|
export * from './channel'
|
||||||
export * from './feed'
|
export * from './feed'
|
||||||
export * from './stream'
|
|
||||||
export * from './guide'
|
export * from './guide'
|
||||||
|
export * from './guideChannel'
|
||||||
|
export * from './issue'
|
||||||
|
export * from './logo'
|
||||||
|
export * from './site'
|
||||||
|
export * from './stream'
|
||||||
|
export * from './channelList'
|
||||||
|
|||||||
41
scripts/models/logo.ts
Normal file
41
scripts/models/logo.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { Collection, type Dictionary } from '@freearhey/core'
|
||||||
|
import type { LogoData } from '../types/logo'
|
||||||
|
import { type Feed } from './feed'
|
||||||
|
|
||||||
|
export class Logo {
|
||||||
|
channelId?: string
|
||||||
|
feedId?: string
|
||||||
|
feed?: Feed
|
||||||
|
tags: Collection = new Collection()
|
||||||
|
width: number = 0
|
||||||
|
height: number = 0
|
||||||
|
format?: string
|
||||||
|
url?: string
|
||||||
|
|
||||||
|
constructor(data?: LogoData) {
|
||||||
|
if (!data) return
|
||||||
|
|
||||||
|
this.channelId = data.channel
|
||||||
|
this.feedId = data.feed || undefined
|
||||||
|
this.tags = new Collection(data.tags)
|
||||||
|
this.width = data.width
|
||||||
|
this.height = data.height
|
||||||
|
this.format = data.format || undefined
|
||||||
|
this.url = data.url
|
||||||
|
}
|
||||||
|
|
||||||
|
withFeed(feedsKeyByStreamId: Dictionary): this {
|
||||||
|
if (!this.feedId) return this
|
||||||
|
|
||||||
|
this.feed = feedsKeyByStreamId.get(this.getStreamId())
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
getStreamId(): string {
|
||||||
|
if (!this.channelId) return ''
|
||||||
|
if (!this.feedId) return this.channelId
|
||||||
|
|
||||||
|
return `${this.channelId}@${this.feedId}`
|
||||||
|
}
|
||||||
|
}
|
||||||
1
scripts/types/channel.d.ts
vendored
1
scripts/types/channel.d.ts
vendored
@@ -15,7 +15,6 @@ export type ChannelData = {
|
|||||||
closed: string
|
closed: string
|
||||||
replaced_by: string
|
replaced_by: string
|
||||||
website: string
|
website: string
|
||||||
logo: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ChannelSearchableData = {
|
export type ChannelSearchableData = {
|
||||||
|
|||||||
1
scripts/types/dataLoader.d.ts
vendored
1
scripts/types/dataLoader.d.ts
vendored
@@ -16,4 +16,5 @@ export type DataLoaderData = {
|
|||||||
timezones: object | object[]
|
timezones: object | object[]
|
||||||
guides: object | object[]
|
guides: object | object[]
|
||||||
streams: object | object[]
|
streams: object | object[]
|
||||||
|
logos: object | object[]
|
||||||
}
|
}
|
||||||
|
|||||||
8
scripts/types/dataProcessor.d.ts
vendored
8
scripts/types/dataProcessor.d.ts
vendored
@@ -1,12 +1,16 @@
|
|||||||
import { Collection, Dictionary } from '@freearhey/core'
|
import { Collection, Dictionary } from '@freearhey/core'
|
||||||
|
|
||||||
export type DataProcessorData = {
|
export type DataProcessorData = {
|
||||||
|
guideChannelsGroupedByStreamId: Dictionary
|
||||||
feedsGroupedByChannelId: Dictionary
|
feedsGroupedByChannelId: Dictionary
|
||||||
guidesGroupedByStreamId: Dictionary
|
logosGroupedByChannelId: Dictionary
|
||||||
|
logosGroupedByStreamId: Dictionary
|
||||||
|
feedsKeyByStreamId: Dictionary
|
||||||
streamsGroupedById: Dictionary
|
streamsGroupedById: Dictionary
|
||||||
channelsKeyById: Dictionary
|
channelsKeyById: Dictionary
|
||||||
|
guideChannels: Collection
|
||||||
channels: Collection
|
channels: Collection
|
||||||
streams: Collection
|
streams: Collection
|
||||||
guides: Collection
|
|
||||||
feeds: Collection
|
feeds: Collection
|
||||||
|
logos: Collection
|
||||||
}
|
}
|
||||||
|
|||||||
9
scripts/types/logo.d.ts
vendored
Normal file
9
scripts/types/logo.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export type LogoData = {
|
||||||
|
channel: string
|
||||||
|
feed: string | null
|
||||||
|
tags: string[]
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
format: string | null
|
||||||
|
url: string
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user