mirror of
https://github.com/iptv-org/epg
synced 2026-04-30 06:26:59 -04:00
continue uniformizing + ditch lodash for native JS methods.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { SiteConfig } from 'epg-grabber'
|
import { SiteConfig } from 'epg-grabber'
|
||||||
import _ from 'lodash'
|
import { deepMerge } from '../functions/functions'
|
||||||
import { pathToFileURL } from 'url'
|
import { pathToFileURL } from 'url'
|
||||||
|
|
||||||
export class ConfigLoader {
|
export class ConfigLoader {
|
||||||
@@ -28,6 +28,6 @@ export class ConfigLoader {
|
|||||||
channels: undefined
|
channels: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
return _.merge(defaultConfig, config)
|
return deepMerge(defaultConfig, config) as SiteConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
43
scripts/functions/functions.ts
Normal file
43
scripts/functions/functions.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// Made to replace lodash functions with their native alternatives. Typed for better TypeScript support.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new array of unique items based on an specific identifier.
|
||||||
|
* This function uses a Map to ensure that each item is unique based on the result of the provided function.
|
||||||
|
* @param {Array} arr - The array to filter for unique items
|
||||||
|
* @param {Function} fn - A function that takes an item and returns a unique identifier
|
||||||
|
* @returns {Array} A new array containing only unique items based on the identifier
|
||||||
|
* @example
|
||||||
|
* const items = [{ id: 1, name: 'A' }, { id: 2, name: 'B' }, { id: 1, name: 'C' }];
|
||||||
|
* const uniqueItems = uniqBy(items, item => item.id);
|
||||||
|
* // uniqueItems will be [{ id: 1, name: 'A' }, { id: 2, name: 'B' }]
|
||||||
|
*/
|
||||||
|
export const uniqBy = <T, K>(arr: T[], fn: (item: T) => K): T[] =>
|
||||||
|
Array.from(new Map(arr.map(item => [fn(item), item])).values())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively merges multiple objects into a single object.
|
||||||
|
* If the same key exists in multiple objects and the values are both objects,
|
||||||
|
* they will be deep merged. Otherwise, the latter value will override the former.
|
||||||
|
*
|
||||||
|
* @param {...object[]} a - An array of objects to be merged
|
||||||
|
* @returns {Record<string, unknown>} A new object containing all merged properties
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const obj1 = { a: { b: 2 }, c: 3 };
|
||||||
|
* const obj2 = { a: { d: 4 }, e: 5 };
|
||||||
|
* deepMerge(obj1, obj2); // { a: { b: 2, d: 4 }, c: 3, e: 5 }
|
||||||
|
*/
|
||||||
|
export const deepMerge = (...a: (object)[]): Record<string, unknown> =>
|
||||||
|
a.reduce((r: { [key: string]: unknown }, o) =>
|
||||||
|
(Object.entries(o).forEach(([k, v]) => { r[k] = r[k] && typeof r[k] === 'object' && typeof v === 'object' ?
|
||||||
|
deepMerge(r[k], v) : v }), r), {} as Record<string, unknown>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort an array of objects by a specific key.
|
||||||
|
*
|
||||||
|
* @param {string} key - The key to sort by
|
||||||
|
* @returns {function} A comparison function for sorting
|
||||||
|
*/
|
||||||
|
export const sortBy = <T>(key: keyof T): ((a: T, b: T) => number) => {
|
||||||
|
return (a: T, b: T) => (a[key] > b[key]) ? 1 : ((b[key] > a[key]) ? -1 : 0)
|
||||||
|
}
|
||||||
@@ -3,7 +3,9 @@ const utc = require('dayjs/plugin/utc')
|
|||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
const parseDuration = require('parse-duration').default
|
const parseDuration = require('parse-duration').default
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const _ = require('lodash')
|
|
||||||
|
// importing custom function sortBy
|
||||||
|
const sortBy = require('../functions/functions')
|
||||||
|
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
@@ -28,7 +30,7 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return _.sortBy(programs, p => p.start)
|
return programs.concat().sort(sortBy('start'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const dayjs = require('dayjs')
|
|||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
|
const { uniqBy } = require('../../functions/functions')
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
@@ -45,8 +46,7 @@ module.exports = {
|
|||||||
return programs
|
return programs
|
||||||
},
|
},
|
||||||
async channels({ country }) {
|
async channels({ country }) {
|
||||||
const _ = require('lodash')
|
|
||||||
|
|
||||||
const countries = {
|
const countries = {
|
||||||
ao: 'ago',
|
ao: 'ago',
|
||||||
bj: 'ben',
|
bj: 'ben',
|
||||||
@@ -114,7 +114,7 @@ module.exports = {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return _.uniqBy(channels, 'site_id')
|
return uniqBy(channels, 'site_id')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1
sites/tv.blue.ch/__data__/content.json
Normal file
1
sites/tv.blue.ch/__data__/content.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"Nodes":{"Count":1,"TotalItemCount":1,"Items":[{"Domain":"TV","Identifier":"1221","Kind":"Channel","Content":{"Description":{"Title":"blue Zoom D","Language":"de"},"Nodes":{"Count":29,"TotalItemCount":29,"Items":[{"Domain":"TV","Identifier":"t1221ddc59247d45","Kind":"Broadcast","Channel":"1221","Content":{"Description":{"Title":"Weekend on the Rocks","Summary":" - «R.E.S.P.E.C.T», lieber Charles Nguela. Der Comedian tourt fleissig durch die Schweiz, macht für uns aber einen Halt, um in der neuen Ausgabe von «Weekend on the Rocks» mit Moderatorin Vania Spescha über die Entertainment-News der Woche zu plaudern.","ShortSummary":"","Country":"CH","ReleaseDate":"2021-01-01T00:00:00Z","Source":"13","Language":"de","Duration":"00:30:00"},"Nodes":{"Count":1,"TotalItemCount":1,"Items":[{"Domain":"TV","Identifier":"t1221ddc59247d45_landscape","Kind":"Image","Role":"Landscape","ContentPath":"/tv/broadcast/1221/t1221ddc59247d45_landscape","Version":{"Date":"2022-01-04T08:55:22.567Z"}}]},"TechnicalAttributes":{"Stereo":true}},"Version":{"Hash":"60d3"},"Availabilities":[{"AvailabilityStart":"2022-01-16T23:30:00Z","AvailabilityEnd":"2022-01-17T00:00:00Z"}],"Relations":[{"Domain":"TV","Kind":"Reference","Role":"ChannelIdentifier","TargetIdentifier":"2b0898c7-3920-3200-7048-4ea5d9138921"},{"Domain":"TV","Kind":"Reference","Role":"OriginalAirSeries","TargetIdentifier":"false"},{"Domain":"TV","Kind":"Reference","Role":"ExternalBroadcastIdentifier","TargetIdentifier":"167324536-11"},{"Domain":"TV","Kind":"Reference","Role":"ProgramIdentifier","TargetIdentifier":"p12211351631155","Title":"Original"}]}]}}}]}}
|
||||||
1
sites/tv.blue.ch/__data__/content_invalid_siteid.json
Normal file
1
sites/tv.blue.ch/__data__/content_invalid_siteid.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"Status":{"Version":"7","Status":"OK","ProcessingTime":"00:00:00.0160674","ExecutionTime":"2022-01-17T13:47:30.584Z"},"Request":{"Domain":"TV","Resource":"Channels","Action":"List","Parameters":"(ids=12210;start=202201170000;end=202201180000;level=normal)","Identifiers":["12210"],"Start":"2022-01-17T00:00:00Z","End":"2022-01-18T00:00:00Z","DataLevel":"Normal"},"DataSource":{"Snapshot":"Tv_20220117114748","DbCreationTime":"2022-01-17T11:49:14.608Z","IncrementCreationTime":"0001-01-01T00:00:00Z"},"Nodes":{"Items":[]}}
|
||||||
1
sites/tv.blue.ch/__data__/content_without_image.json
Normal file
1
sites/tv.blue.ch/__data__/content_without_image.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"Nodes":{"Count":1,"TotalItemCount":1,"Items":[{"Domain":"TV","Identifier":"1221","Kind":"Channel","Content":{"Description":{"Title":"blue Zoom D","Language":"de"},"Nodes":{"Count":29,"TotalItemCount":29,"Items":[{"Domain":"TV","Identifier":"t10014a78a8b0668","Kind":"Broadcast","Channel":"1001","Content":{"Description":{"Title":"Lorem ipsum","Language":"fr","Duration":"00:01:00"}},"Version":{"Hash":"440e"},"Availabilities":[{"AvailabilityStart":"2022-01-17T04:59:00Z","AvailabilityEnd":"2022-01-17T05:00:00Z"}],"Relations":[{"Domain":"TV","Kind":"Reference","Role":"ChannelIdentifier","TargetIdentifier":"3553a4f2-ff63-5200-7048-d8d59d805f81"},{"Domain":"TV","Kind":"Reference","Role":"Dummy","TargetIdentifier":"True"},{"Domain":"TV","Kind":"Reference","Role":"ProgramIdentifier","TargetIdentifier":"p1"}]}]}}}]}}
|
||||||
1
sites/tv.blue.ch/__data__/no_content.json
Normal file
1
sites/tv.blue.ch/__data__/no_content.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"Status":{"Version":"7","Status":"OK","ExecutionTime":"2022-01-17T15:30:37.97Z"},"Request":{"Domain":"TV","Resource":"Channels","Action":"List","Parameters":"(ids=1884;start=202201170000;end=202201180000;level=normal)","Identifiers":["1884"],"Start":"2022-01-17T00:00:00Z","End":"2022-01-18T00:00:00Z","DataLevel":"Normal"},"DataSource":{"Snapshot":"Tv_20220117144354","DbCreationTime":"2022-01-17T14:45:11.84Z","IncrementCreationTime":"0001-01-01T00:00:00Z"},"Nodes":{"Count":1,"TotalItemCount":1,"Items":[{"Domain":"TV","Identifier":"1884","Kind":"Channel","Content":{"Description":{"Title":"Fisu.tv 1","Language":"en"}}}]}}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
const { parser, url } = require('./tv.blue.ch.config.js')
|
const { parser, url } = require('./tv.blue.ch.config.js')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
const customParseFormat = require('dayjs/plugin/customParseFormat')
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
@@ -18,8 +20,7 @@ it('can generate valid url', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response', () => {
|
it('can parse response', () => {
|
||||||
const content =
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'))
|
||||||
'{"Nodes":{"Count":1,"TotalItemCount":1,"Items":[{"Domain":"TV","Identifier":"1221","Kind":"Channel","Content":{"Description":{"Title":"blue Zoom D","Language":"de"},"Nodes":{"Count":29,"TotalItemCount":29,"Items":[{"Domain":"TV","Identifier":"t1221ddc59247d45","Kind":"Broadcast","Channel":"1221","Content":{"Description":{"Title":"Weekend on the Rocks","Summary":" - «R.E.S.P.E.C.T», lieber Charles Nguela. Der Comedian tourt fleissig durch die Schweiz, macht für uns aber einen Halt, um in der neuen Ausgabe von «Weekend on the Rocks» mit Moderatorin Vania Spescha über die Entertainment-News der Woche zu plaudern.","ShortSummary":"","Country":"CH","ReleaseDate":"2021-01-01T00:00:00Z","Source":"13","Language":"de","Duration":"00:30:00"},"Nodes":{"Count":1,"TotalItemCount":1,"Items":[{"Domain":"TV","Identifier":"t1221ddc59247d45_landscape","Kind":"Image","Role":"Landscape","ContentPath":"/tv/broadcast/1221/t1221ddc59247d45_landscape","Version":{"Date":"2022-01-04T08:55:22.567Z"}}]},"TechnicalAttributes":{"Stereo":true}},"Version":{"Hash":"60d3"},"Availabilities":[{"AvailabilityStart":"2022-01-16T23:30:00Z","AvailabilityEnd":"2022-01-17T00:00:00Z"}],"Relations":[{"Domain":"TV","Kind":"Reference","Role":"ChannelIdentifier","TargetIdentifier":"2b0898c7-3920-3200-7048-4ea5d9138921"},{"Domain":"TV","Kind":"Reference","Role":"OriginalAirSeries","TargetIdentifier":"false"},{"Domain":"TV","Kind":"Reference","Role":"ExternalBroadcastIdentifier","TargetIdentifier":"167324536-11"},{"Domain":"TV","Kind":"Reference","Role":"ProgramIdentifier","TargetIdentifier":"p12211351631155","Title":"Original"}]}]}}}]}}'
|
|
||||||
const result = parser({ content }).map(p => {
|
const result = parser({ content }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
@@ -40,8 +41,7 @@ it('can parse response', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('can parse response without image', () => {
|
it('can parse response without image', () => {
|
||||||
const content =
|
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_without_image.json'))
|
||||||
'{"Nodes":{"Count":1,"TotalItemCount":1,"Items":[{"Domain":"TV","Identifier":"1221","Kind":"Channel","Content":{"Description":{"Title":"blue Zoom D","Language":"de"},"Nodes":{"Count":29,"TotalItemCount":29,"Items":[{"Domain":"TV","Identifier":"t10014a78a8b0668","Kind":"Broadcast","Channel":"1001","Content":{"Description":{"Title":"Lorem ipsum","Language":"fr","Duration":"00:01:00"}},"Version":{"Hash":"440e"},"Availabilities":[{"AvailabilityStart":"2022-01-17T04:59:00Z","AvailabilityEnd":"2022-01-17T05:00:00Z"}],"Relations":[{"Domain":"TV","Kind":"Reference","Role":"ChannelIdentifier","TargetIdentifier":"3553a4f2-ff63-5200-7048-d8d59d805f81"},{"Domain":"TV","Kind":"Reference","Role":"Dummy","TargetIdentifier":"True"},{"Domain":"TV","Kind":"Reference","Role":"ProgramIdentifier","TargetIdentifier":"p1"}]}]}}}]}}'
|
|
||||||
const result = parser({ content }).map(p => {
|
const result = parser({ content }).map(p => {
|
||||||
p.start = p.start.toJSON()
|
p.start = p.start.toJSON()
|
||||||
p.stop = p.stop.toJSON()
|
p.stop = p.stop.toJSON()
|
||||||
@@ -59,16 +59,14 @@ it('can parse response without image', () => {
|
|||||||
|
|
||||||
it('can handle wrong site id', () => {
|
it('can handle wrong site id', () => {
|
||||||
const result = parser({
|
const result = parser({
|
||||||
content:
|
content: fs.readFileSync(path.resolve(__dirname, '__data__/content_invalid_siteid.json'))
|
||||||
'{"Status":{"Version":"7","Status":"OK","ProcessingTime":"00:00:00.0160674","ExecutionTime":"2022-01-17T13:47:30.584Z"},"Request":{"Domain":"TV","Resource":"Channels","Action":"List","Parameters":"(ids=12210;start=202201170000;end=202201180000;level=normal)","Identifiers":["12210"],"Start":"2022-01-17T00:00:00Z","End":"2022-01-18T00:00:00Z","DataLevel":"Normal"},"DataSource":{"Snapshot":"Tv_20220117114748","DbCreationTime":"2022-01-17T11:49:14.608Z","IncrementCreationTime":"0001-01-01T00:00:00Z"},"Nodes":{"Items":[]}}'
|
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const result = parser({
|
const result = parser({
|
||||||
content:
|
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
|
||||||
'{"Status":{"Version":"7","Status":"OK","ExecutionTime":"2022-01-17T15:30:37.97Z"},"Request":{"Domain":"TV","Resource":"Channels","Action":"List","Parameters":"(ids=1884;start=202201170000;end=202201180000;level=normal)","Identifiers":["1884"],"Start":"2022-01-17T00:00:00Z","End":"2022-01-18T00:00:00Z","DataLevel":"Normal"},"DataSource":{"Snapshot":"Tv_20220117144354","DbCreationTime":"2022-01-17T14:45:11.84Z","IncrementCreationTime":"0001-01-01T00:00:00Z"},"Nodes":{"Count":1,"TotalItemCount":1,"Items":[{"Domain":"TV","Identifier":"1884","Kind":"Channel","Content":{"Description":{"Title":"Fisu.tv 1","Language":"en"}}}]}}'
|
|
||||||
})
|
})
|
||||||
expect(result).toMatchObject([])
|
expect(result).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|||||||
1
sites/tv.lv/__data__/no_content.json
Normal file
1
sites/tv.lv/__data__/no_content.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"schedule":{"programme":[],"dayName":"Sestdiena","date":"30.11.2024"},"diff":368,"nextDate":"01-12-2024","previousDate":"29-11-2024","current_timestamp":1701194084}
|
||||||
@@ -48,8 +48,7 @@ it('can parse response', () => {
|
|||||||
|
|
||||||
it('can handle empty guide', () => {
|
it('can handle empty guide', () => {
|
||||||
const results = parser({
|
const results = parser({
|
||||||
content:
|
content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))
|
||||||
'{"schedule":{"programme":[],"dayName":"Sestdiena","date":"30.11.2024"},"diff":368,"nextDate":"01-12-2024","previousDate":"29-11-2024","current_timestamp":1701194084}'
|
|
||||||
})
|
})
|
||||||
expect(results).toMatchObject([])
|
expect(results).toMatchObject([])
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ const API_ENDPOINT = 'https://tv-at-prod.yo-digital.com/at-bifrost'
|
|||||||
const headers = {
|
const headers = {
|
||||||
'Device-Id': crypto.randomUUID(),
|
'Device-Id': crypto.randomUUID(),
|
||||||
app_key: 'CTnKA63ruKM0JM1doxAXwwyQLLmQiEiy',
|
app_key: 'CTnKA63ruKM0JM1doxAXwwyQLLmQiEiy',
|
||||||
app_version: '02.0.830',
|
app_version: '02.0.1260',
|
||||||
'X-User-Agent': 'web|web|Firefox-120|02.0.830|1',
|
'X-User-Agent': 'web|web|Firefox-120|02.0.1260|1',
|
||||||
'x-request-tracking-id': crypto.randomUUID()
|
'x-request-tracking-id': crypto.randomUUID()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,18 +3,7 @@ const axios = require('axios')
|
|||||||
|
|
||||||
// Remove the big lodash dependency by implementing a simple uniqBy function
|
// Remove the big lodash dependency by implementing a simple uniqBy function
|
||||||
// Complexity = O(n)
|
// Complexity = O(n)
|
||||||
const uniqBy = (arr, predicate) => {
|
const uniqBy = (arr, fn) => [...new Map(arr.map(x => [fn(x), x])).values()]
|
||||||
const cb = typeof predicate === 'function' ? predicate : (o) => o[predicate]
|
|
||||||
|
|
||||||
return [...arr.reduce((map, item) => {
|
|
||||||
const key = (item === null || item === undefined) ?
|
|
||||||
item : cb(item)
|
|
||||||
|
|
||||||
if (!map.has(key)) map.set(key, item)
|
|
||||||
|
|
||||||
return map
|
|
||||||
}, new Map()).values()]
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
site: 'tv.mail.ru',
|
site: 'tv.mail.ru',
|
||||||
|
|||||||
Reference in New Issue
Block a user